+void
+TimeZoneFormatTest::TestISOFormat(void) {
+ const int32_t OFFSET[] = {
+ 0, // 0
+ 999, // 0.999s
+ -59999, // -59.999s
+ 60000, // 1m
+ -77777, // -1m 17.777s
+ 1800000, // 30m
+ -3600000, // -1h
+ 36000000, // 10h
+ -37800000, // -10h 30m
+ -37845000, // -10h 30m 45s
+ 108000000, // 30h
+ };
+
+ const char* ISO_STR[][11] = {
+ // 0
+ {
+ "Z", "Z", "Z", "Z", "Z",
+ "+00", "+0000", "+00:00", "+0000", "+00:00",
+ "+0000"
+ },
+ // 999
+ {
+ "Z", "Z", "Z", "Z", "Z",
+ "+00", "+0000", "+00:00", "+0000", "+00:00",
+ "+0000"
+ },
+ // -59999
+ {
+ "Z", "Z", "Z", "-000059", "-00:00:59",
+ "+00", "+0000", "+00:00", "-000059", "-00:00:59",
+ "-000059"
+ },
+ // 60000
+ {
+ "+0001", "+0001", "+00:01", "+0001", "+00:01",
+ "+0001", "+0001", "+00:01", "+0001", "+00:01",
+ "+0001"
+ },
+ // -77777
+ {
+ "-0001", "-0001", "-00:01", "-000117", "-00:01:17",
+ "-0001", "-0001", "-00:01", "-000117", "-00:01:17",
+ "-000117"
+ },
+ // 1800000
+ {
+ "+0030", "+0030", "+00:30", "+0030", "+00:30",
+ "+0030", "+0030", "+00:30", "+0030", "+00:30",
+ "+0030"
+ },
+ // -3600000
+ {
+ "-01", "-0100", "-01:00", "-0100", "-01:00",
+ "-01", "-0100", "-01:00", "-0100", "-01:00",
+ "-0100"
+ },
+ // 36000000
+ {
+ "+10", "+1000", "+10:00", "+1000", "+10:00",
+ "+10", "+1000", "+10:00", "+1000", "+10:00",
+ "+1000"
+ },
+ // -37800000
+ {
+ "-1030", "-1030", "-10:30", "-1030", "-10:30",
+ "-1030", "-1030", "-10:30", "-1030", "-10:30",
+ "-1030"
+ },
+ // -37845000
+ {
+ "-1030", "-1030", "-10:30", "-103045", "-10:30:45",
+ "-1030", "-1030", "-10:30", "-103045", "-10:30:45",
+ "-103045"
+ },
+ // 108000000
+ {
+ 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0,
+ 0
+ }
+ };
+
+ const char* PATTERN[] = {
+ "X", "XX", "XXX", "XXXX", "XXXXX",
+ "x", "xx", "xxx", "xxxx", "xxxxx",
+ "Z", // equivalent to "xxxx"
+ 0
+ };
+
+ const int32_t MIN_OFFSET_UNIT[] = {
+ 60000, 60000, 60000, 1000, 1000,
+ 60000, 60000, 60000, 1000, 1000,
+ 1000,
+ };
+
+ // Formatting
+ UErrorCode status = U_ZERO_ERROR;
+ LocalPointer<SimpleDateFormat> sdf(new SimpleDateFormat(status), status);
+ if (U_FAILURE(status)) {
+ dataerrln("Fail new SimpleDateFormat: %s", u_errorName(status));
+ return;
+ }
+ UDate d = Calendar::getNow();
+
+ for (uint32_t i = 0; i < UPRV_LENGTHOF(OFFSET); i++) {
+ SimpleTimeZone* tz = new SimpleTimeZone(OFFSET[i], UnicodeString("Zone Offset:") + OFFSET[i] + "ms");
+ sdf->adoptTimeZone(tz);
+ for (int32_t j = 0; PATTERN[j] != 0; j++) {
+ sdf->applyPattern(UnicodeString(PATTERN[j]));
+ UnicodeString result;
+ sdf->format(d, result);
+
+ if (ISO_STR[i][j]) {
+ if (result != UnicodeString(ISO_STR[i][j])) {
+ errln((UnicodeString)"FAIL: pattern=" + PATTERN[j] + ", offset=" + OFFSET[i] + " -> "
+ + result + " (expected: " + ISO_STR[i][j] + ")");
+ }
+ } else {
+ // Offset out of range
+ // Note: for now, there is no way to propagate the error status through
+ // the SimpleDateFormat::format above.
+ if (result.length() > 0) {
+ errln((UnicodeString)"FAIL: Non-Empty result for pattern=" + PATTERN[j] + ", offset=" + OFFSET[i]
+ + " (expected: empty result)");
+ }
+ }
+ }
+ }
+
+ // Parsing
+ LocalPointer<Calendar> outcal(Calendar::createInstance(status));
+ if (U_FAILURE(status)) {
+ dataerrln("Fail new Calendar: %s", u_errorName(status));
+ return;
+ }
+ for (int32_t i = 0; ISO_STR[i][0] != NULL; i++) {
+ for (int32_t j = 0; PATTERN[j] != 0; j++) {
+ if (ISO_STR[i][j] == 0) {
+ continue;
+ }
+ ParsePosition pos(0);
+ SimpleTimeZone* bogusTZ = new SimpleTimeZone(-1, UnicodeString("Zone Offset: -1ms"));
+ outcal->adoptTimeZone(bogusTZ);
+ sdf->applyPattern(PATTERN[j]);
+
+ sdf->parse(UnicodeString(ISO_STR[i][j]), *(outcal.getAlias()), pos);
+
+ if (pos.getIndex() != (int32_t)uprv_strlen(ISO_STR[i][j])) {
+ errln((UnicodeString)"FAIL: Failed to parse the entire input string: " + ISO_STR[i][j]);
+ }
+
+ const TimeZone& outtz = outcal->getTimeZone();
+ int32_t outOffset = outtz.getRawOffset();
+ int32_t adjustedOffset = OFFSET[i] / MIN_OFFSET_UNIT[j] * MIN_OFFSET_UNIT[j];
+ if (outOffset != adjustedOffset) {
+ errln((UnicodeString)"FAIL: Incorrect offset:" + outOffset + "ms for input string: " + ISO_STR[i][j]
+ + " (expected:" + adjustedOffset + "ms)");
+ }
+ }
+ }
+}
+
+
+typedef struct {
+ const char* locale;
+ const char* tzid;
+ UDate date;
+ UTimeZoneFormatStyle style;
+ const char* expected;
+ UTimeZoneFormatTimeType timeType;
+} FormatTestData;
+
+void
+TimeZoneFormatTest::TestFormat(void) {
+ UDate dateJan = 1358208000000.0; // 2013-01-15T00:00:00Z
+ UDate dateJul = 1373846400000.0; // 2013-07-15T00:00:00Z
+
+ const FormatTestData DATA[] = {
+ {
+ "en",
+ "America/Los_Angeles",
+ dateJan,
+ UTZFMT_STYLE_GENERIC_LOCATION,
+ "Los Angeles Time",
+ UTZFMT_TIME_TYPE_UNKNOWN
+ },
+ {
+ "en",
+ "America/Los_Angeles",
+ dateJan,
+ UTZFMT_STYLE_GENERIC_LONG,
+ "Pacific Time",
+ UTZFMT_TIME_TYPE_UNKNOWN
+ },
+ {
+ "en",
+ "America/Los_Angeles",
+ dateJan,
+ UTZFMT_STYLE_SPECIFIC_LONG,
+ "Pacific Standard Time",
+ UTZFMT_TIME_TYPE_STANDARD
+ },
+ {
+ "en",
+ "America/Los_Angeles",
+ dateJul,
+ UTZFMT_STYLE_SPECIFIC_LONG,
+ "Pacific Daylight Time",
+ UTZFMT_TIME_TYPE_DAYLIGHT
+ },
+ {
+ "ja",
+ "America/Los_Angeles",
+ dateJan,
+ UTZFMT_STYLE_ZONE_ID,
+ "America/Los_Angeles",
+ UTZFMT_TIME_TYPE_UNKNOWN
+ },
+ {
+ "fr",
+ "America/Los_Angeles",
+ dateJul,
+ UTZFMT_STYLE_ZONE_ID_SHORT,
+ "uslax",
+ UTZFMT_TIME_TYPE_UNKNOWN
+ },
+ {
+ "en",
+ "America/Los_Angeles",
+ dateJan,
+ UTZFMT_STYLE_EXEMPLAR_LOCATION,
+ "Los Angeles",
+ UTZFMT_TIME_TYPE_UNKNOWN
+ },
+
+ {
+ "ja",
+ "Asia/Tokyo",
+ dateJan,
+ UTZFMT_STYLE_GENERIC_LONG,
+ "\\u65E5\\u672C\\u6A19\\u6E96\\u6642",
+ UTZFMT_TIME_TYPE_UNKNOWN
+ },
+
+ {0, 0, 0.0, UTZFMT_STYLE_GENERIC_LOCATION, 0, UTZFMT_TIME_TYPE_UNKNOWN}
+ };
+
+ for (int32_t i = 0; DATA[i].locale; i++) {
+ UErrorCode status = U_ZERO_ERROR;
+ LocalPointer<TimeZoneFormat> tzfmt(TimeZoneFormat::createInstance(Locale(DATA[i].locale), status));
+ if (U_FAILURE(status)) {
+ dataerrln("Fail TimeZoneFormat::createInstance: %s", u_errorName(status));
+ continue;
+ }
+
+ LocalPointer<TimeZone> tz(TimeZone::createTimeZone(DATA[i].tzid));
+ UnicodeString out;
+ UTimeZoneFormatTimeType timeType;
+
+ tzfmt->format(DATA[i].style, *(tz.getAlias()), DATA[i].date, out, &timeType);
+ UnicodeString expected(DATA[i].expected, -1, US_INV);
+ expected = expected.unescape();
+
+ assertEquals(UnicodeString("Format result for ") + DATA[i].tzid + " (Test Case " + i + ")", expected, out);
+ if (DATA[i].timeType != timeType) {
+ dataerrln(UnicodeString("Formatted time zone type (Test Case ") + i + "), returned="
+ + timeType + ", expected=" + DATA[i].timeType);
+ }
+ }
+}
+
+void
+TimeZoneFormatTest::TestFormatTZDBNames(void) {
+ UDate dateJan = 1358208000000.0; // 2013-01-15T00:00:00Z
+ UDate dateJul = 1373846400000.0; // 2013-07-15T00:00:00Z
+
+ const FormatTestData DATA[] = {
+ {
+ "en",
+ "America/Chicago",
+ dateJan,
+ UTZFMT_STYLE_SPECIFIC_SHORT,
+ "CST",
+ UTZFMT_TIME_TYPE_STANDARD
+ },
+ {
+ "en",
+ "Asia/Shanghai",
+ dateJan,
+ UTZFMT_STYLE_SPECIFIC_SHORT,
+ "CST",
+ UTZFMT_TIME_TYPE_STANDARD
+ },
+ {
+ "zh_Hans",
+ "Asia/Shanghai",
+ dateJan,
+ UTZFMT_STYLE_SPECIFIC_SHORT,
+ "CST",
+ UTZFMT_TIME_TYPE_STANDARD
+ },
+ {
+ "en",
+ "America/Los_Angeles",
+ dateJul,
+ UTZFMT_STYLE_SPECIFIC_LONG,
+ "GMT-07:00", // No long display names
+ UTZFMT_TIME_TYPE_DAYLIGHT
+ },
+ {
+ "ja",
+ "America/Los_Angeles",
+ dateJul,
+ UTZFMT_STYLE_SPECIFIC_SHORT,
+ "PDT",
+ UTZFMT_TIME_TYPE_DAYLIGHT
+ },
+ {
+ "en",
+ "Australia/Sydney",
+ dateJan,
+ UTZFMT_STYLE_SPECIFIC_SHORT,
+ "AEDT",
+ UTZFMT_TIME_TYPE_DAYLIGHT
+ },
+ {
+ "en",
+ "Australia/Sydney",
+ dateJul,
+ UTZFMT_STYLE_SPECIFIC_SHORT,
+ "AEST",
+ UTZFMT_TIME_TYPE_STANDARD
+ },
+
+ {0, 0, 0.0, UTZFMT_STYLE_GENERIC_LOCATION, 0, UTZFMT_TIME_TYPE_UNKNOWN}
+ };
+
+ for (int32_t i = 0; DATA[i].locale; i++) {
+ UErrorCode status = U_ZERO_ERROR;
+ Locale loc(DATA[i].locale);
+ LocalPointer<TimeZoneFormat> tzfmt(TimeZoneFormat::createInstance(loc, status));
+ if (U_FAILURE(status)) {
+ dataerrln("Fail TimeZoneFormat::createInstance: %s", u_errorName(status));
+ continue;
+ }
+ TimeZoneNames *tzdbNames = TimeZoneNames::createTZDBInstance(loc, status);
+ if (U_FAILURE(status)) {
+ dataerrln("Fail TimeZoneNames::createTZDBInstance: %s", u_errorName(status));
+ continue;
+ }
+ tzfmt->adoptTimeZoneNames(tzdbNames);
+
+ LocalPointer<TimeZone> tz(TimeZone::createTimeZone(DATA[i].tzid));
+ UnicodeString out;
+ UTimeZoneFormatTimeType timeType;
+
+ tzfmt->format(DATA[i].style, *(tz.getAlias()), DATA[i].date, out, &timeType);
+ UnicodeString expected(DATA[i].expected, -1, US_INV);
+ expected = expected.unescape();
+
+ assertEquals(UnicodeString("Format result for ") + DATA[i].tzid + " (Test Case " + i + ")", expected, out);
+ if (DATA[i].timeType != timeType) {
+ dataerrln(UnicodeString("Formatted time zone type (Test Case ") + i + "), returned="
+ + timeType + ", expected=" + DATA[i].timeType);
+ }
+ }
+}
+
+void
+TimeZoneFormatTest::TestFormatCustomZone(void) {
+ struct {
+ const char* id;
+ int32_t offset;
+ const char* expected;
+ } TESTDATA[] = {
+ { "abc", 3600000, "GMT+01:00" }, // unknown ID
+ { "$abc", -3600000, "GMT-01:00" }, // unknown, with ASCII variant char '$'
+ { "\\u00c1\\u00df\\u00c7", 5400000, "GMT+01:30"}, // unknown, with non-ASCII chars
+ { 0, 0, 0 }
+ };
+
+ UDate now = Calendar::getNow();
+
+ for (int32_t i = 0; ; i++) {
+ const char *id = TESTDATA[i].id;
+ if (id == 0) {
+ break;
+ }
+ UnicodeString tzid = UnicodeString(id, -1, US_INV).unescape();
+ SimpleTimeZone tz(TESTDATA[i].offset, tzid);
+
+ UErrorCode status = U_ZERO_ERROR;
+ LocalPointer<TimeZoneFormat> tzfmt(TimeZoneFormat::createInstance(Locale("en"), status));
+ if (tzfmt.isNull()) {
+ dataerrln("FAIL: TimeZoneFormat::createInstance failed for en");
+ return;
+ }
+ UnicodeString tzstr;
+ UnicodeString expected = UnicodeString(TESTDATA[i].expected, -1, US_INV).unescape();
+
+ tzfmt->format(UTZFMT_STYLE_SPECIFIC_LONG, tz, now, tzstr, NULL);
+ assertEquals(UnicodeString("Format result for ") + tzid, expected, tzstr);
+ }
+}
+
+void
+TimeZoneFormatTest::TestFormatTZDBNamesAllZoneCoverage(void) {
+ UErrorCode status = U_ZERO_ERROR;
+ LocalPointer<StringEnumeration> tzids(TimeZone::createEnumeration());
+ if (tzids.getAlias() == nullptr) {
+ dataerrln("%s %d tzids is null", __FILE__, __LINE__);
+ return;
+ }
+ const UnicodeString *tzid;
+ LocalPointer<TimeZoneNames> tzdbNames(TimeZoneNames::createTZDBInstance(Locale("en"), status));
+ UDate now = Calendar::getNow();
+ UnicodeString mzId;
+ UnicodeString name;
+ while ((tzid = tzids->snext(status))) {
+ logln("Zone: " + *tzid);
+ LocalPointer<TimeZone> tz(TimeZone::createTimeZone(*tzid));
+ tzdbNames->getMetaZoneID(*tzid, now, mzId);
+ if (mzId.isBogus()) {
+ logln((UnicodeString)"Meta zone: <not available>");
+ } else {
+ logln((UnicodeString)"Meta zone: " + mzId);
+ }
+
+ // mzID could be bogus here
+ tzdbNames->getMetaZoneDisplayName(mzId, UTZNM_SHORT_STANDARD, name);
+ // name could be bogus here
+ if (name.isBogus()) {
+ logln((UnicodeString)"Meta zone short standard name: <not available>");
+ }
+ else {
+ logln((UnicodeString)"Meta zone short standard name: " + name);
+ }
+
+ tzdbNames->getMetaZoneDisplayName(mzId, UTZNM_SHORT_DAYLIGHT, name);
+ // name could be bogus here
+ if (name.isBogus()) {
+ logln((UnicodeString)"Meta zone short daylight name: <not available>");
+ }
+ else {
+ logln((UnicodeString)"Meta zone short daylight name: " + name);
+ }
+ }
+}