1 /* GENERATED SOURCE. DO NOT MODIFY. */ 2 // 2016 and later: Unicode, Inc. and others. 3 // License & terms of use: http://www.unicode.org/copyright.html#License 4 /* 5 ******************************************************************************* 6 * Copyright (C) 2001-2010, International Business Machines Corporation and * 7 * others. All Rights Reserved. * 8 ******************************************************************************* 9 */ 10 11 /** 12 * Port From: ICU4C v1.8.1 : format : DateFormatRoundTripTest 13 * Source File: $ICU4CRoot/source/test/intltest/dtfmtrtts.cpp 14 **/ 15 16 package android.icu.dev.test.format; 17 18 import java.text.FieldPosition; 19 import java.text.ParseException; 20 import java.util.Date; 21 import java.util.Locale; 22 import java.util.Random; 23 24 import org.junit.Test; 25 import org.junit.runner.RunWith; 26 import org.junit.runners.JUnit4; 27 28 import android.icu.dev.test.TestFmwk; 29 import android.icu.text.DateFormat; 30 import android.icu.text.SimpleDateFormat; 31 import android.icu.util.Calendar; 32 import android.icu.util.GregorianCalendar; 33 import android.icu.util.TimeZone; 34 import android.icu.testsharding.MainTestShard; 35 36 /** 37 * Performs round-trip tests for DateFormat 38 **/ 39 @MainTestShard 40 @RunWith(JUnit4.class) 41 public class DateFormatRoundTripTest extends TestFmwk { 42 public boolean INFINITE = false; 43 public boolean quick = true; 44 private SimpleDateFormat dateFormat; 45 private Calendar getFieldCal; 46 private int SPARSENESS = 18; 47 private int TRIALS = 4; 48 private int DEPTH = 5; 49 private Random ran; 50 51 // TODO: test is randomly failing depending on the randomly generated date 52 @Test 53 public void TestDateFormatRoundTrip() { 54 dateFormat = new SimpleDateFormat("EEE MMM dd HH:mm:ss.SSS zzz yyyy G"); 55 getFieldCal = Calendar.getInstance(); 56 ran = createRandom(); // use test framework's random seed 57 58 final Locale[] avail = DateFormat.getAvailableLocales(); 59 int locCount = avail.length; 60 logln("DateFormat available locales: " + locCount); 61 if (quick) { 62 if (locCount > 5) 63 locCount = 5; 64 logln("Quick mode: only testing first 5 Locales"); 65 } 66 TimeZone tz = TimeZone.getDefault(); 67 logln("Default TimeZone: " + tz.getID()); 68 69 if (INFINITE) { 70 // Special infinite loop test mode for finding hard to reproduce errors 71 Locale loc = Locale.getDefault(); 72 logln("ENTERING INFINITE TEST LOOP FOR Locale: " + loc.getDisplayName()); 73 for (;;) { 74 _test(loc); 75 } 76 } else { 77 _test(Locale.getDefault()); 78 for (int i = 0; i < locCount; ++i) { 79 _test(avail[i]); 80 } 81 } 82 } 83 84 private String styleName(int s) { 85 switch (s) { 86 case DateFormat.SHORT : 87 return "SHORT"; 88 case DateFormat.MEDIUM : 89 return "MEDIUM"; 90 case DateFormat.LONG : 91 return "LONG"; 92 case DateFormat.FULL : 93 return "FULL"; 94 default : 95 return "Unknown"; 96 } 97 } 98 99 private void _test(Locale loc) { 100 if (!INFINITE) { 101 logln("Locale: " + loc.getDisplayName()); 102 } 103 // Total possibilities = 24 104 // 4 date 105 // 4 time 106 // 16 date-time 107 boolean[] TEST_TABLE = new boolean[24]; 108 int i = 0; 109 for (i = 0; i < 24; ++i) 110 TEST_TABLE[i] = true; 111 112 // If we have some sparseness, implement it here. Sparseness decreases 113 // test time by eliminating some tests, up to 23. 114 for (i = 0; i < SPARSENESS; i++) { 115 int random = (int) (ran.nextDouble() * 24); 116 if (random >= 0 && random < 24 && TEST_TABLE[i]) { 117 TEST_TABLE[random] = false; 118 } 119 } 120 121 int itable = 0; 122 int style = 0; 123 for (style = DateFormat.FULL; style <= DateFormat.SHORT; ++style) { 124 if (TEST_TABLE[itable++]) { 125 logln("Testing style " + styleName(style)); 126 DateFormat df = DateFormat.getDateInstance(style, loc); 127 _test(df, false); 128 } 129 } 130 131 for (style = DateFormat.FULL; style <= DateFormat.SHORT; ++style) { 132 if (TEST_TABLE[itable++]) { 133 logln("Testing style " + styleName(style)); 134 DateFormat df = DateFormat.getTimeInstance(style, loc); 135 _test(df, true); 136 } 137 } 138 139 for (int dstyle = DateFormat.FULL; dstyle <= DateFormat.SHORT; ++dstyle) { 140 for (int tstyle = DateFormat.FULL; tstyle <= DateFormat.SHORT; ++tstyle) { 141 if (TEST_TABLE[itable++]) { 142 logln("Testing dstyle " + styleName(dstyle) + ", tstyle " + styleName(tstyle)); 143 DateFormat df = DateFormat.getDateTimeInstance(dstyle, tstyle, loc); 144 _test(df, false); 145 } 146 } 147 } 148 } 149 150 private void _test(DateFormat fmt, boolean timeOnly) { 151 152 if (!(fmt instanceof SimpleDateFormat)) { 153 errln("DateFormat wasn't a SimpleDateFormat"); 154 return; 155 } 156 157 String pat = ((SimpleDateFormat) fmt).toPattern(); 158 logln(pat); 159 160 // NOTE TO MAINTAINER 161 // This indexOf check into the pattern needs to be refined to ignore 162 // quoted characters. Currently, this isn't a problem with the locale 163 // patterns we have, but it may be a problem later. 164 165 boolean hasEra = (pat.indexOf("G") != -1); 166 boolean hasZoneDisplayName = (pat.indexOf("z") != -1) || (pat.indexOf("v") != -1) || (pat.indexOf("V") != -1); 167 boolean hasTwoDigitYear = pat.indexOf("yy") >= 0 && pat.indexOf("yyy") < 0; 168 169 // Because patterns contain incomplete data representing the Date, 170 // we must be careful of how we do the roundtrip. We start with 171 // a randomly generated Date because they're easier to generate. 172 // From this we get a string. The string is our real starting point, 173 // because this string should parse the same way all the time. Note 174 // that it will not necessarily parse back to the original date because 175 // of incompleteness in patterns. For example, a time-only pattern won't 176 // parse back to the same date. 177 178 try { 179 for (int i = 0; i < TRIALS; ++i) { 180 Date[] d = new Date[DEPTH]; 181 String[] s = new String[DEPTH]; 182 183 d[0] = generateDate(); 184 185 // We go through this loop until we achieve a match or until 186 // the maximum loop count is reached. We record the points at 187 // which the date and the string starts to match. Once matching 188 // starts, it should continue. 189 int loop; 190 int dmatch = 0; // d[dmatch].getTime() == d[dmatch-1].getTime() 191 int smatch = 0; // s[smatch].equals(s[smatch-1]) 192 for (loop = 0; loop < DEPTH; ++loop) { 193 if (loop > 0) { 194 d[loop] = fmt.parse(s[loop - 1]); 195 } 196 197 s[loop] = fmt.format(d[loop]); 198 199 if (loop > 0) { 200 if (smatch == 0) { 201 boolean match = s[loop].equals(s[loop - 1]); 202 if (smatch == 0) { 203 if (match) 204 smatch = loop; 205 } else 206 if (!match) 207 errln("FAIL: String mismatch after match"); 208 } 209 210 if (dmatch == 0) { 211 // {sfb} watch out here, this might not work 212 boolean match = d[loop].getTime() == d[loop - 1].getTime(); 213 if (dmatch == 0) { 214 if (match) 215 dmatch = loop; 216 } else 217 if (!match) 218 errln("FAIL: Date mismatch after match"); 219 } 220 221 if (smatch != 0 && dmatch != 0) 222 break; 223 } 224 } 225 // At this point loop == DEPTH if we've failed, otherwise loop is the 226 // max(smatch, dmatch), that is, the index at which we have string and 227 // date matching. 228 229 // Date usually matches in 2. Exceptions handled below. 230 int maxDmatch = 2; 231 int maxSmatch = 1; 232 if (dmatch > maxDmatch || smatch > maxSmatch) { 233 //If the Date is BC 234 if (!timeOnly && !hasEra && getField(d[0], Calendar.ERA) == GregorianCalendar.BC) { 235 maxDmatch = 3; 236 maxSmatch = 2; 237 } 238 if (hasZoneDisplayName && 239 (fmt.getTimeZone().inDaylightTime(d[0]) 240 || fmt.getTimeZone().inDaylightTime(d[1]) 241 || d[0].getTime() < 0L /* before 1970 */ 242 || hasTwoDigitYear && d[1].getTime() < 0L 243 /* before 1970 as the result of 2-digit year parse */)) { 244 maxSmatch = 2; 245 if (timeOnly) { 246 maxDmatch = 3; 247 } 248 } 249 } 250 251 if (dmatch > maxDmatch || smatch > maxSmatch) { 252 SimpleDateFormat sdf = new SimpleDateFormat("EEEE, MMMM d, yyyy HH:mm:ss, z G", Locale.US); 253 logln("Date = " + sdf.format(d[0]) + "; ms = " + d[0].getTime()); 254 logln("Dmatch: " + dmatch + " maxD: " + maxDmatch + " Smatch:" + smatch + " maxS:" + maxSmatch); 255 for (int j = 0; j <= loop && j < DEPTH; ++j) { 256 StringBuffer temp = new StringBuffer(""); 257 FieldPosition pos = new FieldPosition(0); 258 logln((j > 0 ? " P> " : " ") + dateFormat.format(d[j], temp, pos) 259 + " F> " + s[j] + (j > 0 && d[j].getTime() == d[j - 1].getTime() ? " d==" : "") 260 + (j > 0 && s[j].equals(s[j - 1]) ? " s==" : "")); 261 } 262 errln("Pattern: " + pat + " failed to match" + "; ms = " + d[0].getTime()); 263 } 264 } 265 } catch (ParseException e) { 266 errln("Exception: " + e.getMessage()); 267 logln(e.toString()); 268 } 269 } 270 271 private int getField(Date d, int f) { 272 getFieldCal.setTime(d); 273 int ret = getFieldCal.get(f); 274 return ret; 275 } 276 277 private Date generateDate() { 278 double a = ran.nextDouble(); 279 // Now 'a' ranges from 0..1; scale it to range from 0 to 8000 years 280 a *= 8000; 281 // Range from (4000-1970) BC to (8000-1970) AD 282 a -= 4000; 283 // Now scale up to ms 284 a *= 365.25 * 24 * 60 * 60 * 1000; 285 return new Date((long)a); 286 } 287 } 288