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