| /* |
| * Copyright (c) 1997, 2016, Oracle and/or its affiliates. All rights reserved. |
| * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. |
| * |
| * This code is free software; you can redistribute it and/or modify it |
| * under the terms of the GNU General Public License version 2 only, as |
| * published by the Free Software Foundation. |
| * |
| * This code is distributed in the hope that it will be useful, but WITHOUT |
| * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or |
| * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License |
| * version 2 for more details (a copy is included in the LICENSE file that |
| * accompanied this code). |
| * |
| * You should have received a copy of the GNU General Public License version |
| * 2 along with this work; if not, write to the Free Software Foundation, |
| * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. |
| * |
| * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA |
| * or visit www.oracle.com if you need additional information or have any |
| * questions. |
| */ |
| |
| /* |
| * @test |
| * @summary test Date Format (Round Trip) |
| * @bug 8008577 |
| * @library /java/text/testlib |
| * @run main/othervm -Djava.locale.providers=COMPAT,SPI DateFormatRoundTripTest |
| */ |
| |
| import java.text.*; |
| import java.util.*; |
| |
| public class DateFormatRoundTripTest extends IntlTest { |
| |
| static Random RANDOM = null; |
| |
| static final long FIXED_SEED = 3141592653589793238L; // Arbitrary fixed value |
| |
| // Useful for turning up subtle bugs: Use -infinite and run while at lunch. |
| boolean INFINITE = false; // Warning -- makes test run infinite loop!!! |
| |
| boolean random = false; |
| |
| // Options used to reproduce failures |
| Locale locale = null; |
| String pattern = null; |
| Date initialDate = null; |
| |
| Locale[] avail; |
| TimeZone defaultZone; |
| |
| // If SPARSENESS is > 0, we don't run each exhaustive possibility. |
| // There are 24 total possible tests per each locale. A SPARSENESS |
| // of 12 means we run half of them. A SPARSENESS of 23 means we run |
| // 1 of them. SPARSENESS _must_ be in the range 0..23. |
| static final int SPARSENESS = 18; |
| |
| static final int TRIALS = 4; |
| |
| static final int DEPTH = 5; |
| |
| static SimpleDateFormat refFormat = |
| new SimpleDateFormat("EEE MMM dd HH:mm:ss.SSS zzz yyyy G"); |
| |
| public DateFormatRoundTripTest(boolean rand, long seed, boolean infinite, |
| Date date, String pat, Locale loc) { |
| random = rand; |
| if (random) { |
| RANDOM = new Random(seed); |
| } |
| INFINITE = infinite; |
| |
| initialDate = date; |
| locale = loc; |
| pattern = pat; |
| } |
| |
| /** |
| * Parse a name like "fr_FR" into new Locale("fr", "FR", ""); |
| */ |
| static Locale createLocale(String name) { |
| String country = "", |
| variant = ""; |
| int i; |
| if ((i = name.indexOf('_')) >= 0) { |
| country = name.substring(i+1); |
| name = name.substring(0, i); |
| } |
| if ((i = country.indexOf('_')) >= 0) { |
| variant = country.substring(i+1); |
| country = country.substring(0, i); |
| } |
| return new Locale(name, country, variant); |
| } |
| |
| public static void main(String[] args) throws Exception { |
| // Command-line parameters |
| Locale loc = null; |
| boolean infinite = false; |
| boolean random = false; |
| long seed = FIXED_SEED; |
| String pat = null; |
| Date date = null; |
| |
| List<String> newArgs = new ArrayList<>(); |
| for (int i=0; i<args.length; ++i) { |
| if (args[i].equals("-locale") |
| && (i+1) < args.length) { |
| loc = createLocale(args[i+1]); |
| ++i; |
| } else if (args[i].equals("-date") |
| && (i+1) < args.length) { |
| date = new Date(Long.parseLong(args[i+1])); |
| ++i; |
| } else if (args[i].equals("-pattern") |
| && (i+1) < args.length) { |
| pat = args[i+1]; |
| ++i; |
| } else if (args[i].equals("-INFINITE")) { |
| infinite = true; |
| } else if (args[i].equals("-random")) { |
| random = true; |
| } else if (args[i].equals("-randomseed")) { |
| random = true; |
| seed = System.currentTimeMillis(); |
| } else if (args[i].equals("-seed") |
| && (i+1) < args.length) { |
| random = true; |
| seed = Long.parseLong(args[i+1]); |
| ++i; |
| } else { |
| newArgs.add(args[i]); |
| } |
| } |
| |
| if (newArgs.size() != args.length) { |
| args = new String[newArgs.size()]; |
| newArgs.addAll(Arrays.asList(args)); |
| } |
| |
| new DateFormatRoundTripTest(random, seed, infinite, date, pat, loc).run(args); |
| } |
| |
| /** |
| * Print a usage message for this test class. |
| */ |
| void usage() { |
| System.out.println(getClass().getName() + |
| ": [-pattern <pattern>] [-locale <locale>] [-date <ms>] [-INFINITE]"); |
| System.out.println(" [-random | -randomseed | -seed <seed>]"); |
| System.out.println("* Warning: Some patterns will fail with some locales."); |
| System.out.println("* Do not use -pattern unless you know what you are doing!"); |
| System.out.println("When specifying a locale, use a format such as fr_FR."); |
| System.out.println("Use -pattern, -locale, and -date to reproduce a failure."); |
| System.out.println("-random Random with fixed seed (same data every run)."); |
| System.out.println("-randomseed Random with a random seed."); |
| System.out.println("-seed <s> Random using <s> as seed."); |
| super.usage(); |
| } |
| |
| static private class TestCase { |
| private int[] date; |
| TimeZone zone; |
| FormatFactory ff; |
| boolean timeOnly; |
| private Date _date; |
| |
| TestCase(int[] d, TimeZone z, FormatFactory f, boolean timeOnly) { |
| date = d; |
| zone = z; |
| ff = f; |
| this.timeOnly = timeOnly; |
| } |
| |
| TestCase(Date d, TimeZone z, FormatFactory f, boolean timeOnly) { |
| date = null; |
| _date = d; |
| zone = z; |
| ff = f; |
| this.timeOnly = timeOnly; |
| } |
| |
| /** |
| * Create a format for testing. |
| */ |
| DateFormat createFormat() { |
| return ff.createFormat(); |
| } |
| |
| /** |
| * Return the Date of this test case; must be called with the default |
| * zone set to this TestCase's zone. |
| */ |
| @SuppressWarnings("deprecation") |
| Date getDate() { |
| if (_date == null) { |
| // Date constructor will work right iff we are in the target zone |
| int h = 0; |
| int m = 0; |
| int s = 0; |
| if (date.length >= 4) { |
| h = date[3]; |
| if (date.length >= 5) { |
| m = date[4]; |
| if (date.length >= 6) { |
| s = date[5]; |
| } |
| } |
| } |
| _date = new Date(date[0] - 1900, date[1] - 1, date[2], |
| h, m, s); |
| } |
| return _date; |
| } |
| |
| public String toString() { |
| return String.valueOf(getDate().getTime()) + " " + |
| refFormat.format(getDate()) + " : " + ff.createFormat().format(getDate()); |
| } |
| }; |
| |
| private interface FormatFactory { |
| DateFormat createFormat(); |
| } |
| |
| TestCase[] TESTS = { |
| // Feb 29 2004 -- ordinary leap day |
| new TestCase(new int[] {2004, 2, 29}, null, |
| new FormatFactory() { public DateFormat createFormat() { |
| return DateFormat.getDateTimeInstance(DateFormat.LONG, |
| DateFormat.LONG); |
| }}, false), |
| |
| // Feb 29 2000 -- century leap day |
| new TestCase(new int[] {2000, 2, 29}, null, |
| new FormatFactory() { public DateFormat createFormat() { |
| return DateFormat.getDateTimeInstance(DateFormat.LONG, |
| DateFormat.LONG); |
| }}, false), |
| |
| // 0:00:00 Jan 1 1999 -- first second of normal year |
| new TestCase(new int[] {1999, 1, 1}, null, |
| new FormatFactory() { public DateFormat createFormat() { |
| return DateFormat.getDateTimeInstance(); |
| }}, false), |
| |
| // 23:59:59 Dec 31 1999 -- last second of normal year |
| new TestCase(new int[] {1999, 12, 31, 23, 59, 59}, null, |
| new FormatFactory() { public DateFormat createFormat() { |
| return DateFormat.getDateTimeInstance(); |
| }}, false), |
| |
| // 0:00:00 Jan 1 2004 -- first second of leap year |
| new TestCase(new int[] {2004, 1, 1}, null, |
| new FormatFactory() { public DateFormat createFormat() { |
| return DateFormat.getDateTimeInstance(); |
| }}, false), |
| |
| // 23:59:59 Dec 31 2004 -- last second of leap year |
| new TestCase(new int[] {2004, 12, 31, 23, 59, 59}, null, |
| new FormatFactory() { public DateFormat createFormat() { |
| return DateFormat.getDateTimeInstance(); |
| }}, false), |
| |
| // October 25, 1998 1:59:59 AM PDT -- just before DST cessation |
| new TestCase(new Date(909305999000L), TimeZone.getTimeZone("PST"), |
| new FormatFactory() { public DateFormat createFormat() { |
| return DateFormat.getDateTimeInstance(DateFormat.LONG, |
| DateFormat.LONG); |
| }}, false), |
| |
| // October 25, 1998 1:00:00 AM PST -- just after DST cessation |
| new TestCase(new Date(909306000000L), TimeZone.getTimeZone("PST"), |
| new FormatFactory() { public DateFormat createFormat() { |
| return DateFormat.getDateTimeInstance(DateFormat.LONG, |
| DateFormat.LONG); |
| }}, false), |
| |
| // April 4, 1999 1:59:59 AM PST -- just before DST onset |
| new TestCase(new int[] {1999, 4, 4, 1, 59, 59}, |
| TimeZone.getTimeZone("PST"), |
| new FormatFactory() { public DateFormat createFormat() { |
| return DateFormat.getDateTimeInstance(DateFormat.LONG, |
| DateFormat.LONG); |
| }}, false), |
| |
| // April 4, 1999 3:00:00 AM PDT -- just after DST onset |
| new TestCase(new Date(923220000000L), TimeZone.getTimeZone("PST"), |
| new FormatFactory() { public DateFormat createFormat() { |
| return DateFormat.getDateTimeInstance(DateFormat.LONG, |
| DateFormat.LONG); |
| }}, false), |
| |
| // October 4, 1582 11:59:59 PM PDT -- just before Gregorian change |
| new TestCase(new int[] {1582, 10, 4, 23, 59, 59}, null, |
| new FormatFactory() { public DateFormat createFormat() { |
| return DateFormat.getDateTimeInstance(DateFormat.LONG, |
| DateFormat.LONG); |
| }}, false), |
| |
| // October 15, 1582 12:00:00 AM PDT -- just after Gregorian change |
| new TestCase(new int[] {1582, 10, 15, 0, 0, 0}, null, |
| new FormatFactory() { public DateFormat createFormat() { |
| return DateFormat.getDateTimeInstance(DateFormat.LONG, |
| DateFormat.LONG); |
| }}, false), |
| }; |
| |
| public void TestDateFormatRoundTrip() { |
| avail = DateFormat.getAvailableLocales(); |
| logln("DateFormat available locales: " + avail.length); |
| logln("Default TimeZone: " + |
| (defaultZone = TimeZone.getDefault()).getID()); |
| |
| if (random || initialDate != null) { |
| if (RANDOM == null) { |
| // Need this for sparse coverage to reduce combinatorial explosion, |
| // even for non-random looped testing (i.e., with explicit date but |
| // not pattern or locale). |
| RANDOM = new Random(FIXED_SEED); |
| } |
| loopedTest(); |
| } else { |
| for (int i=0; i<TESTS.length; ++i) { |
| doTest(TESTS[i]); |
| } |
| } |
| } |
| |
| /** |
| * TimeZone must be set to tc.zone before this method is called. |
| */ |
| private void doTestInZone(TestCase tc) { |
| logln(escape(tc.toString())); |
| Locale save = Locale.getDefault(); |
| try { |
| if (locale != null) { |
| Locale.setDefault(locale); |
| doTest(locale, tc.createFormat(), tc.timeOnly, tc.getDate()); |
| } else { |
| for (int i=0; i<avail.length; ++i) { |
| Locale.setDefault(avail[i]); |
| doTest(avail[i], tc.createFormat(), tc.timeOnly, tc.getDate()); |
| } |
| } |
| } finally { |
| Locale.setDefault(save); |
| } |
| } |
| |
| private void doTest(TestCase tc) { |
| if (tc.zone == null) { |
| // Just run in the default zone |
| doTestInZone(tc); |
| } else { |
| try { |
| TimeZone.setDefault(tc.zone); |
| doTestInZone(tc); |
| } finally { |
| TimeZone.setDefault(defaultZone); |
| } |
| } |
| } |
| |
| private void loopedTest() { |
| if (INFINITE) { |
| // Special infinite loop test mode for finding hard to reproduce errors |
| if (locale != null) { |
| logln("ENTERING INFINITE TEST LOOP, LOCALE " + locale.getDisplayName()); |
| for (;;) doTest(locale); |
| } else { |
| logln("ENTERING INFINITE TEST LOOP, ALL LOCALES"); |
| for (;;) { |
| for (int i=0; i<avail.length; ++i) { |
| doTest(avail[i]); |
| } |
| } |
| } |
| } |
| else { |
| if (locale != null) { |
| doTest(locale); |
| } else { |
| doTest(Locale.getDefault()); |
| |
| for (int i=0; i<avail.length; ++i) { |
| doTest(avail[i]); |
| } |
| } |
| } |
| } |
| |
| void doTest(Locale loc) { |
| if (!INFINITE) logln("Locale: " + loc.getDisplayName()); |
| |
| if (pattern != null) { |
| doTest(loc, new SimpleDateFormat(pattern, loc)); |
| return; |
| } |
| |
| // Total possibilities = 24 |
| // 4 date |
| // 4 time |
| // 16 date-time |
| boolean[] TEST_TABLE = new boolean[24]; |
| for (int i=0; i<24; ++i) TEST_TABLE[i] = true; |
| |
| // If we have some sparseness, implement it here. Sparseness decreases |
| // test time by eliminating some tests, up to 23. |
| if (!INFINITE) { |
| for (int i=0; i<SPARSENESS; ) { |
| int random = (int)(java.lang.Math.random() * 24); |
| if (random >= 0 && random < 24 && TEST_TABLE[i]) { |
| TEST_TABLE[i] = false; |
| ++i; |
| } |
| } |
| } |
| |
| int itable = 0; |
| for (int style=DateFormat.FULL; style<=DateFormat.SHORT; ++style) { |
| if (TEST_TABLE[itable++]) |
| doTest(loc, DateFormat.getDateInstance(style, loc)); |
| } |
| |
| for (int style=DateFormat.FULL; style<=DateFormat.SHORT; ++style) { |
| if (TEST_TABLE[itable++]) |
| doTest(loc, DateFormat.getTimeInstance(style, loc), true); |
| } |
| |
| for (int dstyle=DateFormat.FULL; dstyle<=DateFormat.SHORT; ++dstyle) { |
| for (int tstyle=DateFormat.FULL; tstyle<=DateFormat.SHORT; ++tstyle) { |
| if (TEST_TABLE[itable++]) |
| doTest(loc, DateFormat.getDateTimeInstance(dstyle, tstyle, loc)); |
| } |
| } |
| } |
| |
| void doTest(Locale loc, DateFormat fmt) { doTest(loc, fmt, false); } |
| |
| void doTest(Locale loc, DateFormat fmt, boolean timeOnly) { |
| doTest(loc, fmt, timeOnly, initialDate != null ? initialDate : generateDate()); |
| } |
| |
| void doTest(Locale loc, DateFormat fmt, boolean timeOnly, Date date) { |
| // Skip testing with the JapaneseImperialCalendar which |
| // doesn't support the Gregorian year semantices with 'y'. |
| if (fmt.getCalendar().getClass().getName().equals("java.util.JapaneseImperialCalendar")) { |
| return; |
| } |
| |
| String pat = ((SimpleDateFormat)fmt).toPattern(); |
| String deqPat = dequotePattern(pat); // Remove quoted elements |
| |
| boolean hasEra = (deqPat.indexOf("G") != -1); |
| boolean hasZone = (deqPat.indexOf("z") != -1); |
| |
| Calendar cal = fmt.getCalendar(); |
| |
| // Because patterns contain incomplete data representing the Date, |
| // we must be careful of how we do the roundtrip. We start with |
| // a randomly generated Date because they're easier to generate. |
| // From this we get a string. The string is our real starting point, |
| // because this string should parse the same way all the time. Note |
| // that it will not necessarily parse back to the original date because |
| // of incompleteness in patterns. For example, a time-only pattern won't |
| // parse back to the same date. |
| |
| try { |
| for (int i=0; i<TRIALS; ++i) { |
| Date[] d = new Date[DEPTH]; |
| String[] s = new String[DEPTH]; |
| String error = null; |
| |
| d[0] = date; |
| |
| // We go through this loop until we achieve a match or until |
| // the maximum loop count is reached. We record the points at |
| // which the date and the string starts to match. Once matching |
| // starts, it should continue. |
| int loop; |
| int dmatch = 0; // d[dmatch].getTime() == d[dmatch-1].getTime() |
| int smatch = 0; // s[smatch].equals(s[smatch-1]) |
| for (loop=0; loop<DEPTH; ++loop) { |
| if (loop > 0) d[loop] = fmt.parse(s[loop-1]); |
| s[loop] = fmt.format(d[loop]); |
| |
| if (loop > 0) { |
| if (smatch == 0) { |
| boolean match = s[loop].equals(s[loop-1]); |
| if (smatch == 0) { |
| if (match) smatch = loop; |
| } |
| else if (!match) { |
| // This should never happen; if it does, fail. |
| smatch = -1; |
| error = "FAIL: String mismatch after match"; |
| } |
| } |
| |
| if (dmatch == 0) { |
| boolean match = d[loop].getTime() == d[loop-1].getTime(); |
| if (dmatch == 0) { |
| if (match) dmatch = loop; |
| } |
| else if (!match) { |
| // This should never happen; if it does, fail. |
| dmatch = -1; |
| error = "FAIL: Date mismatch after match"; |
| } |
| } |
| |
| if (smatch != 0 && dmatch != 0) break; |
| } |
| } |
| // At this point loop == DEPTH if we've failed, otherwise loop is the |
| // max(smatch, dmatch), that is, the index at which we have string and |
| // date matching. |
| |
| // Date usually matches in 2. Exceptions handled below. |
| int maxDmatch = 2; |
| int maxSmatch = 1; |
| if (dmatch > maxDmatch) { |
| // Time-only pattern with zone information and a starting date in PST. |
| if (timeOnly && hasZone && fmt.getTimeZone().inDaylightTime(d[0])) { |
| maxDmatch = 3; |
| maxSmatch = 2; |
| } |
| } |
| |
| // String usually matches in 1. Exceptions are checked for here. |
| if (smatch > maxSmatch) { // Don't compute unless necessary |
| // Starts in BC, with no era in pattern |
| if (!hasEra && getField(cal, d[0], Calendar.ERA) == GregorianCalendar.BC) |
| maxSmatch = 2; |
| // Starts in DST, no year in pattern |
| else if (fmt.getTimeZone().inDaylightTime(d[0]) && |
| deqPat.indexOf("yyyy") == -1) |
| maxSmatch = 2; |
| // Two digit year with zone and year change and zone in pattern |
| else if (hasZone && |
| fmt.getTimeZone().inDaylightTime(d[0]) != |
| fmt.getTimeZone().inDaylightTime(d[dmatch]) && |
| getField(cal, d[0], Calendar.YEAR) != |
| getField(cal, d[dmatch], Calendar.YEAR) && |
| deqPat.indexOf("y") != -1 && |
| deqPat.indexOf("yyyy") == -1) |
| maxSmatch = 2; |
| // Two digit year, year change, DST changeover hour. Example: |
| // FAIL: Pattern: dd/MM/yy HH:mm:ss |
| // Date matched in 2, wanted 2 |
| // String matched in 2, wanted 1 |
| // Thu Apr 02 02:35:52.110 PST 1795 AD F> 02/04/95 02:35:52 |
| // P> Sun Apr 02 01:35:52.000 PST 1995 AD F> 02/04/95 01:35:52 |
| // P> Sun Apr 02 01:35:52.000 PST 1995 AD F> 02/04/95 01:35:52 d== s== |
| // The problem is that the initial time is not a DST onset day, but |
| // then the year changes, and the resultant parsed time IS a DST |
| // onset day. The hour "2:XX" makes no sense if 2:00 is the DST |
| // onset, so DateFormat interprets it as 1:XX (arbitrary -- could |
| // also be 3:XX, same problem). This results in an extra iteration |
| // for String match convergence. |
| else if (!justBeforeOnset(cal, d[0]) && justBeforeOnset(cal, d[dmatch]) && |
| getField(cal, d[0], Calendar.YEAR) != |
| getField(cal, d[dmatch], Calendar.YEAR) && |
| deqPat.indexOf("y") != -1 && |
| deqPat.indexOf("yyyy") == -1) |
| maxSmatch = 2; |
| // Another spurious failure: |
| // FAIL: Pattern: dd MMMM yyyy hh:mm:ss |
| // Date matched in 2, wanted 2 |
| // String matched in 2, wanted 1 |
| // Sun Apr 05 14:28:38.410 PDT 3998 AD F> 05 April 3998 02:28:38 |
| // P> Sun Apr 05 01:28:38.000 PST 3998 AD F> 05 April 3998 01:28:38 |
| // P> Sun Apr 05 01:28:38.000 PST 3998 AD F> 05 April 3998 01:28:38 d== s== |
| // The problem here is that with an 'hh' pattern, hour from 1-12, |
| // a lack of AM/PM -- that is, no 'a' in pattern, and an initial |
| // time in the onset hour + 12:00. |
| else if (deqPat.indexOf('h') >= 0 |
| && deqPat.indexOf('a') < 0 |
| && justBeforeOnset(cal, new Date(d[0].getTime() - 12*60*60*1000L)) |
| && justBeforeOnset(cal, d[1])) |
| maxSmatch = 2; |
| } |
| |
| if (dmatch > maxDmatch || smatch > maxSmatch |
| || dmatch < 0 || smatch < 0) { |
| StringBuffer out = new StringBuffer(); |
| if (error != null) { |
| out.append(error + '\n'); |
| } |
| out.append("FAIL: Pattern: " + pat + ", Locale: " + loc + '\n'); |
| out.append(" Initial date (ms): " + d[0].getTime() + '\n'); |
| out.append(" Date matched in " + dmatch |
| + ", wanted " + maxDmatch + '\n'); |
| out.append(" String matched in " + smatch |
| + ", wanted " + maxSmatch); |
| |
| for (int j=0; j<=loop && j<DEPTH; ++j) { |
| out.append("\n " + |
| (j>0?" P> ":" ") + refFormat.format(d[j]) + " F> " + |
| escape(s[j]) + |
| (j>0&&d[j].getTime()==d[j-1].getTime()?" d==":"") + |
| (j>0&&s[j].equals(s[j-1])?" s==":"")); |
| } |
| errln(escape(out.toString())); |
| } |
| } |
| } |
| catch (ParseException e) { |
| errln(e.toString()); |
| } |
| } |
| |
| /** |
| * Return a field of the given date |
| */ |
| static int getField(Calendar cal, Date d, int f) { |
| // Should be synchronized, but we're single threaded so it's ok |
| cal.setTime(d); |
| return cal.get(f); |
| } |
| |
| /** |
| * Return true if the given Date is in the 1 hour window BEFORE the |
| * change from STD to DST for the given Calendar. |
| */ |
| static final boolean justBeforeOnset(Calendar cal, Date d) { |
| return nearOnset(cal, d, false); |
| } |
| |
| /** |
| * Return true if the given Date is in the 1 hour window AFTER the |
| * change from STD to DST for the given Calendar. |
| */ |
| static final boolean justAfterOnset(Calendar cal, Date d) { |
| return nearOnset(cal, d, true); |
| } |
| |
| /** |
| * Return true if the given Date is in the 1 hour (or whatever the |
| * DST savings is) window before or after the onset of DST. |
| */ |
| static boolean nearOnset(Calendar cal, Date d, boolean after) { |
| cal.setTime(d); |
| if ((cal.get(Calendar.DST_OFFSET) == 0) == after) { |
| return false; |
| } |
| int delta; |
| try { |
| delta = ((SimpleTimeZone) cal.getTimeZone()).getDSTSavings(); |
| } catch (ClassCastException e) { |
| delta = 60*60*1000; // One hour as ms |
| } |
| cal.setTime(new Date(d.getTime() + (after ? -delta : delta))); |
| return (cal.get(Calendar.DST_OFFSET) == 0) == after; |
| } |
| |
| static String escape(String s) { |
| StringBuffer buf = new StringBuffer(); |
| for (int i=0; i<s.length(); ++i) { |
| char c = s.charAt(i); |
| if (c < '\u0080') buf.append(c); |
| else { |
| buf.append("\\u"); |
| if (c < '\u1000') { |
| buf.append('0'); |
| if (c < '\u0100') { |
| buf.append('0'); |
| if (c < '\u0010') { |
| buf.append('0'); |
| } |
| } |
| } |
| buf.append(Integer.toHexString(c)); |
| } |
| } |
| return buf.toString(); |
| } |
| |
| /** |
| * Remove quoted elements from a pattern. E.g., change "hh:mm 'o''clock'" |
| * to "hh:mm ?". All quoted elements are replaced by one or more '?' |
| * characters. |
| */ |
| static String dequotePattern(String pat) { |
| StringBuffer out = new StringBuffer(); |
| boolean inQuote = false; |
| for (int i=0; i<pat.length(); ++i) { |
| char ch = pat.charAt(i); |
| if (ch == '\'') { |
| if ((i+1)<pat.length() |
| && pat.charAt(i+1) == '\'') { |
| // Handle "''" |
| out.append('?'); |
| ++i; |
| } else { |
| inQuote = !inQuote; |
| if (inQuote) { |
| out.append('?'); |
| } |
| } |
| } else if (!inQuote) { |
| out.append(ch); |
| } |
| } |
| return out.toString(); |
| } |
| |
| static Date generateDate() { |
| double a = (RANDOM.nextLong() & 0x7FFFFFFFFFFFFFFFL ) / |
| ((double)0x7FFFFFFFFFFFFFFFL); |
| |
| // Now 'a' ranges from 0..1; scale it to range from 0 to 8000 years |
| a *= 8000; |
| |
| // Range from (4000-1970) BC to (8000-1970) AD |
| a -= 4000; |
| |
| // Now scale up to ms |
| a *= 365.25 * 24 * 60 * 60 * 1000; |
| |
| return new Date((long)a); |
| } |
| } |
| |
| //eof |