2 ********************************************************************************
3 * Copyright (C) 2007-2013, Google, International Business Machines Corporation *
4 * and others. All Rights Reserved. *
5 ********************************************************************************
8 package com.ibm.icu.dev.test.format;
10 import java.text.ParseException;
11 import java.text.ParsePosition;
12 import java.util.Arrays;
13 import java.util.Date;
14 import java.util.EnumSet;
15 import java.util.List;
17 import java.util.TreeSet;
18 import java.util.regex.Pattern;
20 import com.ibm.icu.impl.ZoneMeta;
21 import com.ibm.icu.lang.UCharacter;
22 import com.ibm.icu.text.SimpleDateFormat;
23 import com.ibm.icu.text.TimeZoneFormat;
24 import com.ibm.icu.text.TimeZoneFormat.ParseOption;
25 import com.ibm.icu.text.TimeZoneFormat.Style;
26 import com.ibm.icu.text.TimeZoneFormat.TimeType;
27 import com.ibm.icu.util.BasicTimeZone;
28 import com.ibm.icu.util.Calendar;
29 import com.ibm.icu.util.Output;
30 import com.ibm.icu.util.SimpleTimeZone;
31 import com.ibm.icu.util.TimeZone;
32 import com.ibm.icu.util.TimeZone.SystemTimeZoneType;
33 import com.ibm.icu.util.TimeZoneTransition;
34 import com.ibm.icu.util.ULocale;
36 public class TimeZoneFormatTest extends com.ibm.icu.dev.test.TestFmwk {
38 private static boolean JDKTZ = (TimeZone.getDefaultTimeZoneType() == TimeZone.TIMEZONE_JDK);
40 public static void main(String[] args) throws Exception {
41 new TimeZoneFormatTest().run(args);
44 private static final String[] PATTERNS = {
47 "Z", // equivalent to "xxxx"
48 "ZZZZ", // equivalent to "OOOO"
68 boolean REALLY_VERBOSE_LOG = false;
71 * Test case for checking if a TimeZone is properly set in the result calendar
72 * and if the result TimeZone has the expected behavior.
74 public void TestTimeZoneRoundTrip() {
75 boolean TEST_ALL = getBooleanProperty("TimeZoneRoundTripAll", false);
77 TimeZone unknownZone = new SimpleTimeZone(-31415, "Etc/Unknown");
78 int badDstOffset = -1234;
79 int badZoneOffset = -2345;
81 int[][] testDateData = {
90 Calendar cal = Calendar.getInstance(TimeZone.getTimeZone("UTC"));
93 // Set up rule equivalency test range
96 low = cal.getTimeInMillis();
98 high = cal.getTimeInMillis();
101 Date[] DATES = new Date[testDateData.length];
103 for (int i = 0; i < DATES.length; i++) {
104 cal.set(testDateData[i][0], testDateData[i][1], testDateData[i][2]);
105 DATES[i] = cal.getTime();
108 // Set up test locales
109 ULocale[] LOCALES = null;
110 if (TEST_ALL || getInclusion() > 5) {
111 LOCALES = ULocale.getAvailableLocales();
113 LOCALES = new ULocale[] {new ULocale("en"), new ULocale("en_CA"), new ULocale("fr"), new ULocale("zh_Hant")};
118 tzids = java.util.TimeZone.getAvailableIDs();
120 tzids = TimeZone.getAvailableIDs();
122 int[] inOffsets = new int[2];
123 int[] outOffsets = new int[2];
125 // Run the roundtrip test
126 for (int locidx = 0; locidx < LOCALES.length; locidx++) {
127 logln("Locale: " + LOCALES[locidx].toString());
129 String localGMTString = TimeZoneFormat.getInstance(LOCALES[locidx]).formatOffsetLocalizedGMT(0);
131 for (int patidx = 0; patidx < PATTERNS.length; patidx++) {
132 logln(" pattern: " + PATTERNS[patidx]);
133 SimpleDateFormat sdf = new SimpleDateFormat(PATTERNS[patidx], LOCALES[locidx]);
135 for (int tzidx = 0; tzidx < tzids.length; tzidx++) {
136 TimeZone tz = TimeZone.getTimeZone(tzids[tzidx]);
138 for (int datidx = 0; datidx < DATES.length; datidx++) {
141 String tzstr = sdf.format(DATES[datidx]);
143 // Before parse, set unknown zone to SimpleDateFormat instance
144 // just for making sure that it does not depends on the time zone
146 sdf.setTimeZone(unknownZone);
149 ParsePosition pos = new ParsePosition(0);
150 Calendar outcal = Calendar.getInstance(unknownZone);
151 outcal.set(Calendar.DST_OFFSET, badDstOffset);
152 outcal.set(Calendar.ZONE_OFFSET, badZoneOffset);
154 sdf.parse(tzstr, outcal, pos);
157 TimeZone outtz = outcal.getTimeZone();
159 tz.getOffset(DATES[datidx].getTime(), false, inOffsets);
160 outtz.getOffset(DATES[datidx].getTime(), false, outOffsets);
162 if (PATTERNS[patidx].equals("V")) {
163 // Short zone ID - should support roundtrip for canonical CLDR IDs
164 String canonicalID = TimeZone.getCanonicalID(tzids[tzidx]);
165 if (!outtz.getID().equals(canonicalID)) {
166 if (outtz.getID().equals("Etc/Unknown")) {
167 // Note that some zones like Asia/Riyadh87 does not have
168 // short zone ID and "unk" is used as the fallback
169 if (REALLY_VERBOSE_LOG) {
170 logln("Canonical round trip failed (probably as expected); tz=" + tzids[tzidx]
171 + ", locale=" + LOCALES[locidx] + ", pattern=" + PATTERNS[patidx]
172 + ", time=" + DATES[datidx].getTime() + ", str=" + tzstr
173 + ", outtz=" + outtz.getID());
176 errln("Canonical round trip failed; tz=" + tzids[tzidx]
177 + ", locale=" + LOCALES[locidx] + ", pattern=" + PATTERNS[patidx]
178 + ", time=" + DATES[datidx].getTime() + ", str=" + tzstr
179 + ", outtz=" + outtz.getID());
182 } else if (PATTERNS[patidx].equals("VV")) {
183 // Zone ID - full roundtrip support
184 if (!outtz.getID().equals(tzids[tzidx])) {
185 errln("Zone ID round trip failed; tz=" + tzids[tzidx]
186 + ", locale=" + LOCALES[locidx] + ", pattern=" + PATTERNS[patidx]
187 + ", time=" + DATES[datidx].getTime() + ", str=" + tzstr
188 + ", outtz=" + outtz.getID());
190 } else if (PATTERNS[patidx].equals("VVV") || PATTERNS[patidx].equals("VVVV")) {
191 // Location: time zone rule must be preserved except
192 // zones not actually associated with a specific location.
193 String canonicalID = TimeZone.getCanonicalID(tzids[tzidx]);
194 if (canonicalID != null && !outtz.getID().equals(canonicalID)) {
195 // Canonical ID did not match - check the rules
196 boolean bFailure = false;
197 if ((tz instanceof BasicTimeZone) && (outtz instanceof BasicTimeZone)) {
198 boolean hasNoLocation = TimeZone.getRegion(tzids[tzidx]).equals("001");
199 bFailure = !hasNoLocation
200 && !((BasicTimeZone)outtz).hasEquivalentTransitions(tz, low, high);
203 errln("Canonical round trip failed; tz=" + tzids[tzidx]
204 + ", locale=" + LOCALES[locidx] + ", pattern=" + PATTERNS[patidx]
205 + ", time=" + DATES[datidx].getTime() + ", str=" + tzstr
206 + ", outtz=" + outtz.getID());
207 } else if (REALLY_VERBOSE_LOG) {
208 logln("Canonical round trip failed (as expected); tz=" + tzids[tzidx]
209 + ", locale=" + LOCALES[locidx] + ", pattern=" + PATTERNS[patidx]
210 + ", time=" + DATES[datidx].getTime() + ", str=" + tzstr
211 + ", outtz=" + outtz.getID());
215 boolean isOffsetFormat = (PATTERNS[patidx].charAt(0) == 'Z'
216 || PATTERNS[patidx].charAt(0) == 'O'
217 || PATTERNS[patidx].charAt(0) == 'X'
218 || PATTERNS[patidx].charAt(0) == 'x');
219 boolean minutesOffset = false;
220 if (PATTERNS[patidx].charAt(0) == 'X' || PATTERNS[patidx].charAt(0) == 'x') {
221 minutesOffset = PATTERNS[patidx].length() <= 3;
224 if (!isOffsetFormat) {
225 // Check if localized GMT format is used as a fallback of name styles
227 for (int n = 0; n < tzstr.length(); n++) {
228 if (UCharacter.isDigit(tzstr.charAt(n))) {
232 isOffsetFormat = (numDigits > 0);
235 if (isOffsetFormat || tzstr.equals(localGMTString)) {
236 // Localized GMT or ISO: total offset (raw + dst) must be preserved.
237 int inOffset = inOffsets[0] + inOffsets[1];
238 int outOffset = outOffsets[0] + outOffsets[1];
239 int diff = outOffset - inOffset;
241 diff = (diff / 60000) * 60000;
244 errln("Offset round trip failed; tz=" + tzids[tzidx]
245 + ", locale=" + LOCALES[locidx] + ", pattern=" + PATTERNS[patidx]
246 + ", time=" + DATES[datidx].getTime() + ", str=" + tzstr
247 + ", inOffset=" + inOffset + ", outOffset=" + outOffset);
250 // Specific or generic: raw offset must be preserved.
251 if (inOffsets[0] != outOffsets[0]) {
252 if (JDKTZ && tzids[tzidx].startsWith("SystemV/")) {
253 // JDK uses rule SystemV for these zones while
254 // ICU handles these zones as aliases of existing time zones
255 if (REALLY_VERBOSE_LOG) {
256 logln("Raw offset round trip failed; tz=" + tzids[tzidx]
257 + ", locale=" + LOCALES[locidx] + ", pattern=" + PATTERNS[patidx]
258 + ", time=" + DATES[datidx].getTime() + ", str=" + tzstr
259 + ", inRawOffset=" + inOffsets[0] + ", outRawOffset=" + outOffsets[0]);
263 errln("Raw offset round trip failed; tz=" + tzids[tzidx]
264 + ", locale=" + LOCALES[locidx] + ", pattern=" + PATTERNS[patidx]
265 + ", time=" + DATES[datidx].getTime() + ", str=" + tzstr
266 + ", inRawOffset=" + inOffsets[0] + ", outRawOffset=" + outOffsets[0]);
279 * Test case of round trip time and text. This test case detects every canonical TimeZone's
280 * rule transition since 1900 until 2020, then check if time around each transition can
281 * round trip as expected.
283 public void TestTimeRoundTrip() {
285 boolean TEST_ALL = getBooleanProperty("TimeZoneRoundTripAll", false);
287 int startYear, endYear;
289 if (TEST_ALL || getInclusion() > 5) {
295 Calendar cal = Calendar.getInstance(TimeZone.getTimeZone("UTC"));
296 endYear = cal.get(Calendar.YEAR) + 3;
298 cal.set(startYear, Calendar.JANUARY, 1);
299 final long START_TIME = cal.getTimeInMillis();
301 cal.set(endYear, Calendar.JANUARY, 1);
302 final long END_TIME = cal.getTimeInMillis();
304 // These patterns are ambiguous at DST->STD local time overlap
305 List<String> AMBIGUOUS_DST_DECESSION = Arrays.asList("v", "vvvv", "V", "VV", "VVV", "VVVV");
307 // These patterns are ambiguous at STD->STD/DST->DST local time overlap
308 List<String> AMBIGUOUS_NEGATIVE_SHIFT = Arrays.asList("z", "zzzz", "v", "vvvv", "V", "VV", "VVV", "VVVV");
310 // These patterns only support integer minutes offset
311 List<String> MINUTES_OFFSET = Arrays.asList("X", "XX", "XXX", "x", "xx", "xxx");
313 // Regex pattern used for filtering zone IDs without exemplar location
314 final Pattern LOC_EXCLUSION_PATTERN = Pattern.compile("Etc/.*|SystemV/.*|.*/Riyadh8[7-9]");
316 final String BASEPATTERN = "yyyy-MM-dd'T'HH:mm:ss.SSS";
318 ULocale[] LOCALES = null;
320 // timer for performance analysis
321 long[] times = new long[PATTERNS.length];
325 // It may take about an hour for testing all locales
326 LOCALES = ULocale.getAvailableLocales();
327 } else if (getInclusion() > 5) {
328 LOCALES = new ULocale[] {
329 new ULocale("ar_EG"), new ULocale("bg_BG"), new ULocale("ca_ES"), new ULocale("da_DK"), new ULocale("de"),
330 new ULocale("de_DE"), new ULocale("el_GR"), new ULocale("en"), new ULocale("en_AU"), new ULocale("en_CA"),
331 new ULocale("en_US"), new ULocale("es"), new ULocale("es_ES"), new ULocale("es_MX"), new ULocale("fi_FI"),
332 new ULocale("fr"), new ULocale("fr_CA"), new ULocale("fr_FR"), new ULocale("he_IL"), new ULocale("hu_HU"),
333 new ULocale("it"), new ULocale("it_IT"), new ULocale("ja"), new ULocale("ja_JP"), new ULocale("ko"),
334 new ULocale("ko_KR"), new ULocale("nb_NO"), new ULocale("nl_NL"), new ULocale("nn_NO"), new ULocale("pl_PL"),
335 new ULocale("pt"), new ULocale("pt_BR"), new ULocale("pt_PT"), new ULocale("ru_RU"), new ULocale("sv_SE"),
336 new ULocale("th_TH"), new ULocale("tr_TR"), new ULocale("zh"), new ULocale("zh_Hans"), new ULocale("zh_Hans_CN"),
337 new ULocale("zh_Hant"), new ULocale("zh_Hant_HK"), new ULocale("zh_Hant_TW")
340 LOCALES = new ULocale[] {
345 SimpleDateFormat sdfGMT = new SimpleDateFormat(BASEPATTERN);
346 sdfGMT.setTimeZone(TimeZone.getTimeZone("Etc/GMT"));
349 long[] testTimes = new long[4];
350 boolean[] expectedRoundTrip = new boolean[4];
352 for (int locidx = 0; locidx < LOCALES.length; locidx++) {
353 logln("Locale: " + LOCALES[locidx].toString());
354 for (int patidx = 0; patidx < PATTERNS.length; patidx++) {
355 logln(" pattern: " + PATTERNS[patidx]);
356 String pattern = BASEPATTERN + " " + PATTERNS[patidx];
357 SimpleDateFormat sdf = new SimpleDateFormat(pattern, LOCALES[locidx]);
358 boolean minutesOffset = MINUTES_OFFSET.contains(PATTERNS[patidx]);
360 Set<String> ids = null;
362 ids = new TreeSet<String>();
363 String[] jdkIDs = java.util.TimeZone.getAvailableIDs();
364 for (String jdkID : jdkIDs) {
365 String tmpID = TimeZone.getCanonicalID(jdkID);
371 ids = TimeZone.getAvailableIDs(SystemTimeZoneType.CANONICAL, null, null);
374 for (String id : ids) {
375 if (PATTERNS[patidx].equals("V")) {
376 // Some zones do not have short ID assigned, such as Asia/Riyadh87.
377 // The time roundtrip will fail for such zones with pattern "V" (short zone ID).
378 // This is expected behavior.
379 String shortZoneID = ZoneMeta.getShortID(id);
380 if (shortZoneID == null) {
383 } else if (PATTERNS[patidx].equals("VVV")) {
384 // Some zones are not associated with any region, such as Etc/GMT+8.
385 // The time roundtrip will fail for such zones with pattern "VVV" (exemplar location).
386 // This is expected behavior.
387 if (id.indexOf('/') < 0 || LOC_EXCLUSION_PATTERN.matcher(id).matches()) {
392 BasicTimeZone btz = (BasicTimeZone)TimeZone.getTimeZone(id, TimeZone.TIMEZONE_ICU);
393 TimeZone tz = TimeZone.getTimeZone(id);
397 TimeZoneTransition tzt = null;
398 boolean middle = true;
399 while (t < END_TIME) {
402 expectedRoundTrip[0] = true;
405 int fromOffset = tzt.getFrom().getRawOffset() + tzt.getFrom().getDSTSavings();
406 int toOffset = tzt.getTo().getRawOffset() + tzt.getTo().getDSTSavings();
407 int delta = toOffset - fromOffset;
409 boolean isDstDecession = tzt.getFrom().getDSTSavings() > 0 && tzt.getTo().getDSTSavings() == 0;
410 testTimes[0] = t + delta - 1;
411 expectedRoundTrip[0] = true;
412 testTimes[1] = t + delta;
413 expectedRoundTrip[1] = isDstDecession ?
414 !AMBIGUOUS_DST_DECESSION.contains(PATTERNS[patidx]) :
415 !AMBIGUOUS_NEGATIVE_SHIFT.contains(PATTERNS[patidx]);
416 testTimes[2] = t - 1;
417 expectedRoundTrip[2] = isDstDecession ?
418 !AMBIGUOUS_DST_DECESSION.contains(PATTERNS[patidx]) :
419 !AMBIGUOUS_NEGATIVE_SHIFT.contains(PATTERNS[patidx]);
421 expectedRoundTrip[3] = true;
424 testTimes[0] = t - 1;
425 expectedRoundTrip[0] = true;
427 expectedRoundTrip[1] = true;
431 for (int testidx = 0; testidx < testLen; testidx++) {
433 timer = System.currentTimeMillis();
434 String text = sdf.format(new Date(testTimes[testidx]));
436 Date parsedDate = sdf.parse(text);
437 long restime = parsedDate.getTime();
438 long timeDiff = restime - testTimes[testidx];
439 boolean bTimeMatch = minutesOffset ?
440 (timeDiff/60000)*60000 == 0 : timeDiff == 0;
442 StringBuffer msg = new StringBuffer();
443 msg.append("Time round trip failed for ")
444 .append("tzid=").append(id)
445 .append(", locale=").append(LOCALES[locidx])
446 .append(", pattern=").append(PATTERNS[patidx])
447 .append(", text=").append(text)
448 .append(", gmt=").append(sdfGMT.format(new Date(testTimes[testidx])))
449 .append(", time=").append(testTimes[testidx])
450 .append(", restime=").append(restime)
451 .append(", diff=").append(timeDiff);
452 if (expectedRoundTrip[testidx]) {
453 errln("FAIL: " + msg.toString());
454 } else if (REALLY_VERBOSE_LOG) {
455 logln(msg.toString());
458 } catch (ParseException pe) {
459 errln("FAIL: " + pe.getMessage() + " tzid=" + id + ", locale=" + LOCALES[locidx] +
460 ", pattern=" + PATTERNS[patidx] + ", text=" + text);
462 times[patidx] += System.currentTimeMillis() - timer;
464 tzt = btz.getNextTransition(t, false);
469 // Test the date in the middle of two transitions.
470 t += (tzt.getTime() - t)/2;
482 logln("### Elapsed time by patterns ###");
483 for (int i = 0; i < PATTERNS.length; i++) {
484 logln(times[i] + "ms (" + PATTERNS[i] + ")");
487 logln("Total: " + total + "ms");
488 logln("Iteration: " + testCounts);
491 public void TestParse() {
492 final Object[][] DATA = {
493 // text inpos locale style parseAll? expected outpos time type
494 {"Z", 0, "en_US", Style.ISO_EXTENDED_FULL, false, "Etc/GMT", 1, TimeType.UNKNOWN},
495 {"Z", 0, "en_US", Style.SPECIFIC_LONG, false, "Etc/GMT", 1, TimeType.UNKNOWN},
496 {"Zambia time", 0, "en_US", Style.ISO_EXTENDED_FULL, true, "Etc/GMT", 1, TimeType.UNKNOWN},
497 {"Zambia time", 0, "en_US", Style.GENERIC_LOCATION, false, "Africa/Lusaka", 11, TimeType.UNKNOWN},
498 {"Zambia time", 0, "en_US", Style.ISO_BASIC_LOCAL_FULL, true, "Africa/Lusaka", 11, TimeType.UNKNOWN},
499 {"+00:00", 0, "en_US", Style.ISO_EXTENDED_FULL, false, "Etc/GMT", 6, TimeType.UNKNOWN},
500 {"-01:30:45", 0, "en_US", Style.ISO_EXTENDED_FULL, false, "GMT-01:30:45", 9, TimeType.UNKNOWN},
501 {"-7", 0, "en_US", Style.ISO_BASIC_LOCAL_FULL, false, "GMT-07:00", 2, TimeType.UNKNOWN},
502 {"-2222", 0, "en_US", Style.ISO_BASIC_LOCAL_FULL, false, "GMT-22:22", 5, TimeType.UNKNOWN},
503 {"-3333", 0, "en_US", Style.ISO_BASIC_LOCAL_FULL, false, "GMT-03:33", 4, TimeType.UNKNOWN},
504 {"XXX+01:30YYY", 3, "en_US", Style.LOCALIZED_GMT, false, "GMT+01:30", 9, TimeType.UNKNOWN},
505 {"GMT0", 0, "en_US", Style.SPECIFIC_SHORT, false, "Etc/GMT", 3, TimeType.UNKNOWN},
506 {"EST", 0, "en_US", Style.SPECIFIC_SHORT, false, "America/New_York", 3, TimeType.STANDARD},
507 {"ESTx", 0, "en_US", Style.SPECIFIC_SHORT, false, "America/New_York", 3, TimeType.STANDARD},
508 {"EDTx", 0, "en_US", Style.SPECIFIC_SHORT, false, "America/New_York", 3, TimeType.DAYLIGHT},
509 {"EST", 0, "en_US", Style.SPECIFIC_LONG, false, null, 0, TimeType.UNKNOWN},
510 {"EST", 0, "en_US", Style.SPECIFIC_LONG, true, "America/New_York", 3, TimeType.STANDARD},
511 {"EST", 0, "en_CA", Style.SPECIFIC_SHORT, false, "America/Toronto", 3, TimeType.STANDARD},
514 for (Object[] test : DATA) {
515 String text = (String)test[0];
516 int inPos = (Integer)test[1];
517 ULocale loc = new ULocale((String)test[2]);
518 Style style = (Style)test[3];
519 EnumSet<ParseOption> options = (Boolean)test[4] ? EnumSet.of(ParseOption.ALL_STYLES) : null;
520 String expID = (String)test[5];
521 int expPos = (Integer)test[6];
522 TimeType expType = (TimeType)test[7];
524 TimeZoneFormat tzfmt = TimeZoneFormat.getInstance(loc);
525 Output<TimeType> timeType = new Output<TimeType>(TimeType.UNKNOWN);
526 ParsePosition pos = new ParsePosition(inPos);
527 TimeZone tz = tzfmt.parse(style, text, pos, options, timeType);
529 String errMsg = null;
532 errMsg = "Parse failure - expected: " + expID;
534 } else if (!tz.getID().equals(expID)) {
535 errMsg = "Time zone ID: " + tz.getID() + " - expected: " + expID;
536 } else if (pos.getIndex() != expPos) {
537 errMsg = "Parsed pos: " + pos.getIndex() + " - expected: " + expPos;
538 } else if (timeType.value != expType) {
539 errMsg = "Time type: " + timeType + " - expected: " + expType;
542 if (errMsg != null) {
543 errln("Fail: " + errMsg + " [text=" + text + ", pos=" + inPos + ", style=" + style + "]");
548 public void TestISOFormat() {
549 final int[] OFFSET = {
554 -77777, // -1m 17.777s
558 -37800000, // -10h 30m
559 -37845000, // -10h 30m 45s
563 final String[][] ISO_STR = {
566 "Z", "Z", "Z", "Z", "Z",
567 "+00", "+0000", "+00:00", "+0000", "+00:00",
572 "Z", "Z", "Z", "Z", "Z",
573 "+00", "+0000", "+00:00", "+0000", "+00:00",
578 "Z", "Z", "Z", "-000059", "-00:00:59",
579 "+00", "+0000", "+00:00", "-000059", "-00:00:59",
584 "+0001", "+0001", "+00:01", "+0001", "+00:01",
585 "+0001", "+0001", "+00:01", "+0001", "+00:01",
590 "-0001", "-0001", "-00:01", "-000117", "-00:01:17",
591 "-0001", "-0001", "-00:01", "-000117", "-00:01:17",
596 "+0030", "+0030", "+00:30", "+0030", "+00:30",
597 "+0030", "+0030", "+00:30", "+0030", "+00:30",
602 "-01", "-0100", "-01:00", "-0100", "-01:00",
603 "-01", "-0100", "-01:00", "-0100", "-01:00",
608 "+10", "+1000", "+10:00", "+1000", "+10:00",
609 "+10", "+1000", "+10:00", "+1000", "+10:00",
614 "-1030", "-1030", "-10:30", "-1030", "-10:30",
615 "-1030", "-1030", "-10:30", "-1030", "-10:30",
620 "-1030", "-1030", "-10:30", "-103045", "-10:30:45",
621 "-1030", "-1030", "-10:30", "-103045", "-10:30:45",
626 null, null, null, null, null,
627 null, null, null, null, null,
632 final String[] PATTERN = {
633 "X", "XX", "XXX", "XXXX", "XXXXX", "x", "xx", "xxx", "xxxx", "xxxxx",
634 "Z", // equivalent to "xxxx"
637 final int[] MIN_OFFSET_UNIT = {
638 60000, 60000, 60000, 1000, 1000, 60000, 60000, 60000, 1000, 1000,
643 SimpleDateFormat sdf = new SimpleDateFormat();
646 for (int i = 0; i < OFFSET.length; i++) {
647 SimpleTimeZone tz = new SimpleTimeZone(OFFSET[i], "Zone Offset:" + String.valueOf(OFFSET[i]) + "ms");
649 for (int j = 0; j < PATTERN.length; j++) {
650 sdf.applyPattern(PATTERN[j]);
652 String result = sdf.format(d);
653 if (!result.equals(ISO_STR[i][j])) {
654 errln("FAIL: pattern=" + PATTERN[j] + ", offset=" + OFFSET[i] + " -> "
655 + result + " (expected: " + ISO_STR[i][j] + ")");
657 } catch (IllegalArgumentException e) {
658 if (ISO_STR[i][j] != null) {
659 errln("FAIL: IAE thrown for pattern=" + PATTERN[j] + ", offset=" + OFFSET[i]
660 + " (expected: " + ISO_STR[i][j] + ")");
667 SimpleTimeZone bogusTZ = new SimpleTimeZone(-1, "Zone Offset: -1ms");
668 for (int i = 0; i < ISO_STR.length; i++) {
669 for (int j = 0; j < ISO_STR[i].length; j++) {
670 if (ISO_STR[i][j] == null) {
673 ParsePosition pos = new ParsePosition(0);
674 Calendar outcal = Calendar.getInstance(bogusTZ);
675 sdf.applyPattern(PATTERN[j]);
677 sdf.parse(ISO_STR[i][j], outcal, pos);
679 if (pos.getIndex() != ISO_STR[i][j].length()) {
680 errln("FAIL: Failed to parse the entire input string: " + ISO_STR[i][j]);
684 TimeZone outtz = outcal.getTimeZone();
685 int outOffset = outtz.getRawOffset();
686 int adjustedOffset = OFFSET[i] / MIN_OFFSET_UNIT[j] * MIN_OFFSET_UNIT[j];
688 if (outOffset != adjustedOffset) {
689 errln("FAIL: Incorrect offset:" + outOffset + "ms for input string: " + ISO_STR[i][j]
690 + " (expected:" + adjustedOffset + "ms)");
696 public void TestFormat() {
697 final Date dateJan = new Date(1358208000000L); // 2013-01-15T00:00:00Z
698 final Date dateJul = new Date(1373846400000L); // 2013-07-15T00:00:00Z
700 final Object[][] TESTDATA = {
703 "America/Los_Angeles",
705 Style.GENERIC_LOCATION,
711 "America/Los_Angeles",
719 "America/Los_Angeles",
722 "Pacific Standard Time",
727 "America/Los_Angeles",
730 "Pacific Daylight Time",
735 "America/Los_Angeles",
738 "America/Los_Angeles",
743 "America/Los_Angeles",
751 "America/Los_Angeles",
753 Style.EXEMPLAR_LOCATION,
762 "\u65E5\u672C\u6A19\u6E96\u6642", // "日本標準時"
767 for (Object[] testCase : TESTDATA) {
768 TimeZoneFormat tzfmt = TimeZoneFormat.getInstance(new ULocale((String)testCase[0]));
769 TimeZone tz = TimeZone.getTimeZone((String)testCase[1]);
770 Output<TimeType> timeType = new Output<TimeType>();
771 String out = tzfmt.format((Style)testCase[3], tz, ((Date)testCase[2]).getTime(), timeType);
773 if (!out.equals((String)testCase[4]) || timeType.value != testCase[5]) {
774 errln("Format result for [locale=" + testCase[0] + ",tzid=" + testCase[1] + ",date=" + testCase[2]
775 + ",style=" + testCase[3] + "]: expected [output=" + testCase[4] + ",type=" + testCase[5]
776 + "]; actual [output=" + out + ",type=" + timeType.value + "]");