2 *******************************************************************************
\r
3 * Copyright (C) 2001-2010, International Business Machines Corporation and *
\r
4 * others. All Rights Reserved. *
\r
5 *******************************************************************************
\r
9 * Port From: ICU4C v1.8.1 : format : DateFormatRoundTripTest
\r
10 * Source File: $ICU4CRoot/source/test/intltest/dtfmtrtts.cpp
\r
13 package com.ibm.icu.dev.test.format;
\r
15 import java.text.FieldPosition;
\r
16 import java.text.ParseException;
\r
17 import java.util.Date;
\r
18 import java.util.Locale;
\r
19 import java.util.Random;
\r
21 import com.ibm.icu.text.DateFormat;
\r
22 import com.ibm.icu.text.SimpleDateFormat;
\r
23 import com.ibm.icu.util.Calendar;
\r
24 import com.ibm.icu.util.GregorianCalendar;
\r
25 import com.ibm.icu.util.TimeZone;
\r
28 * Performs round-trip tests for DateFormat
\r
30 public class DateFormatRoundTripTest extends com.ibm.icu.dev.test.TestFmwk {
\r
31 public boolean INFINITE = false;
\r
32 public boolean quick = true;
\r
33 private SimpleDateFormat dateFormat;
\r
34 private Calendar getFieldCal;
\r
35 private int SPARSENESS = 18;
\r
36 private int TRIALS = 4;
\r
37 private int DEPTH = 5;
\r
40 public static void main(String[] args) throws Exception {
\r
41 new DateFormatRoundTripTest().run(args);
\r
44 public void TestDateFormatRoundTrip() {
\r
45 dateFormat = new SimpleDateFormat("EEE MMM dd HH:mm:ss.SSS zzz yyyy G");
\r
46 getFieldCal = Calendar.getInstance();
\r
47 ran = createRandom(); // use test framework's random seed
\r
49 final Locale[] avail = DateFormat.getAvailableLocales();
\r
50 int locCount = avail.length;
\r
51 logln("DateFormat available locales: " + locCount);
\r
55 logln("Quick mode: only testing first 5 Locales");
\r
57 TimeZone tz = TimeZone.getDefault();
\r
58 logln("Default TimeZone: " + tz.getID());
\r
61 // Special infinite loop test mode for finding hard to reproduce errors
\r
62 Locale loc = Locale.getDefault();
\r
63 logln("ENTERING INFINITE TEST LOOP FOR Locale: " + loc.getDisplayName());
\r
68 _test(Locale.getDefault());
\r
69 for (int i = 0; i < locCount; ++i) {
\r
75 public String styleName(int s) {
\r
77 case DateFormat.SHORT :
\r
79 case DateFormat.MEDIUM :
\r
81 case DateFormat.LONG :
\r
83 case DateFormat.FULL :
\r
90 public void _test(Locale loc) {
\r
92 logln("Locale: " + loc.getDisplayName());
\r
94 // Total possibilities = 24
\r
98 boolean[] TEST_TABLE = new boolean[24];
\r
100 for (i = 0; i < 24; ++i)
\r
101 TEST_TABLE[i] = true;
\r
103 // If we have some sparseness, implement it here. Sparseness decreases
\r
104 // test time by eliminating some tests, up to 23.
\r
105 for (i = 0; i < SPARSENESS; i++) {
\r
106 int random = (int) (ran.nextDouble() * 24);
\r
107 if (random >= 0 && random < 24 && TEST_TABLE[i]) {
\r
108 TEST_TABLE[random] = false;
\r
114 for (style = DateFormat.FULL; style <= DateFormat.SHORT; ++style) {
\r
115 if (TEST_TABLE[itable++]) {
\r
116 logln("Testing style " + styleName(style));
\r
117 DateFormat df = DateFormat.getDateInstance(style, loc);
\r
122 for (style = DateFormat.FULL; style <= DateFormat.SHORT; ++style) {
\r
123 if (TEST_TABLE[itable++]) {
\r
124 logln("Testing style " + styleName(style));
\r
125 DateFormat df = DateFormat.getTimeInstance(style, loc);
\r
130 for (int dstyle = DateFormat.FULL; dstyle <= DateFormat.SHORT; ++dstyle) {
\r
131 for (int tstyle = DateFormat.FULL; tstyle <= DateFormat.SHORT; ++tstyle) {
\r
132 if (TEST_TABLE[itable++]) {
\r
133 logln("Testing dstyle " + styleName(dstyle) + ", tstyle " + styleName(tstyle));
\r
134 DateFormat df = DateFormat.getDateTimeInstance(dstyle, tstyle, loc);
\r
141 public void _test(DateFormat fmt, boolean timeOnly) {
\r
143 if (!(fmt instanceof SimpleDateFormat)) {
\r
144 errln("DateFormat wasn't a SimpleDateFormat");
\r
148 String pat = ((SimpleDateFormat) fmt).toPattern();
\r
151 // NOTE TO MAINTAINER
\r
152 // This indexOf check into the pattern needs to be refined to ignore
\r
153 // quoted characters. Currently, this isn't a problem with the locale
\r
154 // patterns we have, but it may be a problem later.
\r
156 boolean hasEra = (pat.indexOf("G") != -1);
\r
157 boolean hasZoneDisplayName = (pat.indexOf("z") != -1) || (pat.indexOf("v") != -1) || (pat.indexOf("V") != -1);
\r
159 // Because patterns contain incomplete data representing the Date,
\r
160 // we must be careful of how we do the roundtrip. We start with
\r
161 // a randomly generated Date because they're easier to generate.
\r
162 // From this we get a string. The string is our real starting point,
\r
163 // because this string should parse the same way all the time. Note
\r
164 // that it will not necessarily parse back to the original date because
\r
165 // of incompleteness in patterns. For example, a time-only pattern won't
\r
166 // parse back to the same date.
\r
169 for (int i = 0; i < TRIALS; ++i) {
\r
170 Date[] d = new Date[DEPTH];
\r
171 String[] s = new String[DEPTH];
\r
173 d[0] = generateDate();
\r
175 // We go through this loop until we achieve a match or until
\r
176 // the maximum loop count is reached. We record the points at
\r
177 // which the date and the string starts to match. Once matching
\r
178 // starts, it should continue.
\r
180 int dmatch = 0; // d[dmatch].getTime() == d[dmatch-1].getTime()
\r
181 int smatch = 0; // s[smatch].equals(s[smatch-1])
\r
182 for (loop = 0; loop < DEPTH; ++loop) {
\r
184 d[loop] = fmt.parse(s[loop - 1]);
\r
187 s[loop] = fmt.format(d[loop]);
\r
191 boolean match = s[loop].equals(s[loop - 1]);
\r
197 errln("FAIL: String mismatch after match");
\r
201 // {sfb} watch out here, this might not work
\r
202 boolean match = d[loop].getTime() == d[loop - 1].getTime();
\r
208 errln("FAIL: Date mismatch after match");
\r
211 if (smatch != 0 && dmatch != 0)
\r
215 // At this point loop == DEPTH if we've failed, otherwise loop is the
\r
216 // max(smatch, dmatch), that is, the index at which we have string and
\r
219 // Date usually matches in 2. Exceptions handled below.
\r
222 if (dmatch > maxDmatch || smatch > maxSmatch) {
\r
223 //If the Date is BC
\r
224 if (!timeOnly && !hasEra && getField(d[0], Calendar.ERA) == GregorianCalendar.BC) {
\r
228 if (hasZoneDisplayName &&
\r
229 (fmt.getTimeZone().inDaylightTime(d[0])
\r
230 || fmt.getTimeZone().inDaylightTime(d[1])
\r
231 || d[0].getTime() < 0L /* before 1970 */)) {
\r
239 if (dmatch > maxDmatch || smatch > maxSmatch) {
\r
240 SimpleDateFormat sdf = new SimpleDateFormat("EEEE, MMMM d, yyyy HH:mm:ss, z G", Locale.US);
\r
241 logln("Date = " + sdf.format(d[0]) + "; ms = " + d[0].getTime());
\r
242 logln("Dmatch: " + dmatch + " maxD: " + maxDmatch + " Smatch:" + smatch + " maxS:" + maxSmatch);
\r
243 for (int j = 0; j <= loop && j < DEPTH; ++j) {
\r
244 StringBuffer temp = new StringBuffer("");
\r
245 FieldPosition pos = new FieldPosition(0);
\r
246 logln((j > 0 ? " P> " : " ") + dateFormat.format(d[j], temp, pos)
\r
247 + " F> " + s[j] + (j > 0 && d[j].getTime() == d[j - 1].getTime() ? " d==" : "")
\r
248 + (j > 0 && s[j].equals(s[j - 1]) ? " s==" : ""));
\r
250 errln("Pattern: " + pat + " failed to match" + "; ms = " + d[0].getTime());
\r
253 } catch (ParseException e) {
\r
254 errln("Exception: " + e.getMessage());
\r
255 logln(e.toString());
\r
259 public int getField(Date d, int f) {
\r
260 getFieldCal.setTime(d);
\r
261 int ret = getFieldCal.get(f);
\r
265 public Date generateDate() {
\r
266 double a = ran.nextDouble();
\r
267 // Now 'a' ranges from 0..1; scale it to range from 0 to 8000 years
\r
269 // Range from (4000-1970) BC to (8000-1970) AD
\r
271 // Now scale up to ms
\r
272 a *= 365.25 * 24 * 60 * 60 * 1000;
\r
273 return new Date((long)a);
\r