| /* |
| * Copyright (C) 2010 The Android Open Source Project |
| * |
| * Licensed under the Apache License, Version 2.0 (the "License"); |
| * you may not use this file except in compliance with the License. |
| * You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| |
| package libcore.java.text; |
| |
| import java.text.DateFormat; |
| import java.text.DateFormatSymbols; |
| import java.text.ParseException; |
| import java.text.ParsePosition; |
| import java.text.SimpleDateFormat; |
| import java.util.Calendar; |
| import java.util.Date; |
| import java.util.GregorianCalendar; |
| import java.util.Locale; |
| import java.util.TimeZone; |
| |
| public class SimpleDateFormatTest extends junit.framework.TestCase { |
| |
| private static final TimeZone AMERICA_LOS_ANGELES = TimeZone.getTimeZone("America/Los_Angeles"); |
| private static final TimeZone AUSTRALIA_LORD_HOWE = TimeZone.getTimeZone("Australia/Lord_Howe"); |
| private static final TimeZone UTC = TimeZone.getTimeZone("Etc/UTC"); |
| |
| /** |
| * The list of time zone ids formatted as "UTC". |
| */ |
| private static final String[] UTC_ZONE_IDS = new String[] { |
| "Etc/UCT", "Etc/UTC", "Etc/Universal", "Etc/Zulu", "UCT", "UTC", "Universal", "Zulu" |
| }; |
| |
| |
| private Locale defaultLocale; |
| |
| @Override |
| public void setUp() throws Exception { |
| super.setUp(); |
| defaultLocale = Locale.getDefault(); |
| // Locale affects timezone names / abbreviations so can affect formatting and parsing. |
| Locale.setDefault(Locale.US); |
| } |
| |
| @Override |
| public void tearDown() throws Exception { |
| Locale.setDefault(defaultLocale); |
| super.tearDown(); |
| } |
| |
| /** |
| * Tests that the default constructor uses the data in the default locale |
| */ |
| public void testDefaultConstructor_localeUS() { |
| SimpleDateFormat sdf = new SimpleDateFormat(); |
| DateFormat referencedDateFormat = DateFormat.getDateTimeInstance( |
| DateFormat.SHORT, DateFormat.SHORT, Locale.US); |
| Date date = new Date(0); |
| |
| assertEquals(referencedDateFormat.format(date), sdf.format(date)); |
| } |
| |
| // The RI fails this test. |
| public void test2DigitYearStartIsCloned() throws Exception { |
| // Test that get2DigitYearStart returns a clone. |
| SimpleDateFormat sdf = new SimpleDateFormat(); |
| sdf.setTimeZone(UTC); |
| |
| Date originalDate = sdf.get2DigitYearStart(); |
| assertNotSame(sdf.get2DigitYearStart(), originalDate); |
| assertEquals(sdf.get2DigitYearStart(), originalDate); |
| originalDate.setTime(0); |
| assertFalse(sdf.get2DigitYearStart().equals(originalDate)); |
| // Test that set2DigitYearStart takes a clone. |
| Date newDate = new Date(); |
| sdf.set2DigitYearStart(newDate); |
| assertNotSame(sdf.get2DigitYearStart(), newDate); |
| assertEquals(sdf.get2DigitYearStart(), newDate); |
| newDate.setTime(0); |
| assertFalse(sdf.get2DigitYearStart().equals(newDate)); |
| } |
| |
| // The RI fails this test because this is an ICU-compatible Android extension. |
| // Necessary for correct localization in various languages (http://b/2633414). |
| public void testStandAloneNames() throws Exception { |
| Locale en = Locale.ENGLISH; |
| Locale pl = new Locale("pl"); |
| Locale ru = new Locale("ru"); |
| |
| assertEquals("January", formatDateUtc(en, "MMMM")); |
| assertEquals("January", formatDateUtc(en, "LLLL")); |
| assertEquals("stycznia", formatDateUtc(pl, "MMMM")); |
| assertEquals("stycze\u0144", formatDateUtc(pl, "LLLL")); |
| |
| assertEquals("Thursday", formatDateUtc(en, "EEEE")); |
| assertEquals("Thursday", formatDateUtc(en, "cccc")); |
| assertEquals("\u0447\u0435\u0442\u0432\u0435\u0440\u0433", formatDateUtc(ru, "EEEE")); |
| assertEquals("\u0447\u0435\u0442\u0432\u0435\u0440\u0433", formatDateUtc(ru, "cccc")); |
| |
| assertEquals(Calendar.JUNE, parseDateUtc(en, "yyyy-MMMM-dd", "1980-June-12").get(Calendar.MONTH)); |
| assertEquals(Calendar.JUNE, parseDateUtc(en, "yyyy-LLLL-dd", "1980-June-12").get(Calendar.MONTH)); |
| assertEquals(Calendar.JUNE, parseDateUtc(pl, "yyyy-MMMM-dd", "1980-czerwca-12").get(Calendar.MONTH)); |
| assertEquals(Calendar.JUNE, parseDateUtc(pl, "yyyy-LLLL-dd", "1980-czerwiec-12").get(Calendar.MONTH)); |
| |
| assertEquals(Calendar.TUESDAY, parseDateUtc(en, "EEEE", "Tuesday").get(Calendar.DAY_OF_WEEK)); |
| assertEquals(Calendar.TUESDAY, parseDateUtc(en, "cccc", "Tuesday").get(Calendar.DAY_OF_WEEK)); |
| assertEquals(Calendar.TUESDAY, parseDateUtc(ru, "EEEE", "\u0432\u0442\u043e\u0440\u043d\u0438\u043a").get(Calendar.DAY_OF_WEEK)); |
| assertEquals(Calendar.TUESDAY, parseDateUtc(ru, "cccc", "\u0412\u0442\u043e\u0440\u043d\u0438\u043a").get(Calendar.DAY_OF_WEEK)); |
| } |
| |
| // The RI fails this test because it doesn't fully support UTS #35. |
| // https://code.google.com/p/android/issues/detail?id=39616 |
| public void testFiveCount_parsing() throws Exception { |
| // It's pretty silly to try to parse the shortest names, because they're almost always |
| // ambiguous. |
| assertCannotParse(Locale.ENGLISH, "MMMMM", "J"); |
| assertCannotParse(Locale.ENGLISH, "LLLLL", "J"); |
| assertCannotParse(Locale.ENGLISH, "EEEEE", "T"); |
| assertCannotParse(Locale.ENGLISH, "ccccc", "T"); |
| } |
| |
| // The RI fails this test because it doesn't fully support UTS #35. |
| // https://code.google.com/p/android/issues/detail?id=39616 |
| public void testFiveCount_M() throws Exception { |
| assertEquals("1", formatDateUtc(Locale.ENGLISH, "M")); |
| assertEquals("01", formatDateUtc(Locale.ENGLISH, "MM")); |
| assertEquals("Jan", formatDateUtc(Locale.ENGLISH, "MMM")); |
| assertEquals("January", formatDateUtc(Locale.ENGLISH, "MMMM")); |
| assertEquals("J", formatDateUtc(Locale.ENGLISH, "MMMMM")); |
| } |
| |
| // The RI fails this test because it doesn't fully support UTS #35. |
| // https://code.google.com/p/android/issues/detail?id=39616 |
| public void testFiveCount_L() throws Exception { |
| assertEquals("1", formatDateUtc(Locale.ENGLISH, "L")); |
| assertEquals("01", formatDateUtc(Locale.ENGLISH, "LL")); |
| assertEquals("Jan", formatDateUtc(Locale.ENGLISH, "LLL")); |
| assertEquals("January", formatDateUtc(Locale.ENGLISH, "LLLL")); |
| assertEquals("J", formatDateUtc(Locale.ENGLISH, "LLLLL")); |
| } |
| |
| // The RI fails this test because it doesn't fully support UTS #35. |
| // https://code.google.com/p/android/issues/detail?id=39616 |
| public void testFiveCount_E() throws Exception { |
| assertEquals("Thu", formatDateUtc(Locale.ENGLISH, "E")); |
| assertEquals("Thu", formatDateUtc(Locale.ENGLISH, "EE")); |
| assertEquals("Thu", formatDateUtc(Locale.ENGLISH, "EEE")); |
| assertEquals("Thursday", formatDateUtc(Locale.ENGLISH, "EEEE")); |
| assertEquals("T", formatDateUtc(Locale.ENGLISH, "EEEEE")); |
| // assertEquals("Th", formatDate(Locale.ENGLISH, "EEEEEE")); // icu4c doesn't support 6. |
| } |
| |
| // The RI fails this test because it doesn't fully support UTS #35. |
| // https://code.google.com/p/android/issues/detail?id=39616 |
| public void testFiveCount_c() throws Exception { |
| assertEquals("Thu", formatDateUtc(Locale.ENGLISH, "c")); |
| assertEquals("Thu", formatDateUtc(Locale.ENGLISH, "cc")); |
| assertEquals("Thu", formatDateUtc(Locale.ENGLISH, "ccc")); |
| assertEquals("Thursday", formatDateUtc(Locale.ENGLISH, "cccc")); |
| assertEquals("T", formatDateUtc(Locale.ENGLISH, "ccccc")); |
| // assertEquals("Th", formatDate(Locale.ENGLISH, "cccccc")); // icu4c doesn't support 6. |
| } |
| |
| // The RI fails this test because it doesn't fully support UTS #35. |
| // https://code.google.com/p/android/issues/detail?id=39616 |
| public void testFiveCount_Z() throws Exception { |
| assertEquals("+0000", formatDateUtc(Locale.ENGLISH, "Z")); |
| assertEquals("+0000", formatDateUtc(Locale.ENGLISH, "ZZ")); |
| assertEquals("+0000", formatDateUtc(Locale.ENGLISH, "ZZZ")); |
| assertEquals("GMT+00:00", formatDateUtc(Locale.ENGLISH, "ZZZZ")); |
| assertEquals("+00:00", formatDateUtc(Locale.ENGLISH, "ZZZZZ")); |
| |
| TimeZone tz = AMERICA_LOS_ANGELES; |
| assertEquals("-0800", formatDate(Locale.ENGLISH, "Z", tz)); |
| assertEquals("-0800", formatDate(Locale.ENGLISH, "ZZ", tz)); |
| assertEquals("-0800", formatDate(Locale.ENGLISH, "ZZZ", tz)); |
| assertEquals("GMT-08:00", formatDate(Locale.ENGLISH, "ZZZZ", tz)); |
| assertEquals("-08:00", formatDate(Locale.ENGLISH, "ZZZZZ", tz)); |
| } |
| |
| // The RI fails this test because it doesn't fully support UTS #35. |
| // https://code.google.com/p/android/issues/detail?id=39616 |
| public void test_parsing_Z() throws Exception { |
| assertEquals(1325421240000L, parseTimeUtc("yyyy-MM-dd' 'Z", "2012-01-01 -1234")); |
| assertEquals(1325421240000L, parseTimeUtc("yyyy-MM-dd' 'ZZ", "2012-01-01 -1234")); |
| assertEquals(1325421240000L, parseTimeUtc("yyyy-MM-dd' 'ZZZ", "2012-01-01 -1234")); |
| assertEquals(1325421240000L, parseTimeUtc("yyyy-MM-dd' 'ZZZZ", "2012-01-01 GMT-12:34")); |
| assertEquals(1325421240000L, parseTimeUtc("yyyy-MM-dd' 'ZZZZZ", "2012-01-01 -12:34")); |
| } |
| |
| private static long parseTimeUtc(String fmt, String value) { |
| return parseDateUtc(Locale.ENGLISH, fmt, value).getTime().getTime(); |
| } |
| |
| public void test2038() { |
| SimpleDateFormat format = new SimpleDateFormat("EEE MMM dd HH:mm:ss yyyy", Locale.US); |
| format.setTimeZone(UTC); |
| |
| assertEquals("Sun Nov 24 17:31:44 1833", |
| format.format(new Date(((long) Integer.MIN_VALUE + Integer.MIN_VALUE) * 1000L))); |
| assertEquals("Fri Dec 13 20:45:52 1901", |
| format.format(new Date(Integer.MIN_VALUE * 1000L))); |
| assertEquals("Thu Jan 01 00:00:00 1970", |
| format.format(new Date(0L))); |
| assertEquals("Tue Jan 19 03:14:07 2038", |
| format.format(new Date(Integer.MAX_VALUE * 1000L))); |
| assertEquals("Sun Feb 07 06:28:16 2106", |
| format.format(new Date((2L + Integer.MAX_VALUE + Integer.MAX_VALUE) * 1000L))); |
| } |
| |
| private String formatDateUtc(Locale l, String fmt) { |
| return formatDate(l, fmt, UTC); |
| } |
| |
| private String formatDate(Locale l, String fmt, TimeZone tz) { |
| DateFormat dateFormat = new SimpleDateFormat(fmt, l); |
| dateFormat.setTimeZone(tz); |
| return dateFormat.format(new Date(0)); |
| } |
| |
| private static void assertCannotParse(Locale l, String fmt, String value) { |
| SimpleDateFormat sdf = new SimpleDateFormat(fmt, l); |
| sdf.setTimeZone(UTC); |
| ParsePosition pp = new ParsePosition(0); |
| Date d = sdf.parse(value, pp); |
| assertNull("Value " + value + " must not parse in locale " + l + " with format " + fmt, d); |
| } |
| |
| /** |
| * Parse a date with a SimpleDateFormat set to use UTC. If fmt contains a pattern for zone the |
| * use of UTC should have no effect, but in other cases it can affect the outcome. The returned |
| * calendar will also be set to UTC. |
| */ |
| private static Calendar parseDateUtc(Locale l, String fmt, String value) { |
| return parseDate(l, fmt, value, UTC); |
| } |
| |
| private static Calendar parseDate(Locale l, String fmt, String value, TimeZone tz) { |
| SimpleDateFormat sdf = new SimpleDateFormat(fmt, l); |
| sdf.setTimeZone(tz); |
| ParsePosition pp = new ParsePosition(0); |
| Date d = sdf.parse(value, pp); |
| if (d == null) { |
| fail(pp.toString()); |
| } |
| if (pp.getIndex() != value.length()) { |
| fail("Value " + value + " must be fully consumed: " + pp.toString()); |
| } |
| Calendar c = Calendar.getInstance(tz); |
| c.setTime(d); |
| return c; |
| } |
| |
| // http://code.google.com/p/android/issues/detail?id=13420 |
| public void testParsingUncommonTimeZoneAbbreviations() { |
| String fmt = "yyyy-MM-dd HH:mm:ss.SSS z"; |
| String date = "2010-12-23 12:44:57.0 CET"; |
| // ICU considers "CET" (Central European Time) to be common in Britain... |
| assertEquals(1293104697000L, parseDateUtc(Locale.UK, fmt, date).getTimeInMillis()); |
| // ...but not in the US. |
| assertCannotParse(Locale.US, fmt, date); |
| } |
| |
| // In Honeycomb, only one Olson id was associated with CET (or any other "uncommon" |
| // abbreviation). This was changed after KitKat to avoid Java hacks on top of ICU data. |
| // ICU data only provides abbreviations for timezones in the locales where they would |
| // not be ambiguous to most people of that locale. |
| public void testFormattingUncommonTimeZoneAbbreviations() { |
| String fmt = "yyyy-MM-dd HH:mm:ss.SSS z"; |
| String unambiguousDate = "1970-01-01 01:00:00.000 CET"; |
| String ambiguousDate = "1970-01-01 01:00:00.000 GMT+01:00"; |
| |
| // The locale to use when formatting. Not every Locale renders "Europe/Berlin" as "CET". The |
| // UK is one that does, the US is one that does not. |
| Locale cetUnambiguousLocale = Locale.UK; |
| Locale cetAmbiguousLocale = Locale.US; |
| TimeZone europeBerlin = TimeZone.getTimeZone("Europe/Berlin"); |
| TimeZone europeZurich = TimeZone.getTimeZone("Europe/Zurich"); |
| |
| SimpleDateFormat sdf = new SimpleDateFormat(fmt, cetUnambiguousLocale); |
| sdf.setTimeZone(europeBerlin); |
| assertEquals(unambiguousDate, sdf.format(new Date(0))); |
| sdf = new SimpleDateFormat(fmt, cetUnambiguousLocale); |
| sdf.setTimeZone(europeZurich); |
| assertEquals(unambiguousDate, sdf.format(new Date(0))); |
| |
| sdf = new SimpleDateFormat(fmt, cetAmbiguousLocale); |
| sdf.setTimeZone(europeBerlin); |
| assertEquals(ambiguousDate, sdf.format(new Date(0))); |
| sdf = new SimpleDateFormat(fmt, cetAmbiguousLocale); |
| sdf.setTimeZone(europeZurich); |
| assertEquals(ambiguousDate, sdf.format(new Date(0))); |
| } |
| |
| // http://code.google.com/p/android/issues/detail?id=8258 |
| public void testTimeZoneFormatting() throws Exception { |
| Date epoch = new Date(0); |
| |
| // Create a SimpleDateFormat that defaults to America/Chicago... |
| TimeZone americaChicago = TimeZone.getTimeZone("America/Chicago"); |
| TimeZone.setDefault(americaChicago); |
| SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss Z"); |
| assertEquals(americaChicago, sdf.getTimeZone()); |
| |
| // We should see something appropriate to America/Chicago... |
| assertEquals("1969-12-31 18:00:00 -0600", sdf.format(epoch)); |
| // We can set any TimeZone we want: |
| sdf.setTimeZone(AMERICA_LOS_ANGELES); |
| assertEquals("1969-12-31 16:00:00 -0800", sdf.format(epoch)); |
| sdf.setTimeZone(UTC); |
| assertEquals("1970-01-01 00:00:00 +0000", sdf.format(epoch)); |
| |
| // A new SimpleDateFormat will default to America/Chicago... |
| sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss Z"); |
| assertEquals(americaChicago, sdf.getTimeZone()); |
| |
| // ...and parsing an America/Los_Angeles time will *not* change that... |
| sdf.parse("2010-12-03 00:00:00 -0800"); |
| assertEquals(americaChicago, sdf.getTimeZone()); |
| |
| // ...so our time zone here is "America/Chicago": |
| assertEquals("1969-12-31 18:00:00 -0600", sdf.format(epoch)); |
| // We can set any TimeZone we want: |
| sdf.setTimeZone(AMERICA_LOS_ANGELES); |
| assertEquals("1969-12-31 16:00:00 -0800", sdf.format(epoch)); |
| sdf.setTimeZone(UTC); |
| assertEquals("1970-01-01 00:00:00 +0000", sdf.format(epoch)); |
| |
| sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); |
| sdf.setTimeZone(UTC); |
| Date date = sdf.parse("2010-07-08 02:44:48"); |
| assertEquals(UTC, sdf.getTimeZone()); |
| assertEquals(1278557088000L, date.getTime()); |
| |
| sdf = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ssZ"); |
| sdf.setTimeZone(AMERICA_LOS_ANGELES); |
| assertEquals("2010-07-07T19:44:48-0700", sdf.format(date)); |
| assertEquals(AMERICA_LOS_ANGELES, sdf.getTimeZone()); |
| sdf.setTimeZone(UTC); |
| assertEquals("2010-07-08T02:44:48+0000", sdf.format(date)); |
| assertEquals(UTC, sdf.getTimeZone()); |
| } |
| |
| public void testDstZoneNameWithNonDstTimestamp() throws Exception { |
| SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm zzzz", Locale.US); |
| Calendar calendar = new GregorianCalendar(AMERICA_LOS_ANGELES); |
| calendar.setTime(format.parse("2011-06-21T10:00 Pacific Standard Time")); // 18:00 GMT-8 |
| assertEquals(11, calendar.get(Calendar.HOUR_OF_DAY)); // 18:00 GMT-7 |
| assertEquals(0, calendar.get(Calendar.MINUTE)); |
| } |
| |
| public void testNonDstZoneNameWithDstTimestamp() throws Exception { |
| SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm zzzz", Locale.US); |
| Calendar calendar = new GregorianCalendar(AMERICA_LOS_ANGELES); |
| calendar.setTime(format.parse("2010-12-21T10:00 Pacific Daylight Time")); // 17:00 GMT-7 |
| assertEquals(9, calendar.get(Calendar.HOUR_OF_DAY)); // 17:00 GMT-8 |
| assertEquals(0, calendar.get(Calendar.MINUTE)); |
| } |
| |
| // http://b/4723412 |
| public void testDstZoneWithNonDstTimestampForNonHourDstZone() throws Exception { |
| SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm zzzz", Locale.US); |
| Calendar calendar = new GregorianCalendar(AUSTRALIA_LORD_HOWE); |
| calendar.setTime(format.parse("2011-06-21T20:00 Lord Howe Daylight Time")); // 9:00 GMT+11 |
| assertEquals(19, calendar.get(Calendar.HOUR_OF_DAY)); // 9:00 GMT+10:30 |
| assertEquals(30, calendar.get(Calendar.MINUTE)); |
| } |
| |
| public void testNonDstZoneWithDstTimestampForNonHourDstZone() throws Exception { |
| SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm zzzz", Locale.US); |
| Calendar calendar = new GregorianCalendar(AUSTRALIA_LORD_HOWE); |
| calendar.setTime(format.parse("2010-12-21T19:30 Lord Howe Standard Time")); //9:00 GMT+10:30 |
| assertEquals(20, calendar.get(Calendar.HOUR_OF_DAY)); // 9:00 GMT+11:00 |
| assertEquals(0, calendar.get(Calendar.MINUTE)); |
| } |
| |
| public void testLocales() throws Exception { |
| // Just run through them all. Handy as a poor man's benchmark, and a sanity check. |
| for (Locale l : Locale.getAvailableLocales()) { |
| SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss zzzz", l); |
| sdf.format(new Date(0)); |
| } |
| } |
| |
| // http://code.google.com/p/android/issues/detail?id=14963 |
| public void testParseTimezoneOnly() throws Exception { |
| new SimpleDateFormat("z", Locale.FRANCE).parse("UTC"); |
| new SimpleDateFormat("z", Locale.US).parse("UTC"); |
| } |
| |
| // http://code.google.com/p/android/issues/detail?id=36689 |
| public void testParseArabic() throws Exception { |
| SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss", new Locale("ar", "EG")); |
| sdf.setTimeZone(AMERICA_LOS_ANGELES); |
| |
| // Can we parse an ASCII-formatted date in an Arabic locale? |
| Date d = sdf.parse("2012-08-29 10:02:45"); |
| assertEquals(1346259765000L, d.getTime()); |
| |
| // Can we format a date correctly in an Arabic locale? |
| String formatted = sdf.format(d); |
| assertEquals("٢٠١٢-٠٨-٢٩ ١٠:٠٢:٤٥", formatted); |
| |
| // Can we parse the Arabic-formatted date in an Arabic locale, and get the same date |
| // we started with? |
| Date d2 = sdf.parse(formatted); |
| assertEquals(d, d2); |
| } |
| |
| public void test_59383() throws Exception { |
| SimpleDateFormat sdf = new SimpleDateFormat("d. MMM yyyy H:mm", Locale.GERMAN); |
| sdf.setTimeZone(AMERICA_LOS_ANGELES); |
| assertEquals(1376927400000L, sdf.parse("19. Aug 2013 8:50").getTime()); |
| assertEquals(1376927400000L, sdf.parse("19. Aug. 2013 8:50").getTime()); |
| } |
| |
| // http://b/16969112 |
| public void test_fractionalSeconds() throws Exception { |
| SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.S"); |
| sdf.setTimeZone(UTC); |
| assertEquals("1970-01-02 02:17:36.7", sdf.format(sdf.parse("1970-01-02 02:17:36.7"))); |
| |
| // We only have millisecond precision for Date objects, so we'll lose |
| // information from the fractional seconds section of the string presentation. |
| sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSSSSS"); |
| sdf.setTimeZone(UTC); |
| assertEquals("1970-01-02 02:17:36.789000", sdf.format(sdf.parse("1970-01-02 02:17:36.789564"))); |
| } |
| |
| public void test_nullLocales() { |
| try { |
| SimpleDateFormat.getDateInstance(DateFormat.SHORT, null); |
| fail(); |
| } catch (NullPointerException expected) {} |
| |
| try { |
| SimpleDateFormat.getDateTimeInstance(DateFormat.SHORT, DateFormat.SHORT, null); |
| fail(); |
| } catch (NullPointerException expected) {} |
| |
| try { |
| SimpleDateFormat.getTimeInstance(DateFormat.SHORT, null); |
| fail(); |
| } catch (NullPointerException expected) {} |
| } |
| |
| // http://b/17431155 |
| public void test_sl_dates() throws Exception { |
| DateFormat df = DateFormat.getDateInstance(DateFormat.SHORT, new Locale("sl")); |
| assertEquals(TimeZone.getDefault(), df.getTimeZone()); |
| df.setTimeZone(UTC); |
| assertEquals("1. 1. 70", df.format(0L)); |
| } |
| |
| public void testLenientParsingForZ() throws Exception { |
| SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSSZ"); |
| Date date = sdf.parse("2016-01-06T23:05:49.480+00:00"); |
| Calendar calendar = Calendar.getInstance(UTC); |
| calendar.setTime(date); |
| assertEquals(11, calendar.get(Calendar.HOUR)); |
| assertEquals(5, calendar.get(Calendar.MINUTE)); |
| assertEquals(49, calendar.get(Calendar.SECOND)); |
| |
| Date date2 = sdf.parse("2016-01-06T23:05:49.480+00:00"); |
| assertEquals(date, date2); |
| |
| try { |
| date = sdf.parse("2016-01-06T23:05:49.480+00pissoff"); |
| fail(); |
| } catch (ParseException expected) { |
| } |
| |
| SimpleDateFormat sdf2 = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSSXXX"); |
| Date date3 = sdf2.parse("2016-01-06T23:05:49.480+00:00"); |
| assertEquals(date, date3); |
| try { |
| sdf2.parse("2016-01-06T23:05:49.480+0000"); |
| fail(); |
| } catch (ParseException expected) { |
| } |
| } |
| |
| // http://b/27760434 |
| public void testTimeZoneNotChangedByParse() throws Exception { |
| SimpleDateFormat df = new SimpleDateFormat("dd MMM yyyy HH:mm:ss zzz"); |
| df.setTimeZone(UTC); |
| df.parse("22 Jul 1977 12:23:45 HST"); |
| assertEquals(UTC, df.getTimeZone()); |
| } |
| |
| public void testZoneStringsUsedForParsingWhenPresent() throws ParseException { |
| DateFormatSymbols symbols = DateFormatSymbols.getInstance(Locale.ENGLISH); |
| String[][] zoneStrings = symbols.getZoneStrings(); |
| TimeZone tz = TimeZone.getTimeZone(zoneStrings[0][0]); |
| zoneStrings[0][1] = "CustomTimeZone"; |
| symbols.setZoneStrings(zoneStrings); |
| |
| SimpleDateFormat sdf = new SimpleDateFormat("dd MM yyyy HH:mm zzz", symbols); |
| |
| Date gmtDate = sdf.parse("1 1 2000 12:00 GMT"); |
| Date customDate = sdf.parse("1 1 2000 12:00 CustomTimeZone"); |
| assertEquals(tz.getOffset(gmtDate.getTime()), customDate.getTime() - gmtDate.getTime()); |
| } |
| |
| public void testTimeZoneFormattingRespectsSetZoneStrings() throws ParseException { |
| DateFormatSymbols symbols = DateFormatSymbols.getInstance(Locale.ENGLISH); |
| String[][] zoneStrings = symbols.getZoneStrings(); |
| TimeZone tz = TimeZone.getTimeZone(zoneStrings[0][0]); |
| String originalTzName = zoneStrings[0][1]; |
| symbols.setZoneStrings(zoneStrings); |
| SimpleDateFormat sdf = new SimpleDateFormat("zzzz", symbols); |
| sdf.setTimeZone(tz); |
| |
| // just re-setting the default values |
| assertEquals(originalTzName, sdf.format(new Date(1376927400000L))); |
| |
| // providing a custom name |
| zoneStrings[0][1] = "CustomTimeZone"; |
| symbols.setZoneStrings(zoneStrings); |
| sdf = new SimpleDateFormat("zzzz", symbols); |
| sdf.setTimeZone(tz); |
| assertEquals("CustomTimeZone", sdf.format(new Date(1376927400000L))); |
| |
| // setting the name to null should format as GMT[+-]... |
| zoneStrings[0][1] = null; |
| symbols.setZoneStrings(zoneStrings); |
| sdf = new SimpleDateFormat("zzzz", symbols); |
| sdf.setTimeZone(tz); |
| assertTrue(sdf.format(new Date(1376927400000L)).startsWith("GMT")); |
| } |
| |
| // http://b/30323478 |
| public void testStandaloneWeekdayParsing() throws Exception { |
| Locale fi = new Locale("fi"); // Finnish has separate standalone weekday names |
| // tiistaina = Tuesday (regular) |
| // tiistai = Tuesday (standalone) |
| assertEquals(Calendar.TUESDAY, |
| parseDateUtc(fi, "cccc yyyy", "tiistai 2000").get(Calendar.DAY_OF_WEEK)); |
| assertEquals(Calendar.TUESDAY, |
| parseDateUtc(fi, "EEEE yyyy", "tiistaina 2000").get(Calendar.DAY_OF_WEEK)); |
| assertCannotParse(fi, "cccc yyyy", "tiistaina 2000"); |
| assertCannotParse(fi, "EEEE yyyy", "tiistai 2000"); |
| } |
| |
| // http://b/30323478 |
| public void testStandaloneWeekdayFormatting() throws Exception { |
| Locale fi = new Locale("fi"); // Finnish has separate standalone weekday names |
| assertEquals("torstai", formatDateUtc(fi, "cccc")); |
| assertEquals("torstaina", formatDateUtc(fi, "EEEE")); |
| } |
| |
| public void testDayNumberOfWeek() throws Exception { |
| Locale en = Locale.ENGLISH; |
| Locale pl = new Locale("pl"); |
| |
| assertEquals("4", formatDateUtc(en, "u")); |
| assertEquals("04", formatDateUtc(en, "uu")); |
| assertEquals("4", formatDateUtc(pl, "u")); |
| assertEquals("04", formatDateUtc(pl, "uu")); |
| |
| assertEquals(Calendar.THURSDAY, parseDateUtc(en, "u", "4").get(Calendar.DAY_OF_WEEK)); |
| assertEquals(Calendar.MONDAY, parseDateUtc(en, "uu", "1").get(Calendar.DAY_OF_WEEK)); |
| } |
| |
| // Tests that Android's SimpleDateFormat provides localized short strings for UTC |
| // (http://b/36337342) i.e. it does not fall back to "GMT" or "GMT+00:00". |
| public void testFormatUtcShort() { |
| String timeZonePattern = "z"; |
| int timeZoneStyle = TimeZone.SHORT; |
| |
| doTestFormat(Locale.ENGLISH, timeZoneStyle, timeZonePattern, "UTC"); |
| doTestFormat(Locale.FRANCE, timeZoneStyle, timeZonePattern, "UTC"); |
| doTestFormat(Locale.SIMPLIFIED_CHINESE, timeZoneStyle, timeZonePattern, "UTC"); |
| } |
| |
| // Tests that Android's SimpleDateFormat provides localized long strings for UTC |
| // (http://b/36337342) |
| public void testFormatUtcLong() { |
| String timeZonePattern = "zzzz"; |
| int timeZoneStyle = TimeZone.LONG; |
| doTestFormat(Locale.ENGLISH, timeZoneStyle, timeZonePattern, "Coordinated Universal Time"); |
| doTestFormat(Locale.FRANCE, timeZoneStyle, timeZonePattern, "Temps universel coordonné"); |
| doTestFormat(Locale.SIMPLIFIED_CHINESE, timeZoneStyle, timeZonePattern, "协调世界时"); |
| } |
| |
| private static void doTestFormat(Locale locale, int timeZoneStyle, String timeZonePattern, |
| String expectedString) { |
| DateFormat dateFormat = new SimpleDateFormat(timeZonePattern, locale); |
| for (String timeZoneId : UTC_ZONE_IDS) { |
| TimeZone timeZone = TimeZone.getTimeZone(timeZoneId); |
| |
| // Confirm the time zone ID was recognized and we didn't just get "GMT". |
| assertEquals(timeZoneId, timeZone.getID()); |
| |
| dateFormat.setTimeZone(timeZone); |
| String timeZoneString = dateFormat.format(new Date(0)); |
| assertEquals(timeZone.getDisplayName( |
| false /* daylight */, timeZoneStyle, locale), timeZoneString); |
| |
| assertEquals(expectedString, timeZoneString); |
| } |
| } |
| |
| // Tests that Android's SimpleDateFormat can parse localized short strings for UTC |
| // (http://b/36337342) |
| public void testParseUtcShort() throws Exception { |
| String timeZonePattern = "z"; |
| int timeZoneStyle = TimeZone.SHORT; |
| doUtcParsingTest(Locale.ENGLISH, timeZonePattern, timeZoneStyle, "UTC"); |
| doUtcParsingTest(Locale.FRENCH, timeZonePattern, timeZoneStyle, "UTC"); |
| doUtcParsingTest(Locale.SIMPLIFIED_CHINESE, timeZonePattern, timeZoneStyle, "UTC"); |
| } |
| |
| // Tests that Android's SimpleDateFormat can parse localized long strings for UTC |
| // (http://b/36337342) |
| public void testParseUtcLong() throws Exception { |
| String timeZonePattern = "zzzz"; |
| int timeZoneStyle = TimeZone.LONG; |
| doUtcParsingTest(Locale.ENGLISH, timeZonePattern, timeZoneStyle, |
| "Coordinated Universal Time"); |
| doUtcParsingTest(Locale.FRENCH, timeZonePattern, timeZoneStyle, |
| "Temps universel coordonné"); |
| doUtcParsingTest(Locale.SIMPLIFIED_CHINESE, timeZonePattern, timeZoneStyle, |
| "协调世界时"); |
| } |
| |
| private static void doUtcParsingTest(Locale locale, String timeZonePattern, int timeZoneStyle, |
| String timeZoneString) throws Exception { |
| String basePattern = "yyyyMMdd HH:mm:ss.SSS"; |
| String fullPattern = basePattern + " " + timeZonePattern; |
| |
| TimeZone nonUtcZone = TimeZone.getTimeZone("America/Los_Angeles"); |
| |
| DateFormat formatter = new SimpleDateFormat(basePattern, locale); |
| DateFormat parser = new SimpleDateFormat(fullPattern, locale); |
| |
| for (String timeZoneId : UTC_ZONE_IDS) { |
| TimeZone timeZone = TimeZone.getTimeZone(timeZoneId); |
| |
| // Confirm the time zone ID was recognized and we didn't just get "GMT". |
| assertEquals(timeZoneId, timeZone.getID()); |
| |
| assertEquals(timeZoneString, |
| timeZone.getDisplayName(false /* daylight */, timeZoneStyle, locale)); |
| |
| // Format an arbitrary instant in the chosen time zone. We should get something like |
| // "20180126 13:23:34.456". |
| Date dateToFormat = new Date(); |
| |
| formatter.setTimeZone(timeZone); |
| String dateTimeString = formatter.format(dateToFormat); |
| |
| // Append the time zone. e.g. "20180126 13:23:34.456 Coordinated Universal Time". |
| String dateTimeStringWithTimeZone = dateTimeString + " " + timeZoneString; |
| |
| // Androidism: The formatter always resets the time zone of the formatter after parsing |
| // but we set it here to make it very clear the parser must be using a non-UTC time |
| // zone by default even though the string provides all the time zone information. |
| parser.setTimeZone(nonUtcZone); |
| |
| // Parse the date with time zone back, which should be interpreted as being in UTC. |
| Date parsedDate = parser.parse(dateTimeStringWithTimeZone); |
| |
| // The original instant should be returned, which means the formatter / parser were able |
| // to understand the time zone in the string. |
| assertEquals(dateToFormat, parsedDate); |
| } |
| } |
| |
| // http://b/35134326 |
| public void testTimeZoneParsingErrorIndex() { |
| SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy z", Locale.ENGLISH); |
| |
| checkTimeZoneParsingErrorIndex(dateFormat); |
| } |
| |
| // http://b/35134326 |
| public void testTimeZoneParsingErrorIndexWithZoneStrings() { |
| SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy z", Locale.ENGLISH); |
| // Force legacy code path by using zone strings. |
| DateFormatSymbols dfs = dateFormat.getDateFormatSymbols(); |
| dfs.setZoneStrings(dfs.getZoneStrings()); |
| dateFormat.setDateFormatSymbols(dfs); |
| |
| checkTimeZoneParsingErrorIndex(dateFormat); |
| } |
| |
| // Tests that 'b' and 'B' pattern symbols are silently ignored so that CLDR 32 patterns |
| // can be used. http://b/68139386 |
| public void testDayPeriodFormat() throws Exception { |
| SimpleDateFormat isoFormat = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss"); |
| isoFormat.setTimeZone(TimeZone.getTimeZone("UTC")); |
| Date date = isoFormat.parse("2017-01-01T08:00:00"); |
| |
| for (Locale locale : new Locale[] { Locale.US, Locale.FRANCE }) { |
| // Pattern letter 'b' |
| assertDayPeriodFormat("HHb", date, "08", locale); |
| assertDayPeriodFormat("HHbb", date, "08", locale); |
| assertDayPeriodFormat("HHbbb", date, "08", locale); |
| assertDayPeriodFormat("HHbbbb", date, "08", locale); |
| assertDayPeriodFormat("HHbbbbb", date, "08", locale); |
| |
| // Pattern letter 'B' |
| assertDayPeriodFormat("HHB", date, "08", locale); |
| assertDayPeriodFormat("HHBB", date, "08", locale); |
| assertDayPeriodFormat("HHBBB", date, "08", locale); |
| assertDayPeriodFormat("HHBBBB", date, "08", locale); |
| assertDayPeriodFormat("HHBBBBB", date, "08", locale); |
| } |
| } |
| |
| // Tests that SimpleDateFormat with 'b' and 'B' pattern symbols can't parse any date |
| public void testDayPeriodParse() { |
| assertDayPeriodParseFailure("b", ""); |
| assertDayPeriodParseFailure("HHb", "1"); |
| assertDayPeriodParseFailure("HHb", "12"); |
| assertDayPeriodParseFailure("HH b", "12 AM"); |
| assertDayPeriodParseFailure("HH b", "12 midnight"); |
| |
| assertDayPeriodParseFailure("B", ""); |
| assertDayPeriodParseFailure("HHB", "8"); |
| assertDayPeriodParseFailure("HHB", "08"); |
| assertDayPeriodParseFailure("HH B", "08 AM"); |
| assertDayPeriodParseFailure("HH B", "08 in the morning"); |
| } |
| |
| private void assertDayPeriodParseFailure(String pattern, String source) { |
| SimpleDateFormat simpleDateFormat = new SimpleDateFormat(pattern, Locale.US); |
| ParsePosition parsePosition = new ParsePosition(0); |
| Date d = simpleDateFormat.parse(source, parsePosition); |
| assertNull(d); |
| assertEquals(0, parsePosition.getIndex()); |
| } |
| |
| private void assertDayPeriodFormat(String pattern, Date date, String expected, Locale locale) { |
| SimpleDateFormat simpleDateFormat = new SimpleDateFormat(pattern, locale); |
| simpleDateFormat.setTimeZone(TimeZone.getTimeZone("UTC")); |
| assertEquals(expected, simpleDateFormat.format(date)); |
| } |
| |
| private void checkTimeZoneParsingErrorIndex(SimpleDateFormat dateFormat) { |
| ParsePosition pos = new ParsePosition(0); |
| Date parsed; |
| parsed = dateFormat.parse("2000 foobar", pos); |
| assertNull(parsed); |
| assertEquals("Wrong error index", 5, pos.getErrorIndex()); |
| } |
| |
| // http://b/38396219 |
| public void testDisplayNamesOnNonGregorianCalendar() { |
| assertEquals("Jan", formatDateNonGregorianCalendar("MMM")); // MONTH |
| assertEquals("Jan", formatDateNonGregorianCalendar("LLL")); // MONTH_STANDALONE |
| assertEquals("Thu", formatDateNonGregorianCalendar("EEE")); // DAY_OF_WEEK |
| assertEquals("Thu", formatDateNonGregorianCalendar("ccc")); // STANDALONE_DAY_OF_WEEK |
| } |
| |
| /** |
| * Format a date using a "non-gregorian" calendar. This means that we use a calendar that is not |
| * exactly {@code java.util.GregorianCalendar} as checked by |
| * {@link SimpleDateFormat#isGregorianCalendar()}. |
| */ |
| private static String formatDateNonGregorianCalendar(String fmt) { |
| DateFormat dateFormat = new SimpleDateFormat(fmt, Locale.US); |
| NonGregorianCalendar cal = new NonGregorianCalendar(); |
| cal.clear(); |
| cal.setTimeZone(UTC); |
| dateFormat.setCalendar(cal); |
| return dateFormat.format(new Date(0)); |
| } |
| |
| /** |
| * Calendar that pretends that it's not a GregorianCalendar, for {@link |
| * #testDisplayNamesOnNonGregorianCalendar()}. |
| */ |
| private static class NonGregorianCalendar extends GregorianCalendar { |
| } |
| } |