Home | History | Annotate | Download | only in text
      1 /*
      2  * Copyright (C) 2010 The Android Open Source Project
      3  *
      4  * Licensed under the Apache License, Version 2.0 (the "License");
      5  * you may not use this file except in compliance with the License.
      6  * You may obtain a copy of the License at
      7  *
      8  *      http://www.apache.org/licenses/LICENSE-2.0
      9  *
     10  * Unless required by applicable law or agreed to in writing, software
     11  * distributed under the License is distributed on an "AS IS" BASIS,
     12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     13  * See the License for the specific language governing permissions and
     14  * limitations under the License.
     15  */
     16 
     17 package libcore.java.text;
     18 
     19 import java.text.DateFormat;
     20 import java.text.DateFormatSymbols;
     21 import java.text.ParseException;
     22 import java.text.ParsePosition;
     23 import java.text.SimpleDateFormat;
     24 import java.util.Calendar;
     25 import java.util.Date;
     26 import java.util.GregorianCalendar;
     27 import java.util.Locale;
     28 import java.util.TimeZone;
     29 
     30 public class SimpleDateFormatTest extends junit.framework.TestCase {
     31 
     32     private static final TimeZone AMERICA_LOS_ANGELES = TimeZone.getTimeZone("America/Los_Angeles");
     33     private static final TimeZone AUSTRALIA_LORD_HOWE = TimeZone.getTimeZone("Australia/Lord_Howe");
     34     private static final TimeZone UTC = TimeZone.getTimeZone("Etc/UTC");
     35 
     36     /**
     37      * The list of time zone ids formatted as "UTC".
     38      */
     39     private static final String[] UTC_ZONE_IDS = new String[] {
     40             "Etc/UCT", "Etc/UTC", "Etc/Universal", "Etc/Zulu", "UCT", "UTC", "Universal", "Zulu"
     41     };
     42 
     43 
     44     private Locale defaultLocale;
     45 
     46     @Override
     47     public void setUp() throws Exception {
     48         super.setUp();
     49         defaultLocale = Locale.getDefault();
     50         // Locale affects timezone names / abbreviations so can affect formatting and parsing.
     51         Locale.setDefault(Locale.US);
     52     }
     53 
     54     @Override
     55     public void tearDown() throws Exception {
     56         Locale.setDefault(defaultLocale);
     57         super.tearDown();
     58     }
     59 
     60     public void testDefaultConstructor_localeUS() {
     61         SimpleDateFormat sdf = new SimpleDateFormat();
     62         sdf.setTimeZone(UTC);
     63         assertEquals("M/d/yy h:mm a", sdf.toPattern());
     64         assertEquals("1/1/70 12:00 AM", sdf.format(new Date(0)));
     65     }
     66 
     67     // The RI fails this test.
     68     public void test2DigitYearStartIsCloned() throws Exception {
     69         // Test that get2DigitYearStart returns a clone.
     70         SimpleDateFormat sdf = new SimpleDateFormat();
     71         sdf.setTimeZone(UTC);
     72 
     73         Date originalDate = sdf.get2DigitYearStart();
     74         assertNotSame(sdf.get2DigitYearStart(), originalDate);
     75         assertEquals(sdf.get2DigitYearStart(), originalDate);
     76         originalDate.setTime(0);
     77         assertFalse(sdf.get2DigitYearStart().equals(originalDate));
     78         // Test that set2DigitYearStart takes a clone.
     79         Date newDate = new Date();
     80         sdf.set2DigitYearStart(newDate);
     81         assertNotSame(sdf.get2DigitYearStart(), newDate);
     82         assertEquals(sdf.get2DigitYearStart(), newDate);
     83         newDate.setTime(0);
     84         assertFalse(sdf.get2DigitYearStart().equals(newDate));
     85     }
     86 
     87     // The RI fails this test because this is an ICU-compatible Android extension.
     88     // Necessary for correct localization in various languages (http://b/2633414).
     89     public void testStandAloneNames() throws Exception {
     90         Locale en = Locale.ENGLISH;
     91         Locale pl = new Locale("pl");
     92         Locale ru = new Locale("ru");
     93 
     94         assertEquals("January", formatDateUtc(en, "MMMM"));
     95         assertEquals("January", formatDateUtc(en, "LLLL"));
     96         assertEquals("stycznia", formatDateUtc(pl, "MMMM"));
     97         assertEquals("stycze\u0144", formatDateUtc(pl, "LLLL"));
     98 
     99         assertEquals("Thursday", formatDateUtc(en, "EEEE"));
    100         assertEquals("Thursday", formatDateUtc(en, "cccc"));
    101         assertEquals("\u0447\u0435\u0442\u0432\u0435\u0440\u0433", formatDateUtc(ru, "EEEE"));
    102         assertEquals("\u0447\u0435\u0442\u0432\u0435\u0440\u0433", formatDateUtc(ru, "cccc"));
    103 
    104         assertEquals(Calendar.JUNE, parseDateUtc(en, "yyyy-MMMM-dd", "1980-June-12").get(Calendar.MONTH));
    105         assertEquals(Calendar.JUNE, parseDateUtc(en, "yyyy-LLLL-dd", "1980-June-12").get(Calendar.MONTH));
    106         assertEquals(Calendar.JUNE, parseDateUtc(pl, "yyyy-MMMM-dd", "1980-czerwca-12").get(Calendar.MONTH));
    107         assertEquals(Calendar.JUNE, parseDateUtc(pl, "yyyy-LLLL-dd", "1980-czerwiec-12").get(Calendar.MONTH));
    108 
    109         assertEquals(Calendar.TUESDAY, parseDateUtc(en, "EEEE", "Tuesday").get(Calendar.DAY_OF_WEEK));
    110         assertEquals(Calendar.TUESDAY, parseDateUtc(en, "cccc", "Tuesday").get(Calendar.DAY_OF_WEEK));
    111         assertEquals(Calendar.TUESDAY, parseDateUtc(ru, "EEEE", "\u0432\u0442\u043e\u0440\u043d\u0438\u043a").get(Calendar.DAY_OF_WEEK));
    112         assertEquals(Calendar.TUESDAY, parseDateUtc(ru, "cccc", "\u0412\u0442\u043e\u0440\u043d\u0438\u043a").get(Calendar.DAY_OF_WEEK));
    113     }
    114 
    115     // The RI fails this test because it doesn't fully support UTS #35.
    116     // https://code.google.com/p/android/issues/detail?id=39616
    117     public void testFiveCount_parsing() throws Exception {
    118       // It's pretty silly to try to parse the shortest names, because they're almost always
    119       // ambiguous.
    120       assertCannotParse(Locale.ENGLISH, "MMMMM", "J");
    121       assertCannotParse(Locale.ENGLISH, "LLLLL", "J");
    122       assertCannotParse(Locale.ENGLISH, "EEEEE", "T");
    123       assertCannotParse(Locale.ENGLISH, "ccccc", "T");
    124     }
    125 
    126     // The RI fails this test because it doesn't fully support UTS #35.
    127     // https://code.google.com/p/android/issues/detail?id=39616
    128     public void testFiveCount_M() throws Exception {
    129       assertEquals("1", formatDateUtc(Locale.ENGLISH, "M"));
    130       assertEquals("01", formatDateUtc(Locale.ENGLISH, "MM"));
    131       assertEquals("Jan", formatDateUtc(Locale.ENGLISH, "MMM"));
    132       assertEquals("January", formatDateUtc(Locale.ENGLISH, "MMMM"));
    133       assertEquals("J", formatDateUtc(Locale.ENGLISH, "MMMMM"));
    134     }
    135 
    136     // The RI fails this test because it doesn't fully support UTS #35.
    137     // https://code.google.com/p/android/issues/detail?id=39616
    138     public void testFiveCount_L() throws Exception {
    139       assertEquals("1", formatDateUtc(Locale.ENGLISH, "L"));
    140       assertEquals("01", formatDateUtc(Locale.ENGLISH, "LL"));
    141       assertEquals("Jan", formatDateUtc(Locale.ENGLISH, "LLL"));
    142       assertEquals("January", formatDateUtc(Locale.ENGLISH, "LLLL"));
    143       assertEquals("J", formatDateUtc(Locale.ENGLISH, "LLLLL"));
    144     }
    145 
    146     // The RI fails this test because it doesn't fully support UTS #35.
    147     // https://code.google.com/p/android/issues/detail?id=39616
    148     public void testFiveCount_E() throws Exception {
    149       assertEquals("Thu", formatDateUtc(Locale.ENGLISH, "E"));
    150       assertEquals("Thu", formatDateUtc(Locale.ENGLISH, "EE"));
    151       assertEquals("Thu", formatDateUtc(Locale.ENGLISH, "EEE"));
    152       assertEquals("Thursday", formatDateUtc(Locale.ENGLISH, "EEEE"));
    153       assertEquals("T", formatDateUtc(Locale.ENGLISH, "EEEEE"));
    154       // assertEquals("Th", formatDate(Locale.ENGLISH, "EEEEEE")); // icu4c doesn't support 6.
    155     }
    156 
    157     // The RI fails this test because it doesn't fully support UTS #35.
    158     // https://code.google.com/p/android/issues/detail?id=39616
    159     public void testFiveCount_c() throws Exception {
    160       assertEquals("Thu", formatDateUtc(Locale.ENGLISH, "c"));
    161       assertEquals("Thu", formatDateUtc(Locale.ENGLISH, "cc"));
    162       assertEquals("Thu", formatDateUtc(Locale.ENGLISH, "ccc"));
    163       assertEquals("Thursday", formatDateUtc(Locale.ENGLISH, "cccc"));
    164       assertEquals("T", formatDateUtc(Locale.ENGLISH, "ccccc"));
    165       // assertEquals("Th", formatDate(Locale.ENGLISH, "cccccc")); // icu4c doesn't support 6.
    166     }
    167 
    168     // The RI fails this test because it doesn't fully support UTS #35.
    169     // https://code.google.com/p/android/issues/detail?id=39616
    170     public void testFiveCount_Z() throws Exception {
    171       assertEquals("+0000", formatDateUtc(Locale.ENGLISH, "Z"));
    172       assertEquals("+0000", formatDateUtc(Locale.ENGLISH, "ZZ"));
    173       assertEquals("+0000", formatDateUtc(Locale.ENGLISH, "ZZZ"));
    174       assertEquals("GMT+00:00", formatDateUtc(Locale.ENGLISH, "ZZZZ"));
    175       assertEquals("+00:00", formatDateUtc(Locale.ENGLISH, "ZZZZZ"));
    176 
    177       TimeZone tz = AMERICA_LOS_ANGELES;
    178       assertEquals("-0800", formatDate(Locale.ENGLISH, "Z", tz));
    179       assertEquals("-0800", formatDate(Locale.ENGLISH, "ZZ", tz));
    180       assertEquals("-0800", formatDate(Locale.ENGLISH, "ZZZ", tz));
    181       assertEquals("GMT-08:00", formatDate(Locale.ENGLISH, "ZZZZ", tz));
    182       assertEquals("-08:00", formatDate(Locale.ENGLISH, "ZZZZZ", tz));
    183     }
    184 
    185     // The RI fails this test because it doesn't fully support UTS #35.
    186     // https://code.google.com/p/android/issues/detail?id=39616
    187     public void test_parsing_Z() throws Exception {
    188       assertEquals(1325421240000L, parseTimeUtc("yyyy-MM-dd' 'Z", "2012-01-01 -1234"));
    189       assertEquals(1325421240000L, parseTimeUtc("yyyy-MM-dd' 'ZZ", "2012-01-01 -1234"));
    190       assertEquals(1325421240000L, parseTimeUtc("yyyy-MM-dd' 'ZZZ", "2012-01-01 -1234"));
    191       assertEquals(1325421240000L, parseTimeUtc("yyyy-MM-dd' 'ZZZZ", "2012-01-01 GMT-12:34"));
    192       assertEquals(1325421240000L, parseTimeUtc("yyyy-MM-dd' 'ZZZZZ", "2012-01-01 -12:34"));
    193     }
    194 
    195     private static long parseTimeUtc(String fmt, String value) {
    196       return parseDateUtc(Locale.ENGLISH, fmt, value).getTime().getTime();
    197     }
    198 
    199     public void test2038() {
    200         SimpleDateFormat format = new SimpleDateFormat("EEE MMM dd HH:mm:ss yyyy", Locale.US);
    201         format.setTimeZone(UTC);
    202 
    203         assertEquals("Sun Nov 24 17:31:44 1833",
    204                 format.format(new Date(((long) Integer.MIN_VALUE + Integer.MIN_VALUE) * 1000L)));
    205         assertEquals("Fri Dec 13 20:45:52 1901",
    206                 format.format(new Date(Integer.MIN_VALUE * 1000L)));
    207         assertEquals("Thu Jan 01 00:00:00 1970",
    208                 format.format(new Date(0L)));
    209         assertEquals("Tue Jan 19 03:14:07 2038",
    210                 format.format(new Date(Integer.MAX_VALUE * 1000L)));
    211         assertEquals("Sun Feb 07 06:28:16 2106",
    212                 format.format(new Date((2L + Integer.MAX_VALUE + Integer.MAX_VALUE) * 1000L)));
    213     }
    214 
    215     private String formatDateUtc(Locale l, String fmt) {
    216         return formatDate(l, fmt, UTC);
    217     }
    218 
    219     private String formatDate(Locale l, String fmt, TimeZone tz) {
    220         DateFormat dateFormat = new SimpleDateFormat(fmt, l);
    221         dateFormat.setTimeZone(tz);
    222         return dateFormat.format(new Date(0));
    223     }
    224 
    225     private static void assertCannotParse(Locale l, String fmt, String value) {
    226         SimpleDateFormat sdf = new SimpleDateFormat(fmt, l);
    227         sdf.setTimeZone(UTC);
    228         ParsePosition pp = new ParsePosition(0);
    229         Date d = sdf.parse(value, pp);
    230         assertNull("Value " + value + " must not parse in locale " + l + " with format " + fmt, d);
    231     }
    232 
    233     /**
    234      * Parse a date with a SimpleDateFormat set to use UTC. If fmt contains a pattern for zone the
    235      * use of UTC should have no effect, but in other cases it can affect the outcome. The returned
    236      * calendar will also be set to UTC.
    237      */
    238     private static Calendar parseDateUtc(Locale l, String fmt, String value) {
    239         return parseDate(l, fmt, value, UTC);
    240     }
    241 
    242     private static Calendar parseDate(Locale l, String fmt, String value, TimeZone tz) {
    243         SimpleDateFormat sdf = new SimpleDateFormat(fmt, l);
    244         sdf.setTimeZone(tz);
    245         ParsePosition pp = new ParsePosition(0);
    246         Date d = sdf.parse(value, pp);
    247         if (d == null) {
    248             fail(pp.toString());
    249         }
    250         if (pp.getIndex() != value.length()) {
    251             fail("Value " + value + " must be fully consumed: " +  pp.toString());
    252         }
    253         Calendar c = Calendar.getInstance(tz);
    254         c.setTime(d);
    255         return c;
    256     }
    257 
    258     // http://code.google.com/p/android/issues/detail?id=13420
    259     public void testParsingUncommonTimeZoneAbbreviations() {
    260         String fmt = "yyyy-MM-dd HH:mm:ss.SSS z";
    261         String date = "2010-12-23 12:44:57.0 CET";
    262         // ICU considers "CET" (Central European Time) to be common in Britain...
    263         assertEquals(1293104697000L, parseDateUtc(Locale.UK, fmt, date).getTimeInMillis());
    264         // ...but not in the US.
    265         assertCannotParse(Locale.US, fmt, date);
    266     }
    267 
    268     // In Honeycomb, only one Olson id was associated with CET (or any other "uncommon"
    269     // abbreviation). This was changed after KitKat to avoid Java hacks on top of ICU data.
    270     // ICU data only provides abbreviations for timezones in the locales where they would
    271     // not be ambiguous to most people of that locale.
    272     public void testFormattingUncommonTimeZoneAbbreviations() {
    273         String fmt = "yyyy-MM-dd HH:mm:ss.SSS z";
    274         String unambiguousDate = "1970-01-01 01:00:00.000 CET";
    275         String ambiguousDate = "1970-01-01 01:00:00.000 GMT+01:00";
    276 
    277         // The locale to use when formatting. Not every Locale renders "Europe/Berlin" as "CET". The
    278         // UK is one that does, the US is one that does not.
    279         Locale cetUnambiguousLocale = Locale.UK;
    280         Locale cetAmbiguousLocale = Locale.US;
    281         TimeZone europeBerlin = TimeZone.getTimeZone("Europe/Berlin");
    282         TimeZone europeZurich = TimeZone.getTimeZone("Europe/Zurich");
    283 
    284         SimpleDateFormat sdf = new SimpleDateFormat(fmt, cetUnambiguousLocale);
    285         sdf.setTimeZone(europeBerlin);
    286         assertEquals(unambiguousDate, sdf.format(new Date(0)));
    287         sdf = new SimpleDateFormat(fmt, cetUnambiguousLocale);
    288         sdf.setTimeZone(europeZurich);
    289         assertEquals(unambiguousDate, sdf.format(new Date(0)));
    290 
    291         sdf = new SimpleDateFormat(fmt, cetAmbiguousLocale);
    292         sdf.setTimeZone(europeBerlin);
    293         assertEquals(ambiguousDate, sdf.format(new Date(0)));
    294         sdf = new SimpleDateFormat(fmt, cetAmbiguousLocale);
    295         sdf.setTimeZone(europeZurich);
    296         assertEquals(ambiguousDate, sdf.format(new Date(0)));
    297     }
    298 
    299     // http://code.google.com/p/android/issues/detail?id=8258
    300     public void testTimeZoneFormatting() throws Exception {
    301         Date epoch = new Date(0);
    302 
    303         // Create a SimpleDateFormat that defaults to America/Chicago...
    304         TimeZone americaChicago = TimeZone.getTimeZone("America/Chicago");
    305         TimeZone.setDefault(americaChicago);
    306         SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss Z");
    307         assertEquals(americaChicago, sdf.getTimeZone());
    308 
    309         // We should see something appropriate to America/Chicago...
    310         assertEquals("1969-12-31 18:00:00 -0600", sdf.format(epoch));
    311         // We can set any TimeZone we want:
    312         sdf.setTimeZone(AMERICA_LOS_ANGELES);
    313         assertEquals("1969-12-31 16:00:00 -0800", sdf.format(epoch));
    314         sdf.setTimeZone(UTC);
    315         assertEquals("1970-01-01 00:00:00 +0000", sdf.format(epoch));
    316 
    317         // A new SimpleDateFormat will default to America/Chicago...
    318         sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss Z");
    319         assertEquals(americaChicago, sdf.getTimeZone());
    320 
    321         // ...and parsing an America/Los_Angeles time will *not* change that...
    322         sdf.parse("2010-12-03 00:00:00 -0800");
    323         assertEquals(americaChicago, sdf.getTimeZone());
    324 
    325         // ...so our time zone here is "America/Chicago":
    326         assertEquals("1969-12-31 18:00:00 -0600", sdf.format(epoch));
    327         // We can set any TimeZone we want:
    328         sdf.setTimeZone(AMERICA_LOS_ANGELES);
    329         assertEquals("1969-12-31 16:00:00 -0800", sdf.format(epoch));
    330         sdf.setTimeZone(UTC);
    331         assertEquals("1970-01-01 00:00:00 +0000", sdf.format(epoch));
    332 
    333         sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
    334         sdf.setTimeZone(UTC);
    335         Date date = sdf.parse("2010-07-08 02:44:48");
    336         assertEquals(UTC, sdf.getTimeZone());
    337         assertEquals(1278557088000L, date.getTime());
    338 
    339         sdf = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ssZ");
    340         sdf.setTimeZone(AMERICA_LOS_ANGELES);
    341         assertEquals("2010-07-07T19:44:48-0700", sdf.format(date));
    342         assertEquals(AMERICA_LOS_ANGELES, sdf.getTimeZone());
    343         sdf.setTimeZone(UTC);
    344         assertEquals("2010-07-08T02:44:48+0000", sdf.format(date));
    345         assertEquals(UTC, sdf.getTimeZone());
    346     }
    347 
    348     public void testDstZoneNameWithNonDstTimestamp() throws Exception {
    349         SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm zzzz", Locale.US);
    350         Calendar calendar = new GregorianCalendar(AMERICA_LOS_ANGELES);
    351         calendar.setTime(format.parse("2011-06-21T10:00 Pacific Standard Time")); // 18:00 GMT-8
    352         assertEquals(11, calendar.get(Calendar.HOUR_OF_DAY)); // 18:00 GMT-7
    353         assertEquals(0, calendar.get(Calendar.MINUTE));
    354     }
    355 
    356     public void testNonDstZoneNameWithDstTimestamp() throws Exception {
    357         SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm zzzz", Locale.US);
    358         Calendar calendar = new GregorianCalendar(AMERICA_LOS_ANGELES);
    359         calendar.setTime(format.parse("2010-12-21T10:00 Pacific Daylight Time")); // 17:00 GMT-7
    360         assertEquals(9, calendar.get(Calendar.HOUR_OF_DAY)); // 17:00 GMT-8
    361         assertEquals(0, calendar.get(Calendar.MINUTE));
    362     }
    363 
    364     // http://b/4723412
    365     public void testDstZoneWithNonDstTimestampForNonHourDstZone() throws Exception {
    366         SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm zzzz", Locale.US);
    367         Calendar calendar = new GregorianCalendar(AUSTRALIA_LORD_HOWE);
    368         calendar.setTime(format.parse("2011-06-21T20:00 Lord Howe Daylight Time")); // 9:00 GMT+11
    369         assertEquals(19, calendar.get(Calendar.HOUR_OF_DAY)); // 9:00 GMT+10:30
    370         assertEquals(30, calendar.get(Calendar.MINUTE));
    371     }
    372 
    373     public void testNonDstZoneWithDstTimestampForNonHourDstZone() throws Exception {
    374         SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm zzzz", Locale.US);
    375         Calendar calendar = new GregorianCalendar(AUSTRALIA_LORD_HOWE);
    376         calendar.setTime(format.parse("2010-12-21T19:30 Lord Howe Standard Time")); //9:00 GMT+10:30
    377         assertEquals(20, calendar.get(Calendar.HOUR_OF_DAY)); // 9:00 GMT+11:00
    378         assertEquals(0, calendar.get(Calendar.MINUTE));
    379     }
    380 
    381     public void testLocales() throws Exception {
    382         // Just run through them all. Handy as a poor man's benchmark, and a sanity check.
    383         for (Locale l : Locale.getAvailableLocales()) {
    384             SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss zzzz", l);
    385             sdf.format(new Date(0));
    386         }
    387     }
    388 
    389     // http://code.google.com/p/android/issues/detail?id=14963
    390     public void testParseTimezoneOnly() throws Exception {
    391         new SimpleDateFormat("z", Locale.FRANCE).parse("UTC");
    392         new SimpleDateFormat("z", Locale.US).parse("UTC");
    393     }
    394 
    395     // http://code.google.com/p/android/issues/detail?id=36689
    396     public void testParseArabic() throws Exception {
    397         SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss", new Locale("ar", "EG"));
    398         sdf.setTimeZone(AMERICA_LOS_ANGELES);
    399 
    400         // Can we parse an ASCII-formatted date in an Arabic locale?
    401         Date d = sdf.parse("2012-08-29 10:02:45");
    402         assertEquals(1346259765000L, d.getTime());
    403 
    404         // Can we format a date correctly in an Arabic locale?
    405         String formatted = sdf.format(d);
    406         assertEquals("-- ::", formatted);
    407 
    408         // Can we parse the Arabic-formatted date in an Arabic locale, and get the same date
    409         // we started with?
    410         Date d2 = sdf.parse(formatted);
    411         assertEquals(d, d2);
    412     }
    413 
    414     public void test_59383() throws Exception {
    415         SimpleDateFormat sdf = new SimpleDateFormat("d. MMM yyyy H:mm", Locale.GERMAN);
    416         sdf.setTimeZone(AMERICA_LOS_ANGELES);
    417         assertEquals(1376927400000L, sdf.parse("19. Aug 2013 8:50").getTime());
    418         assertEquals(1376927400000L, sdf.parse("19. Aug. 2013 8:50").getTime());
    419     }
    420 
    421     // http://b/16969112
    422     public void test_fractionalSeconds() throws Exception {
    423         SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.S");
    424         sdf.setTimeZone(UTC);
    425         assertEquals("1970-01-02 02:17:36.7", sdf.format(sdf.parse("1970-01-02 02:17:36.7")));
    426 
    427         // We only have millisecond precision for Date objects, so we'll lose
    428         // information from the fractional seconds section of the string presentation.
    429         sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSSSSS");
    430         sdf.setTimeZone(UTC);
    431         assertEquals("1970-01-02 02:17:36.789000", sdf.format(sdf.parse("1970-01-02 02:17:36.789564")));
    432     }
    433 
    434     public void test_nullLocales() {
    435         try {
    436             SimpleDateFormat.getDateInstance(DateFormat.SHORT, null);
    437             fail();
    438         } catch (NullPointerException expected) {}
    439 
    440         try {
    441             SimpleDateFormat.getDateTimeInstance(DateFormat.SHORT, DateFormat.SHORT, null);
    442             fail();
    443         } catch (NullPointerException expected) {}
    444 
    445         try {
    446             SimpleDateFormat.getTimeInstance(DateFormat.SHORT, null);
    447             fail();
    448         } catch (NullPointerException expected) {}
    449     }
    450 
    451     // http://b/17431155
    452     public void test_sl_dates() throws Exception {
    453         DateFormat df = DateFormat.getDateInstance(DateFormat.SHORT, new Locale("sl"));
    454         assertEquals(TimeZone.getDefault(), df.getTimeZone());
    455         df.setTimeZone(UTC);
    456         assertEquals("1. 1. 70", df.format(0L));
    457     }
    458 
    459     public void testLenientParsingForZ() throws Exception {
    460         SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSSZ");
    461         Date date = sdf.parse("2016-01-06T23:05:49.480+00:00");
    462         Calendar calendar = Calendar.getInstance(UTC);
    463         calendar.setTime(date);
    464         assertEquals(11, calendar.get(Calendar.HOUR));
    465         assertEquals(5, calendar.get(Calendar.MINUTE));
    466         assertEquals(49, calendar.get(Calendar.SECOND));
    467 
    468         Date date2 = sdf.parse("2016-01-06T23:05:49.480+00:00");
    469         assertEquals(date, date2);
    470 
    471         try {
    472             date = sdf.parse("2016-01-06T23:05:49.480+00pissoff");
    473             fail();
    474         } catch (ParseException expected) {
    475         }
    476 
    477         SimpleDateFormat sdf2 = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSSXXX");
    478         Date date3 = sdf2.parse("2016-01-06T23:05:49.480+00:00");
    479         assertEquals(date, date3);
    480         try {
    481             sdf2.parse("2016-01-06T23:05:49.480+0000");
    482             fail();
    483         } catch (ParseException expected) {
    484         }
    485     }
    486 
    487     // http://b/27760434
    488     public void testTimeZoneNotChangedByParse() throws Exception {
    489         SimpleDateFormat df = new SimpleDateFormat("dd MMM yyyy HH:mm:ss zzz");
    490         df.setTimeZone(UTC);
    491         df.parse("22 Jul 1977 12:23:45 HST");
    492         assertEquals(UTC, df.getTimeZone());
    493     }
    494 
    495     public void testZoneStringsUsedForParsingWhenPresent() throws ParseException {
    496         DateFormatSymbols symbols = DateFormatSymbols.getInstance(Locale.ENGLISH);
    497         String[][] zoneStrings = symbols.getZoneStrings();
    498         TimeZone tz = TimeZone.getTimeZone(zoneStrings[0][0]);
    499         zoneStrings[0][1] = "CustomTimeZone";
    500         symbols.setZoneStrings(zoneStrings);
    501 
    502         SimpleDateFormat sdf = new SimpleDateFormat("dd MM yyyy HH:mm zzz", symbols);
    503 
    504         Date gmtDate = sdf.parse("1 1 2000 12:00 GMT");
    505         Date customDate = sdf.parse("1 1 2000 12:00 CustomTimeZone");
    506         assertEquals(tz.getOffset(gmtDate.getTime()), customDate.getTime() - gmtDate.getTime());
    507     }
    508 
    509     public void testTimeZoneFormattingRespectsSetZoneStrings() throws ParseException {
    510         DateFormatSymbols symbols = DateFormatSymbols.getInstance(Locale.ENGLISH);
    511         String[][] zoneStrings = symbols.getZoneStrings();
    512         TimeZone tz = TimeZone.getTimeZone(zoneStrings[0][0]);
    513         String originalTzName = zoneStrings[0][1];
    514         symbols.setZoneStrings(zoneStrings);
    515         SimpleDateFormat sdf = new SimpleDateFormat("zzzz", symbols);
    516         sdf.setTimeZone(tz);
    517 
    518         // just re-setting the default values
    519         assertEquals(originalTzName, sdf.format(new Date(1376927400000L)));
    520 
    521         // providing a custom name
    522         zoneStrings[0][1] = "CustomTimeZone";
    523         symbols.setZoneStrings(zoneStrings);
    524         sdf = new SimpleDateFormat("zzzz", symbols);
    525         sdf.setTimeZone(tz);
    526         assertEquals("CustomTimeZone", sdf.format(new Date(1376927400000L)));
    527 
    528         // setting the name to null should format as GMT[+-]...
    529         zoneStrings[0][1] = null;
    530         symbols.setZoneStrings(zoneStrings);
    531         sdf = new SimpleDateFormat("zzzz", symbols);
    532         sdf.setTimeZone(tz);
    533         assertTrue(sdf.format(new Date(1376927400000L)).startsWith("GMT"));
    534     }
    535 
    536     // http://b/30323478
    537     public void testStandaloneWeekdayParsing() throws Exception {
    538         Locale fi = new Locale("fi"); // Finnish has separate standalone weekday names
    539         // tiistaina = Tuesday (regular)
    540         // tiistai = Tuesday (standalone)
    541         assertEquals(Calendar.TUESDAY,
    542                 parseDateUtc(fi, "cccc yyyy", "tiistai 2000").get(Calendar.DAY_OF_WEEK));
    543         assertEquals(Calendar.TUESDAY,
    544                 parseDateUtc(fi, "EEEE yyyy", "tiistaina 2000").get(Calendar.DAY_OF_WEEK));
    545         assertCannotParse(fi, "cccc yyyy", "tiistaina 2000");
    546         assertCannotParse(fi, "EEEE yyyy", "tiistai 2000");
    547     }
    548 
    549     // http://b/30323478
    550     public void testStandaloneWeekdayFormatting() throws Exception {
    551         Locale fi = new Locale("fi"); // Finnish has separate standalone weekday names
    552         assertEquals("torstai", formatDateUtc(fi, "cccc"));
    553         assertEquals("torstaina", formatDateUtc(fi, "EEEE"));
    554     }
    555 
    556     public void testDayNumberOfWeek() throws Exception {
    557         Locale en = Locale.ENGLISH;
    558         Locale pl = new Locale("pl");
    559 
    560         assertEquals("4", formatDateUtc(en, "u"));
    561         assertEquals("04", formatDateUtc(en, "uu"));
    562         assertEquals("4", formatDateUtc(pl, "u"));
    563         assertEquals("04", formatDateUtc(pl, "uu"));
    564 
    565         assertEquals(Calendar.THURSDAY, parseDateUtc(en, "u", "4").get(Calendar.DAY_OF_WEEK));
    566         assertEquals(Calendar.MONDAY, parseDateUtc(en, "uu", "1").get(Calendar.DAY_OF_WEEK));
    567     }
    568 
    569     // Tests that Android's SimpleDateFormat provides localized short strings for UTC
    570     // (http://b/36337342) i.e. it does not fall back to "GMT" or "GMT+00:00".
    571     public void testFormatUtcShort() {
    572         String timeZonePattern = "z";
    573         int timeZoneStyle = TimeZone.SHORT;
    574 
    575         doTestFormat(Locale.ENGLISH, timeZoneStyle, timeZonePattern, "UTC");
    576         doTestFormat(Locale.FRANCE, timeZoneStyle, timeZonePattern, "UTC");
    577         doTestFormat(Locale.SIMPLIFIED_CHINESE, timeZoneStyle, timeZonePattern, "UTC");
    578     }
    579 
    580     // Tests that Android's SimpleDateFormat provides localized long strings for UTC
    581     // (http://b/36337342)
    582     public void testFormatUtcLong() {
    583         String timeZonePattern = "zzzz";
    584         int timeZoneStyle = TimeZone.LONG;
    585         doTestFormat(Locale.ENGLISH, timeZoneStyle, timeZonePattern, "Coordinated Universal Time");
    586         doTestFormat(Locale.FRANCE, timeZoneStyle, timeZonePattern, "Temps universel coordonn");
    587         doTestFormat(Locale.SIMPLIFIED_CHINESE, timeZoneStyle, timeZonePattern, "");
    588     }
    589 
    590     private static void doTestFormat(Locale locale, int timeZoneStyle, String timeZonePattern,
    591             String expectedString) {
    592         DateFormat dateFormat = new SimpleDateFormat(timeZonePattern, locale);
    593         for (String timeZoneId : UTC_ZONE_IDS) {
    594             TimeZone timeZone = TimeZone.getTimeZone(timeZoneId);
    595 
    596             // Confirm the time zone ID was recognized and we didn't just get "GMT".
    597             assertEquals(timeZoneId, timeZone.getID());
    598 
    599             dateFormat.setTimeZone(timeZone);
    600             String timeZoneString = dateFormat.format(new Date(0));
    601             assertEquals(timeZone.getDisplayName(
    602                     false /* daylight */, timeZoneStyle, locale), timeZoneString);
    603 
    604             assertEquals(expectedString, timeZoneString);
    605         }
    606     }
    607 
    608     // Tests that Android's SimpleDateFormat can parse localized short strings for UTC
    609     // (http://b/36337342)
    610     public void testParseUtcShort() throws Exception {
    611         String timeZonePattern = "z";
    612         int timeZoneStyle = TimeZone.SHORT;
    613         doUtcParsingTest(Locale.ENGLISH, timeZonePattern, timeZoneStyle, "UTC");
    614         doUtcParsingTest(Locale.FRENCH, timeZonePattern, timeZoneStyle, "UTC");
    615         doUtcParsingTest(Locale.SIMPLIFIED_CHINESE, timeZonePattern, timeZoneStyle, "UTC");
    616     }
    617 
    618     // Tests that Android's SimpleDateFormat can parse localized long strings for UTC
    619     // (http://b/36337342)
    620     public void testParseUtcLong() throws Exception {
    621         String timeZonePattern = "zzzz";
    622         int timeZoneStyle = TimeZone.LONG;
    623         doUtcParsingTest(Locale.ENGLISH, timeZonePattern, timeZoneStyle,
    624                 "Coordinated Universal Time");
    625         doUtcParsingTest(Locale.FRENCH, timeZonePattern, timeZoneStyle,
    626                 "Temps universel coordonn");
    627         doUtcParsingTest(Locale.SIMPLIFIED_CHINESE, timeZonePattern, timeZoneStyle,
    628                 "");
    629     }
    630 
    631     private static void doUtcParsingTest(Locale locale, String timeZonePattern, int timeZoneStyle,
    632             String timeZoneString) throws Exception {
    633         String basePattern = "yyyyMMdd HH:mm:ss.SSS";
    634         String fullPattern = basePattern + " " + timeZonePattern;
    635 
    636         TimeZone nonUtcZone = TimeZone.getTimeZone("America/Los_Angeles");
    637 
    638         DateFormat formatter = new SimpleDateFormat(basePattern, locale);
    639         DateFormat parser = new SimpleDateFormat(fullPattern, locale);
    640 
    641         for (String timeZoneId : UTC_ZONE_IDS) {
    642             TimeZone timeZone = TimeZone.getTimeZone(timeZoneId);
    643 
    644             // Confirm the time zone ID was recognized and we didn't just get "GMT".
    645             assertEquals(timeZoneId, timeZone.getID());
    646 
    647             assertEquals(timeZoneString,
    648                     timeZone.getDisplayName(false /* daylight */, timeZoneStyle, locale));
    649 
    650             // Format an arbitrary instant in the chosen time zone. We should get something like
    651             // "20180126 13:23:34.456".
    652             Date dateToFormat = new Date();
    653 
    654             formatter.setTimeZone(timeZone);
    655             String dateTimeString = formatter.format(dateToFormat);
    656 
    657             // Append the time zone. e.g. "20180126 13:23:34.456 Coordinated Universal Time".
    658             String dateTimeStringWithTimeZone = dateTimeString + " " + timeZoneString;
    659 
    660             // Androidism: The formatter always resets the time zone of the formatter after parsing
    661             // but we set it here to make it very clear the parser must be using a non-UTC time
    662             // zone by default even though the string provides all the time zone information.
    663             parser.setTimeZone(nonUtcZone);
    664 
    665             // Parse the date with time zone back, which should be interpreted as being in UTC.
    666             Date parsedDate = parser.parse(dateTimeStringWithTimeZone);
    667 
    668             // The original instant should be returned, which means the formatter / parser were able
    669             // to understand the time zone in the string.
    670             assertEquals(dateToFormat, parsedDate);
    671         }
    672     }
    673 
    674     // http://b/35134326
    675     public void testTimeZoneParsingErrorIndex() {
    676         SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy z", Locale.ENGLISH);
    677 
    678         checkTimeZoneParsingErrorIndex(dateFormat);
    679     }
    680 
    681     // http://b/35134326
    682     public void testTimeZoneParsingErrorIndexWithZoneStrings() {
    683         SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy z", Locale.ENGLISH);
    684         // Force legacy code path by using zone strings.
    685         DateFormatSymbols dfs = dateFormat.getDateFormatSymbols();
    686         dfs.setZoneStrings(dfs.getZoneStrings());
    687         dateFormat.setDateFormatSymbols(dfs);
    688 
    689         checkTimeZoneParsingErrorIndex(dateFormat);
    690     }
    691 
    692     // Tests that 'b' and 'B' pattern symbols are silently ignored so that CLDR 32 patterns
    693     // can be used. http://b/68139386
    694     public void testDayPeriodFormat() throws Exception {
    695         SimpleDateFormat isoFormat = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss");
    696         isoFormat.setTimeZone(TimeZone.getTimeZone("UTC"));
    697         Date date = isoFormat.parse("2017-01-01T08:00:00");
    698 
    699         for (Locale locale : new Locale[] { Locale.US, Locale.FRANCE }) {
    700             // Pattern letter 'b'
    701             assertDayPeriodFormat("HHb", date, "08", locale);
    702             assertDayPeriodFormat("HHbb", date, "08", locale);
    703             assertDayPeriodFormat("HHbbb", date, "08", locale);
    704             assertDayPeriodFormat("HHbbbb", date, "08", locale);
    705             assertDayPeriodFormat("HHbbbbb", date, "08", locale);
    706 
    707             // Pattern letter 'B'
    708             assertDayPeriodFormat("HHB", date, "08", locale);
    709             assertDayPeriodFormat("HHBB", date, "08", locale);
    710             assertDayPeriodFormat("HHBBB", date, "08", locale);
    711             assertDayPeriodFormat("HHBBBB", date, "08", locale);
    712             assertDayPeriodFormat("HHBBBBB", date, "08", locale);
    713         }
    714     }
    715 
    716     // Tests that SimpleDateFormat with 'b' and 'B' pattern symbols can't parse any date
    717     public void testDayPeriodParse() {
    718         assertDayPeriodParseFailure("b", "");
    719         assertDayPeriodParseFailure("HHb", "1");
    720         assertDayPeriodParseFailure("HHb", "12");
    721         assertDayPeriodParseFailure("HH b", "12 AM");
    722         assertDayPeriodParseFailure("HH b", "12 midnight");
    723 
    724         assertDayPeriodParseFailure("B", "");
    725         assertDayPeriodParseFailure("HHB", "8");
    726         assertDayPeriodParseFailure("HHB", "08");
    727         assertDayPeriodParseFailure("HH B", "08 AM");
    728         assertDayPeriodParseFailure("HH B", "08 in the morning");
    729     }
    730 
    731     private void assertDayPeriodParseFailure(String pattern, String source) {
    732         SimpleDateFormat simpleDateFormat = new SimpleDateFormat(pattern, Locale.US);
    733         ParsePosition parsePosition = new ParsePosition(0);
    734         Date d = simpleDateFormat.parse(source, parsePosition);
    735         assertNull(d);
    736         assertEquals(0, parsePosition.getIndex());
    737     }
    738 
    739     private void assertDayPeriodFormat(String pattern, Date date, String expected, Locale locale) {
    740         SimpleDateFormat simpleDateFormat = new SimpleDateFormat(pattern, locale);
    741         simpleDateFormat.setTimeZone(TimeZone.getTimeZone("UTC"));
    742         assertEquals(expected, simpleDateFormat.format(date));
    743     }
    744 
    745     private void checkTimeZoneParsingErrorIndex(SimpleDateFormat dateFormat) {
    746         ParsePosition pos = new ParsePosition(0);
    747         Date parsed;
    748         parsed = dateFormat.parse("2000 foobar", pos);
    749         assertNull(parsed);
    750         assertEquals("Wrong error index", 5, pos.getErrorIndex());
    751     }
    752 
    753     // http://b/38396219
    754     public void testDisplayNamesOnNonGregorianCalendar() {
    755         assertEquals("Jan", formatDateNonGregorianCalendar("MMM")); // MONTH
    756         assertEquals("Jan", formatDateNonGregorianCalendar("LLL")); // MONTH_STANDALONE
    757         assertEquals("Thu", formatDateNonGregorianCalendar("EEE")); // DAY_OF_WEEK
    758         assertEquals("Thu", formatDateNonGregorianCalendar("ccc")); // STANDALONE_DAY_OF_WEEK
    759     }
    760 
    761     /**
    762      * Format a date using a "non-gregorian" calendar. This means that we use a calendar that is not
    763      * exactly {@code java.util.GregorianCalendar} as checked by
    764      * {@link SimpleDateFormat#isGregorianCalendar()}.
    765      */
    766     private static String formatDateNonGregorianCalendar(String fmt) {
    767         DateFormat dateFormat = new SimpleDateFormat(fmt, Locale.US);
    768         NonGregorianCalendar cal = new NonGregorianCalendar();
    769         cal.clear();
    770         cal.setTimeZone(UTC);
    771         dateFormat.setCalendar(cal);
    772         return dateFormat.format(new Date(0));
    773     }
    774 
    775     /**
    776      * Calendar that pretends that it's not a GregorianCalendar, for {@link
    777      * #testDisplayNamesOnNonGregorianCalendar()}.
    778      */
    779     private static class NonGregorianCalendar extends GregorianCalendar {
    780     }
    781 }
    782