1 /* 2 * Copyright (c) 1997, 2016, Oracle and/or its affiliates. All rights reserved. 3 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. 4 * 5 * This code is free software; you can redistribute it and/or modify it 6 * under the terms of the GNU General Public License version 2 only, as 7 * published by the Free Software Foundation. 8 * 9 * This code is distributed in the hope that it will be useful, but WITHOUT 10 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 11 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License 12 * version 2 for more details (a copy is included in the LICENSE file that 13 * accompanied this code). 14 * 15 * You should have received a copy of the GNU General Public License version 16 * 2 along with this work; if not, write to the Free Software Foundation, 17 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. 18 * 19 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA 20 * or visit www.oracle.com if you need additional information or have any 21 * questions. 22 */ 23 24 /* 25 * @test 26 * @summary test Date Format (Round Trip) 27 * @bug 8008577 28 * @library /java/text/testlib 29 * @run main/othervm -Djava.locale.providers=COMPAT,SPI DateFormatRoundTripTest 30 */ 31 32 import java.text.*; 33 import java.util.*; 34 35 public class DateFormatRoundTripTest extends IntlTest { 36 37 static Random RANDOM = null; 38 39 static final long FIXED_SEED = 3141592653589793238L; // Arbitrary fixed value 40 41 // Useful for turning up subtle bugs: Use -infinite and run while at lunch. 42 boolean INFINITE = false; // Warning -- makes test run infinite loop!!! 43 44 boolean random = false; 45 46 // Options used to reproduce failures 47 Locale locale = null; 48 String pattern = null; 49 Date initialDate = null; 50 51 Locale[] avail; 52 TimeZone defaultZone; 53 54 // If SPARSENESS is > 0, we don't run each exhaustive possibility. 55 // There are 24 total possible tests per each locale. A SPARSENESS 56 // of 12 means we run half of them. A SPARSENESS of 23 means we run 57 // 1 of them. SPARSENESS _must_ be in the range 0..23. 58 static final int SPARSENESS = 18; 59 60 static final int TRIALS = 4; 61 62 static final int DEPTH = 5; 63 64 static SimpleDateFormat refFormat = 65 new SimpleDateFormat("EEE MMM dd HH:mm:ss.SSS zzz yyyy G"); 66 DateFormatRoundTripTest(boolean rand, long seed, boolean infinite, Date date, String pat, Locale loc)67 public DateFormatRoundTripTest(boolean rand, long seed, boolean infinite, 68 Date date, String pat, Locale loc) { 69 random = rand; 70 if (random) { 71 RANDOM = new Random(seed); 72 } 73 INFINITE = infinite; 74 75 initialDate = date; 76 locale = loc; 77 pattern = pat; 78 } 79 80 /** 81 * Parse a name like "fr_FR" into new Locale("fr", "FR", ""); 82 */ createLocale(String name)83 static Locale createLocale(String name) { 84 String country = "", 85 variant = ""; 86 int i; 87 if ((i = name.indexOf('_')) >= 0) { 88 country = name.substring(i+1); 89 name = name.substring(0, i); 90 } 91 if ((i = country.indexOf('_')) >= 0) { 92 variant = country.substring(i+1); 93 country = country.substring(0, i); 94 } 95 return new Locale(name, country, variant); 96 } 97 main(String[] args)98 public static void main(String[] args) throws Exception { 99 // Command-line parameters 100 Locale loc = null; 101 boolean infinite = false; 102 boolean random = false; 103 long seed = FIXED_SEED; 104 String pat = null; 105 Date date = null; 106 107 List<String> newArgs = new ArrayList<>(); 108 for (int i=0; i<args.length; ++i) { 109 if (args[i].equals("-locale") 110 && (i+1) < args.length) { 111 loc = createLocale(args[i+1]); 112 ++i; 113 } else if (args[i].equals("-date") 114 && (i+1) < args.length) { 115 date = new Date(Long.parseLong(args[i+1])); 116 ++i; 117 } else if (args[i].equals("-pattern") 118 && (i+1) < args.length) { 119 pat = args[i+1]; 120 ++i; 121 } else if (args[i].equals("-INFINITE")) { 122 infinite = true; 123 } else if (args[i].equals("-random")) { 124 random = true; 125 } else if (args[i].equals("-randomseed")) { 126 random = true; 127 seed = System.currentTimeMillis(); 128 } else if (args[i].equals("-seed") 129 && (i+1) < args.length) { 130 random = true; 131 seed = Long.parseLong(args[i+1]); 132 ++i; 133 } else { 134 newArgs.add(args[i]); 135 } 136 } 137 138 if (newArgs.size() != args.length) { 139 args = new String[newArgs.size()]; 140 newArgs.addAll(Arrays.asList(args)); 141 } 142 143 new DateFormatRoundTripTest(random, seed, infinite, date, pat, loc).run(args); 144 } 145 146 /** 147 * Print a usage message for this test class. 148 */ usage()149 void usage() { 150 System.out.println(getClass().getName() + 151 ": [-pattern <pattern>] [-locale <locale>] [-date <ms>] [-INFINITE]"); 152 System.out.println(" [-random | -randomseed | -seed <seed>]"); 153 System.out.println("* Warning: Some patterns will fail with some locales."); 154 System.out.println("* Do not use -pattern unless you know what you are doing!"); 155 System.out.println("When specifying a locale, use a format such as fr_FR."); 156 System.out.println("Use -pattern, -locale, and -date to reproduce a failure."); 157 System.out.println("-random Random with fixed seed (same data every run)."); 158 System.out.println("-randomseed Random with a random seed."); 159 System.out.println("-seed <s> Random using <s> as seed."); 160 super.usage(); 161 } 162 163 static private class TestCase { 164 private int[] date; 165 TimeZone zone; 166 FormatFactory ff; 167 boolean timeOnly; 168 private Date _date; 169 TestCase(int[] d, TimeZone z, FormatFactory f, boolean timeOnly)170 TestCase(int[] d, TimeZone z, FormatFactory f, boolean timeOnly) { 171 date = d; 172 zone = z; 173 ff = f; 174 this.timeOnly = timeOnly; 175 } 176 TestCase(Date d, TimeZone z, FormatFactory f, boolean timeOnly)177 TestCase(Date d, TimeZone z, FormatFactory f, boolean timeOnly) { 178 date = null; 179 _date = d; 180 zone = z; 181 ff = f; 182 this.timeOnly = timeOnly; 183 } 184 185 /** 186 * Create a format for testing. 187 */ createFormat()188 DateFormat createFormat() { 189 return ff.createFormat(); 190 } 191 192 /** 193 * Return the Date of this test case; must be called with the default 194 * zone set to this TestCase's zone. 195 */ 196 @SuppressWarnings("deprecation") getDate()197 Date getDate() { 198 if (_date == null) { 199 // Date constructor will work right iff we are in the target zone 200 int h = 0; 201 int m = 0; 202 int s = 0; 203 if (date.length >= 4) { 204 h = date[3]; 205 if (date.length >= 5) { 206 m = date[4]; 207 if (date.length >= 6) { 208 s = date[5]; 209 } 210 } 211 } 212 _date = new Date(date[0] - 1900, date[1] - 1, date[2], 213 h, m, s); 214 } 215 return _date; 216 } 217 toString()218 public String toString() { 219 return String.valueOf(getDate().getTime()) + " " + 220 refFormat.format(getDate()) + " : " + ff.createFormat().format(getDate()); 221 } 222 }; 223 224 private interface FormatFactory { createFormat()225 DateFormat createFormat(); 226 } 227 228 TestCase[] TESTS = { 229 // Feb 29 2004 -- ordinary leap day 230 new TestCase(new int[] {2004, 2, 29}, null, 231 new FormatFactory() { public DateFormat createFormat() { 232 return DateFormat.getDateTimeInstance(DateFormat.LONG, 233 DateFormat.LONG); 234 }}, false), 235 236 // Feb 29 2000 -- century leap day 237 new TestCase(new int[] {2000, 2, 29}, null, 238 new FormatFactory() { public DateFormat createFormat() { 239 return DateFormat.getDateTimeInstance(DateFormat.LONG, 240 DateFormat.LONG); 241 }}, false), 242 243 // 0:00:00 Jan 1 1999 -- first second of normal year 244 new TestCase(new int[] {1999, 1, 1}, null, 245 new FormatFactory() { public DateFormat createFormat() { 246 return DateFormat.getDateTimeInstance(); 247 }}, false), 248 249 // 23:59:59 Dec 31 1999 -- last second of normal year 250 new TestCase(new int[] {1999, 12, 31, 23, 59, 59}, null, 251 new FormatFactory() { public DateFormat createFormat() { 252 return DateFormat.getDateTimeInstance(); 253 }}, false), 254 255 // 0:00:00 Jan 1 2004 -- first second of leap year 256 new TestCase(new int[] {2004, 1, 1}, null, 257 new FormatFactory() { public DateFormat createFormat() { 258 return DateFormat.getDateTimeInstance(); 259 }}, false), 260 261 // 23:59:59 Dec 31 2004 -- last second of leap year 262 new TestCase(new int[] {2004, 12, 31, 23, 59, 59}, null, 263 new FormatFactory() { public DateFormat createFormat() { 264 return DateFormat.getDateTimeInstance(); 265 }}, false), 266 267 // October 25, 1998 1:59:59 AM PDT -- just before DST cessation 268 new TestCase(new Date(909305999000L), TimeZone.getTimeZone("PST"), 269 new FormatFactory() { public DateFormat createFormat() { 270 return DateFormat.getDateTimeInstance(DateFormat.LONG, 271 DateFormat.LONG); 272 }}, false), 273 274 // October 25, 1998 1:00:00 AM PST -- just after DST cessation 275 new TestCase(new Date(909306000000L), TimeZone.getTimeZone("PST"), 276 new FormatFactory() { public DateFormat createFormat() { 277 return DateFormat.getDateTimeInstance(DateFormat.LONG, 278 DateFormat.LONG); 279 }}, false), 280 281 // April 4, 1999 1:59:59 AM PST -- just before DST onset 282 new TestCase(new int[] {1999, 4, 4, 1, 59, 59}, 283 TimeZone.getTimeZone("PST"), 284 new FormatFactory() { public DateFormat createFormat() { 285 return DateFormat.getDateTimeInstance(DateFormat.LONG, 286 DateFormat.LONG); 287 }}, false), 288 289 // April 4, 1999 3:00:00 AM PDT -- just after DST onset 290 new TestCase(new Date(923220000000L), TimeZone.getTimeZone("PST"), 291 new FormatFactory() { public DateFormat createFormat() { 292 return DateFormat.getDateTimeInstance(DateFormat.LONG, 293 DateFormat.LONG); 294 }}, false), 295 296 // October 4, 1582 11:59:59 PM PDT -- just before Gregorian change 297 new TestCase(new int[] {1582, 10, 4, 23, 59, 59}, null, 298 new FormatFactory() { public DateFormat createFormat() { 299 return DateFormat.getDateTimeInstance(DateFormat.LONG, 300 DateFormat.LONG); 301 }}, false), 302 303 // October 15, 1582 12:00:00 AM PDT -- just after Gregorian change 304 new TestCase(new int[] {1582, 10, 15, 0, 0, 0}, null, 305 new FormatFactory() { public DateFormat createFormat() { 306 return DateFormat.getDateTimeInstance(DateFormat.LONG, 307 DateFormat.LONG); 308 }}, false), 309 }; 310 TestDateFormatRoundTrip()311 public void TestDateFormatRoundTrip() { 312 avail = DateFormat.getAvailableLocales(); 313 logln("DateFormat available locales: " + avail.length); 314 logln("Default TimeZone: " + 315 (defaultZone = TimeZone.getDefault()).getID()); 316 317 if (random || initialDate != null) { 318 if (RANDOM == null) { 319 // Need this for sparse coverage to reduce combinatorial explosion, 320 // even for non-random looped testing (i.e., with explicit date but 321 // not pattern or locale). 322 RANDOM = new Random(FIXED_SEED); 323 } 324 loopedTest(); 325 } else { 326 for (int i=0; i<TESTS.length; ++i) { 327 doTest(TESTS[i]); 328 } 329 } 330 } 331 332 /** 333 * TimeZone must be set to tc.zone before this method is called. 334 */ doTestInZone(TestCase tc)335 private void doTestInZone(TestCase tc) { 336 logln(escape(tc.toString())); 337 Locale save = Locale.getDefault(); 338 try { 339 if (locale != null) { 340 Locale.setDefault(locale); 341 doTest(locale, tc.createFormat(), tc.timeOnly, tc.getDate()); 342 } else { 343 for (int i=0; i<avail.length; ++i) { 344 Locale.setDefault(avail[i]); 345 doTest(avail[i], tc.createFormat(), tc.timeOnly, tc.getDate()); 346 } 347 } 348 } finally { 349 Locale.setDefault(save); 350 } 351 } 352 doTest(TestCase tc)353 private void doTest(TestCase tc) { 354 if (tc.zone == null) { 355 // Just run in the default zone 356 doTestInZone(tc); 357 } else { 358 try { 359 TimeZone.setDefault(tc.zone); 360 doTestInZone(tc); 361 } finally { 362 TimeZone.setDefault(defaultZone); 363 } 364 } 365 } 366 loopedTest()367 private void loopedTest() { 368 if (INFINITE) { 369 // Special infinite loop test mode for finding hard to reproduce errors 370 if (locale != null) { 371 logln("ENTERING INFINITE TEST LOOP, LOCALE " + locale.getDisplayName()); 372 for (;;) doTest(locale); 373 } else { 374 logln("ENTERING INFINITE TEST LOOP, ALL LOCALES"); 375 for (;;) { 376 for (int i=0; i<avail.length; ++i) { 377 doTest(avail[i]); 378 } 379 } 380 } 381 } 382 else { 383 if (locale != null) { 384 doTest(locale); 385 } else { 386 doTest(Locale.getDefault()); 387 388 for (int i=0; i<avail.length; ++i) { 389 doTest(avail[i]); 390 } 391 } 392 } 393 } 394 doTest(Locale loc)395 void doTest(Locale loc) { 396 if (!INFINITE) logln("Locale: " + loc.getDisplayName()); 397 398 if (pattern != null) { 399 doTest(loc, new SimpleDateFormat(pattern, loc)); 400 return; 401 } 402 403 // Total possibilities = 24 404 // 4 date 405 // 4 time 406 // 16 date-time 407 boolean[] TEST_TABLE = new boolean[24]; 408 for (int i=0; i<24; ++i) TEST_TABLE[i] = true; 409 410 // If we have some sparseness, implement it here. Sparseness decreases 411 // test time by eliminating some tests, up to 23. 412 if (!INFINITE) { 413 for (int i=0; i<SPARSENESS; ) { 414 int random = (int)(java.lang.Math.random() * 24); 415 if (random >= 0 && random < 24 && TEST_TABLE[i]) { 416 TEST_TABLE[i] = false; 417 ++i; 418 } 419 } 420 } 421 422 int itable = 0; 423 for (int style=DateFormat.FULL; style<=DateFormat.SHORT; ++style) { 424 if (TEST_TABLE[itable++]) 425 doTest(loc, DateFormat.getDateInstance(style, loc)); 426 } 427 428 for (int style=DateFormat.FULL; style<=DateFormat.SHORT; ++style) { 429 if (TEST_TABLE[itable++]) 430 doTest(loc, DateFormat.getTimeInstance(style, loc), true); 431 } 432 433 for (int dstyle=DateFormat.FULL; dstyle<=DateFormat.SHORT; ++dstyle) { 434 for (int tstyle=DateFormat.FULL; tstyle<=DateFormat.SHORT; ++tstyle) { 435 if (TEST_TABLE[itable++]) 436 doTest(loc, DateFormat.getDateTimeInstance(dstyle, tstyle, loc)); 437 } 438 } 439 } 440 doTest(Locale loc, DateFormat fmt)441 void doTest(Locale loc, DateFormat fmt) { doTest(loc, fmt, false); } 442 doTest(Locale loc, DateFormat fmt, boolean timeOnly)443 void doTest(Locale loc, DateFormat fmt, boolean timeOnly) { 444 doTest(loc, fmt, timeOnly, initialDate != null ? initialDate : generateDate()); 445 } 446 doTest(Locale loc, DateFormat fmt, boolean timeOnly, Date date)447 void doTest(Locale loc, DateFormat fmt, boolean timeOnly, Date date) { 448 // Skip testing with the JapaneseImperialCalendar which 449 // doesn't support the Gregorian year semantices with 'y'. 450 if (fmt.getCalendar().getClass().getName().equals("java.util.JapaneseImperialCalendar")) { 451 return; 452 } 453 454 String pat = ((SimpleDateFormat)fmt).toPattern(); 455 String deqPat = dequotePattern(pat); // Remove quoted elements 456 457 boolean hasEra = (deqPat.indexOf("G") != -1); 458 boolean hasZone = (deqPat.indexOf("z") != -1); 459 460 Calendar cal = fmt.getCalendar(); 461 462 // Because patterns contain incomplete data representing the Date, 463 // we must be careful of how we do the roundtrip. We start with 464 // a randomly generated Date because they're easier to generate. 465 // From this we get a string. The string is our real starting point, 466 // because this string should parse the same way all the time. Note 467 // that it will not necessarily parse back to the original date because 468 // of incompleteness in patterns. For example, a time-only pattern won't 469 // parse back to the same date. 470 471 try { 472 for (int i=0; i<TRIALS; ++i) { 473 Date[] d = new Date[DEPTH]; 474 String[] s = new String[DEPTH]; 475 String error = null; 476 477 d[0] = date; 478 479 // We go through this loop until we achieve a match or until 480 // the maximum loop count is reached. We record the points at 481 // which the date and the string starts to match. Once matching 482 // starts, it should continue. 483 int loop; 484 int dmatch = 0; // d[dmatch].getTime() == d[dmatch-1].getTime() 485 int smatch = 0; // s[smatch].equals(s[smatch-1]) 486 for (loop=0; loop<DEPTH; ++loop) { 487 if (loop > 0) d[loop] = fmt.parse(s[loop-1]); 488 s[loop] = fmt.format(d[loop]); 489 490 if (loop > 0) { 491 if (smatch == 0) { 492 boolean match = s[loop].equals(s[loop-1]); 493 if (smatch == 0) { 494 if (match) smatch = loop; 495 } 496 else if (!match) { 497 // This should never happen; if it does, fail. 498 smatch = -1; 499 error = "FAIL: String mismatch after match"; 500 } 501 } 502 503 if (dmatch == 0) { 504 boolean match = d[loop].getTime() == d[loop-1].getTime(); 505 if (dmatch == 0) { 506 if (match) dmatch = loop; 507 } 508 else if (!match) { 509 // This should never happen; if it does, fail. 510 dmatch = -1; 511 error = "FAIL: Date mismatch after match"; 512 } 513 } 514 515 if (smatch != 0 && dmatch != 0) break; 516 } 517 } 518 // At this point loop == DEPTH if we've failed, otherwise loop is the 519 // max(smatch, dmatch), that is, the index at which we have string and 520 // date matching. 521 522 // Date usually matches in 2. Exceptions handled below. 523 int maxDmatch = 2; 524 int maxSmatch = 1; 525 if (dmatch > maxDmatch) { 526 // Time-only pattern with zone information and a starting date in PST. 527 if (timeOnly && hasZone && fmt.getTimeZone().inDaylightTime(d[0])) { 528 maxDmatch = 3; 529 maxSmatch = 2; 530 } 531 } 532 533 // String usually matches in 1. Exceptions are checked for here. 534 if (smatch > maxSmatch) { // Don't compute unless necessary 535 // Starts in BC, with no era in pattern 536 if (!hasEra && getField(cal, d[0], Calendar.ERA) == GregorianCalendar.BC) 537 maxSmatch = 2; 538 // Starts in DST, no year in pattern 539 else if (fmt.getTimeZone().inDaylightTime(d[0]) && 540 deqPat.indexOf("yyyy") == -1) 541 maxSmatch = 2; 542 // Two digit year with zone and year change and zone in pattern 543 else if (hasZone && 544 fmt.getTimeZone().inDaylightTime(d[0]) != 545 fmt.getTimeZone().inDaylightTime(d[dmatch]) && 546 getField(cal, d[0], Calendar.YEAR) != 547 getField(cal, d[dmatch], Calendar.YEAR) && 548 deqPat.indexOf("y") != -1 && 549 deqPat.indexOf("yyyy") == -1) 550 maxSmatch = 2; 551 // Two digit year, year change, DST changeover hour. Example: 552 // FAIL: Pattern: dd/MM/yy HH:mm:ss 553 // Date matched in 2, wanted 2 554 // String matched in 2, wanted 1 555 // Thu Apr 02 02:35:52.110 PST 1795 AD F> 02/04/95 02:35:52 556 // P> Sun Apr 02 01:35:52.000 PST 1995 AD F> 02/04/95 01:35:52 557 // P> Sun Apr 02 01:35:52.000 PST 1995 AD F> 02/04/95 01:35:52 d== s== 558 // The problem is that the initial time is not a DST onset day, but 559 // then the year changes, and the resultant parsed time IS a DST 560 // onset day. The hour "2:XX" makes no sense if 2:00 is the DST 561 // onset, so DateFormat interprets it as 1:XX (arbitrary -- could 562 // also be 3:XX, same problem). This results in an extra iteration 563 // for String match convergence. 564 else if (!justBeforeOnset(cal, d[0]) && justBeforeOnset(cal, d[dmatch]) && 565 getField(cal, d[0], Calendar.YEAR) != 566 getField(cal, d[dmatch], Calendar.YEAR) && 567 deqPat.indexOf("y") != -1 && 568 deqPat.indexOf("yyyy") == -1) 569 maxSmatch = 2; 570 // Another spurious failure: 571 // FAIL: Pattern: dd MMMM yyyy hh:mm:ss 572 // Date matched in 2, wanted 2 573 // String matched in 2, wanted 1 574 // Sun Apr 05 14:28:38.410 PDT 3998 AD F> 05 April 3998 02:28:38 575 // P> Sun Apr 05 01:28:38.000 PST 3998 AD F> 05 April 3998 01:28:38 576 // P> Sun Apr 05 01:28:38.000 PST 3998 AD F> 05 April 3998 01:28:38 d== s== 577 // The problem here is that with an 'hh' pattern, hour from 1-12, 578 // a lack of AM/PM -- that is, no 'a' in pattern, and an initial 579 // time in the onset hour + 12:00. 580 else if (deqPat.indexOf('h') >= 0 581 && deqPat.indexOf('a') < 0 582 && justBeforeOnset(cal, new Date(d[0].getTime() - 12*60*60*1000L)) 583 && justBeforeOnset(cal, d[1])) 584 maxSmatch = 2; 585 } 586 587 if (dmatch > maxDmatch || smatch > maxSmatch 588 || dmatch < 0 || smatch < 0) { 589 StringBuffer out = new StringBuffer(); 590 if (error != null) { 591 out.append(error + '\n'); 592 } 593 out.append("FAIL: Pattern: " + pat + ", Locale: " + loc + '\n'); 594 out.append(" Initial date (ms): " + d[0].getTime() + '\n'); 595 out.append(" Date matched in " + dmatch 596 + ", wanted " + maxDmatch + '\n'); 597 out.append(" String matched in " + smatch 598 + ", wanted " + maxSmatch); 599 600 for (int j=0; j<=loop && j<DEPTH; ++j) { 601 out.append("\n " + 602 (j>0?" P> ":" ") + refFormat.format(d[j]) + " F> " + 603 escape(s[j]) + 604 (j>0&&d[j].getTime()==d[j-1].getTime()?" d==":"") + 605 (j>0&&s[j].equals(s[j-1])?" s==":"")); 606 } 607 errln(escape(out.toString())); 608 } 609 } 610 } 611 catch (ParseException e) { 612 errln(e.toString()); 613 } 614 } 615 616 /** 617 * Return a field of the given date 618 */ getField(Calendar cal, Date d, int f)619 static int getField(Calendar cal, Date d, int f) { 620 // Should be synchronized, but we're single threaded so it's ok 621 cal.setTime(d); 622 return cal.get(f); 623 } 624 625 /** 626 * Return true if the given Date is in the 1 hour window BEFORE the 627 * change from STD to DST for the given Calendar. 628 */ justBeforeOnset(Calendar cal, Date d)629 static final boolean justBeforeOnset(Calendar cal, Date d) { 630 return nearOnset(cal, d, false); 631 } 632 633 /** 634 * Return true if the given Date is in the 1 hour window AFTER the 635 * change from STD to DST for the given Calendar. 636 */ justAfterOnset(Calendar cal, Date d)637 static final boolean justAfterOnset(Calendar cal, Date d) { 638 return nearOnset(cal, d, true); 639 } 640 641 /** 642 * Return true if the given Date is in the 1 hour (or whatever the 643 * DST savings is) window before or after the onset of DST. 644 */ nearOnset(Calendar cal, Date d, boolean after)645 static boolean nearOnset(Calendar cal, Date d, boolean after) { 646 cal.setTime(d); 647 if ((cal.get(Calendar.DST_OFFSET) == 0) == after) { 648 return false; 649 } 650 int delta; 651 try { 652 delta = ((SimpleTimeZone) cal.getTimeZone()).getDSTSavings(); 653 } catch (ClassCastException e) { 654 delta = 60*60*1000; // One hour as ms 655 } 656 cal.setTime(new Date(d.getTime() + (after ? -delta : delta))); 657 return (cal.get(Calendar.DST_OFFSET) == 0) == after; 658 } 659 escape(String s)660 static String escape(String s) { 661 StringBuffer buf = new StringBuffer(); 662 for (int i=0; i<s.length(); ++i) { 663 char c = s.charAt(i); 664 if (c < '\u0080') buf.append(c); 665 else { 666 buf.append("\\u"); 667 if (c < '\u1000') { 668 buf.append('0'); 669 if (c < '\u0100') { 670 buf.append('0'); 671 if (c < '\u0010') { 672 buf.append('0'); 673 } 674 } 675 } 676 buf.append(Integer.toHexString(c)); 677 } 678 } 679 return buf.toString(); 680 } 681 682 /** 683 * Remove quoted elements from a pattern. E.g., change "hh:mm 'o''clock'" 684 * to "hh:mm ?". All quoted elements are replaced by one or more '?' 685 * characters. 686 */ dequotePattern(String pat)687 static String dequotePattern(String pat) { 688 StringBuffer out = new StringBuffer(); 689 boolean inQuote = false; 690 for (int i=0; i<pat.length(); ++i) { 691 char ch = pat.charAt(i); 692 if (ch == '\'') { 693 if ((i+1)<pat.length() 694 && pat.charAt(i+1) == '\'') { 695 // Handle "''" 696 out.append('?'); 697 ++i; 698 } else { 699 inQuote = !inQuote; 700 if (inQuote) { 701 out.append('?'); 702 } 703 } 704 } else if (!inQuote) { 705 out.append(ch); 706 } 707 } 708 return out.toString(); 709 } 710 generateDate()711 static Date generateDate() { 712 double a = (RANDOM.nextLong() & 0x7FFFFFFFFFFFFFFFL ) / 713 ((double)0x7FFFFFFFFFFFFFFFL); 714 715 // Now 'a' ranges from 0..1; scale it to range from 0 to 8000 years 716 a *= 8000; 717 718 // Range from (4000-1970) BC to (8000-1970) AD 719 a -= 4000; 720 721 // Now scale up to ms 722 a *= 365.25 * 24 * 60 * 60 * 1000; 723 724 return new Date((long)a); 725 } 726 } 727 728 //eof 729