1/* 2 * Copyright (C) 2013 Google Inc. 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); you may not 5 * use this file except in compliance with the License. You may obtain a copy of 6 * 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, WITHOUT 12 * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 13 * License for the specific language governing permissions and limitations under 14 * the License. 15 */ 16 17package libcore.java.util; 18 19import java.time.Instant; 20import java.time.LocalDateTime; 21import java.time.ZoneId; 22import java.time.ZonedDateTime; 23import java.util.Calendar; 24import java.util.Date; 25import java.util.GregorianCalendar; 26import java.util.Locale; 27import java.util.SimpleTimeZone; 28import java.util.TimeZone; 29import junit.framework.TestCase; 30 31public class GregorianCalendarTest extends TestCase { 32 33 private static final TimeZone LOS_ANGELES = TimeZone.getTimeZone("America/Los_Angeles"); 34 35 private static final TimeZone LONDON = TimeZone.getTimeZone("Europe/London"); 36 37 private static final int HOUR_IN_MILLIS = 3600000; 38 39 private static final SimpleTimeZone CUSTOM_LOS_ANGELES_TIME_ZONE = new SimpleTimeZone(-28800000, 40 "Custom America/Los_Angeles", 41 Calendar.MARCH, 9, 0, hours(2), 42 Calendar.NOVEMBER, 2, 0, hours(2), 43 hours(1)); 44 45 private Locale defaultLocale; 46 47 @Override 48 public void setUp() throws Exception { 49 super.setUp(); 50 defaultLocale = Locale.getDefault(); 51 // Most tests are locale independent, but locale does affect start-of-week. 52 Locale.setDefault(Locale.US); 53 } 54 55 @Override 56 public void tearDown() throws Exception { 57 Locale.setDefault(defaultLocale); 58 super.tearDown(); 59 } 60 61 // Documented a previous difference in behavior between this and the RI, see 62 // https://code.google.com/p/android/issues/detail?id=61993 for more details. 63 // Switching to OpenJDK has fixed that issue and so this test has been changed to reflect 64 // the correct behavior. 65 public void test_computeFields_dayOfWeekAndWeekOfYearSet() { 66 Calendar greg = new GregorianCalendar(LOS_ANGELES, Locale.ENGLISH); 67 68 // Ensure we use different values to the default ones. 69 int differentWeekOfYear = greg.get(Calendar.WEEK_OF_YEAR) == 1 ? 2 : 1; 70 int differentDayOfWeek = greg.get(Calendar.DAY_OF_WEEK) == Calendar.MONDAY 71 ? Calendar.TUESDAY : Calendar.MONDAY; 72 73 // Setting WEEK_OF_YEAR and DAY_OF_WEEK with an intervening 74 // call to computeFields will work. 75 greg.set(Calendar.WEEK_OF_YEAR, differentWeekOfYear); 76 assertEquals(differentWeekOfYear, greg.get(Calendar.WEEK_OF_YEAR)); 77 greg.set(Calendar.DAY_OF_WEEK, differentDayOfWeek); 78 assertEquals(differentWeekOfYear, greg.get(Calendar.WEEK_OF_YEAR)); 79 80 // Setting WEEK_OF_YEAR after DAY_OF_WEEK with no intervening 81 // call to computeFields will work. 82 greg = new GregorianCalendar(LOS_ANGELES, Locale.ENGLISH); 83 greg.set(Calendar.DAY_OF_WEEK, differentDayOfWeek); 84 greg.set(Calendar.WEEK_OF_YEAR, differentWeekOfYear); 85 assertEquals(differentWeekOfYear, greg.get(Calendar.WEEK_OF_YEAR)); 86 assertEquals(differentDayOfWeek, greg.get(Calendar.DAY_OF_WEEK)); 87 88 // Setting DAY_OF_WEEK after WEEK_OF_YEAR with no intervening 89 // call to computeFields will work. 90 greg = new GregorianCalendar(LOS_ANGELES, Locale.ENGLISH); 91 greg.set(Calendar.WEEK_OF_YEAR, differentWeekOfYear); 92 greg.set(Calendar.DAY_OF_WEEK, differentDayOfWeek); 93 assertEquals(differentWeekOfYear, greg.get(Calendar.WEEK_OF_YEAR)); 94 assertEquals(differentDayOfWeek, greg.get(Calendar.DAY_OF_WEEK)); 95 } 96 97 /** 98 * Specialized tests for those fields affected by GregorianCalendar cut over date. 99 * 100 * <p>Expands on a regression test created for harmony-2947. 101 */ 102 public void test_fieldsAffectedByGregorianCutOver() { 103 104 Date date = new Date(Date.parse("Jan 1 00:00:01 GMT 2000")); 105 assertEquals(946684801000L, date.getTime()); 106 107 GregorianCalendar gc; 108 109 // Test in America/Los_Angeles 110 gc = new GregorianCalendar(LOS_ANGELES, Locale.ENGLISH); 111 gc.setGregorianChange(date); 112 gc.setTime(date); 113 114 // Check the date to ensure that it is 18th Dec 1999. The reason this is not 1st Jan 2000 115 // is that the offset for Los Angeles is GMT-08:00. setGregorianChange() is interpreted as 116 // a wall time, not an instant. So, the instant that corresponds to 117 // "1st Jan 2000 00:00:01 GMT" is 8 hours before the Julian/Gregorian switch in LA. The day 118 // before 1st Jan 2000 (Gregorian Calendar) would be 18th Dec 1999 in the Julian calendar. 119 // The reason it is not the 31st Dec 1999 is simply a result of the discontinuity that 120 // occurred when switching calendars. That happened for real when the calendars were 121 // switched in 1582. 122 // 123 // A different year explains why we get very different results for the methods being tested. 124 assertEquals(1999, gc.get(Calendar.YEAR)); 125 assertEquals(Calendar.DECEMBER, gc.get(Calendar.MONTH)); 126 assertEquals(18, gc.get(Calendar.DAY_OF_MONTH)); 127 128 assertEquals(50, gc.getActualMaximum(Calendar.WEEK_OF_YEAR)); 129 assertEquals(50, gc.getLeastMaximum(Calendar.WEEK_OF_YEAR)); 130 assertEquals(3, gc.getActualMaximum(Calendar.WEEK_OF_MONTH)); 131 assertEquals(3, gc.getLeastMaximum(Calendar.WEEK_OF_MONTH)); 132 assertEquals(18, gc.getActualMaximum(Calendar.DAY_OF_MONTH)); 133 assertEquals(18, gc.getLeastMaximum(Calendar.DAY_OF_MONTH)); 134 assertEquals(352, gc.getActualMaximum(Calendar.DAY_OF_YEAR)); 135 assertEquals(352, gc.getLeastMaximum(Calendar.DAY_OF_YEAR)); 136 assertEquals(3, gc.getActualMaximum(Calendar.DAY_OF_WEEK_IN_MONTH)); 137 assertEquals(3, gc.getLeastMaximum(Calendar.DAY_OF_WEEK_IN_MONTH)); 138 139 // Test in Europe/London 140 gc = new GregorianCalendar(LONDON, Locale.ENGLISH); 141 gc.setGregorianChange(date); 142 gc.setTime(date); 143 144 // Check the date is actually 1st Jan 2000. 145 assertEquals(2000, gc.get(Calendar.YEAR)); 146 assertEquals(Calendar.JANUARY, gc.get(Calendar.MONTH)); 147 assertEquals(1, gc.get(Calendar.DAY_OF_MONTH)); 148 149 assertEquals(53, gc.getActualMaximum(Calendar.WEEK_OF_YEAR)); 150 assertEquals(52, gc.getLeastMaximum(Calendar.WEEK_OF_YEAR)); 151 assertEquals(5, gc.getActualMaximum(Calendar.WEEK_OF_MONTH)); 152 assertEquals(4, gc.getLeastMaximum(Calendar.WEEK_OF_MONTH)); 153 assertEquals(31, gc.getActualMaximum(Calendar.DAY_OF_MONTH)); 154 assertEquals(28, gc.getLeastMaximum(Calendar.DAY_OF_MONTH)); 155 assertEquals(366, gc.getActualMaximum(Calendar.DAY_OF_YEAR)); 156 assertEquals(365, gc.getLeastMaximum(Calendar.DAY_OF_YEAR)); 157 assertEquals(5, gc.getActualMaximum(Calendar.DAY_OF_WEEK_IN_MONTH)); 158 assertEquals(4, gc.getLeastMaximum(Calendar.DAY_OF_WEEK_IN_MONTH)); 159 } 160 161 public void test_computeTime_enteringDst_TimeZone_LosAngeles_2014() { 162 TimeZone timeZone = TimeZone.getTimeZone("America/Los_Angeles"); 163 checkDstLosAngeles2014(timeZone); 164 } 165 166 /** 167 * This test will fail in the RI. 168 * 169 * <p>The AOSP behavior is different for backwards compatibility with previous versions of 170 * Android. 171 * 172 * <p>Search in this file for 'OpenJDK Failure' to see more details. 173 */ 174 public void test_computeTime_enteringDst_DelegatingTimeZone_LosAngeles_2014() { 175 TimeZone timeZone = TimeZone.getTimeZone("America/Los_Angeles"); 176 timeZone = new DelegatingTimeZone(timeZone); 177 checkDstLosAngeles2014(timeZone); 178 } 179 180 /** 181 * This test will fail in the RI. 182 * 183 * <p>The AOSP behavior is different for backwards compatibility with previous versions of 184 * Android. 185 * 186 * <p>Search in this file for 'OpenJDK Failure' to see more details. 187 */ 188 public void test_computeTime_enteringDst_SimpleTimeZone_LosAngeles_2014() { 189 checkDstLosAngeles2014(CUSTOM_LOS_ANGELES_TIME_ZONE); 190 } 191 192 public void test_computeTime_enteringDst() { 193 // Get the DST entry time with a ZoneInfo implementation of TimeZone. 194 TimeZone zoneInfo = TimeZone.getTimeZone("America/Los_Angeles"); 195 long zoneInfoTime = getDstLosAngeles2014(zoneInfo); 196 197 // Check that the time is correct. 198 assertTrue(zoneInfo.inDaylightTime(new Date(zoneInfoTime))); 199 assertFalse(zoneInfo.inDaylightTime(new Date(zoneInfoTime - 1))); 200 201 // Get the DST entry time with a SimpleTimeZone implementation of TimeZone. 202 SimpleTimeZone simpleTimeZone = new SimpleTimeZone(-28800000, 203 "Custom America/Los_Angeles", 204 Calendar.MARCH, 9, 0, 7200000, 205 Calendar.NOVEMBER, 2, 0, 7200000, 206 3600000); 207 long simpleTimeZoneTime = getDstLosAngeles2014(simpleTimeZone); 208 209 } 210 211 public void test_isWeekDateSupported() { 212 assertTrue(new GregorianCalendar().isWeekDateSupported()); 213 } 214 215 public void test_setWeekDate() { 216 GregorianCalendar cal = new GregorianCalendar(); 217 218 // When first week should have at least 4 days 219 cal.setMinimalDaysInFirstWeek(4); 220 cal.setWeekDate(2016, 13, Calendar.TUESDAY); 221 assertEquals(Calendar.TUESDAY, cal.get(Calendar.DAY_OF_WEEK)); 222 assertEquals(29, cal.get(Calendar.DAY_OF_MONTH)); 223 224 // When first week can have single day 225 cal.setMinimalDaysInFirstWeek(1); 226 cal.setWeekDate(2016, 13, Calendar.TUESDAY); 227 assertEquals(Calendar.TUESDAY, cal.get(Calendar.DAY_OF_WEEK)); 228 assertEquals(22, cal.get(Calendar.DAY_OF_MONTH)); 229 230 try { 231 cal.setWeekDate(2016, 13, Calendar.SATURDAY + 1); 232 fail(); 233 } catch (IllegalArgumentException expected) { 234 } 235 236 try { 237 cal.setWeekDate(2016, 13, Calendar.SUNDAY - 1); 238 fail(); 239 } catch (IllegalArgumentException expected) { 240 } 241 242 // in non-lenient mode 243 cal.setLenient(false); 244 try { 245 cal.setWeekDate(2016, 60, Calendar.SATURDAY); 246 fail(); 247 } catch (IllegalArgumentException expected) {} 248 249 try { 250 cal.setWeekDate(-1, 60, Calendar.SATURDAY); 251 fail(); 252 } catch (IllegalArgumentException expected) {} 253 } 254 255 public void test_getWeekYear() { 256 GregorianCalendar cal = new GregorianCalendar(); 257 258 cal.set(2016, Calendar.MARCH, 29); 259 assertEquals(2016, cal.getWeekYear()); 260 261 // With minimal days in first week is set to 4 262 cal.setMinimalDaysInFirstWeek(4); 263 cal.set(2016, Calendar.JANUARY, 1); 264 assertEquals(2015, cal.getWeekYear()); 265 cal.set(2015, Calendar.DECEMBER, 31); 266 assertEquals(2015, cal.getWeekYear()); 267 268 // With minimal days in first week is set to 1 269 cal.setMinimalDaysInFirstWeek(1); 270 cal.set(2016, Calendar.JANUARY, 1); 271 assertEquals(2016, cal.getWeekYear()); 272 cal.set(2015, Calendar.DECEMBER, 31); 273 assertEquals(2016, cal.getWeekYear()); 274 } 275 276 public void test_getWeeksInWeekYear() { 277 GregorianCalendar cal = new GregorianCalendar(); 278 279 // With minimal days in first week is set to 1 280 cal.setMinimalDaysInFirstWeek(1); 281 cal.set(2016, Calendar.JANUARY, 10); 282 assertEquals(53, cal.getWeeksInWeekYear()); 283 284 // With minimal days in first week is set to 4 285 cal.setMinimalDaysInFirstWeek(4); 286 cal.set(2016, Calendar.JANUARY, 10); 287 assertEquals(52, cal.getWeeksInWeekYear()); 288 } 289 290 public void test_fromZonedDateTime() { 291 ZonedDateTime zdt = ZonedDateTime.parse("2007-12-03T10:15:30+01:00[Europe/Paris]"); 292 GregorianCalendar calendar = GregorianCalendar.from(zdt); 293 TimeZone timeZone = TimeZone.getTimeZone("Europe/Paris"); 294 assertEquals(timeZone, calendar.getTimeZone()); 295 assertEquals(2007, calendar.get(Calendar.YEAR)); 296 assertEquals(Calendar.DECEMBER, calendar.get(Calendar.MONTH)); 297 assertEquals(3, calendar.get(Calendar.DAY_OF_MONTH)); 298 assertEquals(10, calendar.get(Calendar.HOUR_OF_DAY)); 299 assertEquals(15, calendar.get(Calendar.MINUTE)); 300 assertEquals(3600 * 1000, calendar.getTimeZone().getRawOffset()); // in milliseconds 301 } 302 303 public void test_fromZonedDateTime_invalidValues() { 304 ZoneId gmt = ZoneId.of("GMT"); 305 ZonedDateTime[] invalidValues = { 306 ZonedDateTime.of(LocalDateTime.MAX, gmt), 307 ZonedDateTime.ofInstant(Instant.ofEpochMilli(Long.MAX_VALUE).plusMillis(1), gmt), 308 ZonedDateTime.ofInstant(Instant.ofEpochMilli(Long.MIN_VALUE).minusMillis(1), gmt), 309 ZonedDateTime.of(LocalDateTime.MAX, gmt) }; 310 for (ZonedDateTime invalidValue : invalidValues) { 311 try { 312 GregorianCalendar.from(invalidValue); 313 fail("GregorianCalendar.from() should have failed with " + invalidValue); 314 } catch (IllegalArgumentException expected) {} 315 } 316 } 317 318 public void test_toZonedDateTime() { 319 TimeZone timeZone = TimeZone.getTimeZone("Europe/Paris"); 320 GregorianCalendar calendar = new GregorianCalendar(timeZone); 321 calendar.set(2007, Calendar.DECEMBER, 3, 10, 15, 30); 322 calendar.set(Calendar.MILLISECOND, 0); 323 ZonedDateTime zdt = calendar.toZonedDateTime(); 324 assertEquals(ZonedDateTime.parse("2007-12-03T10:15:30+01:00[Europe/Paris]"), zdt); 325 } 326 327 private long getDstLosAngeles2014(TimeZone timeZone) { 328 GregorianCalendar cal = new GregorianCalendar(timeZone, Locale.ENGLISH); 329 cal.set(Calendar.MILLISECOND, 0); 330 cal.set(2014, Calendar.MARCH, 9, 2, 0, 0); 331 332 return cal.getTimeInMillis(); 333 } 334 335 private void checkDstLosAngeles2014(TimeZone timeZone) { 336 Calendar cal = new GregorianCalendar(timeZone, Locale.ENGLISH); 337 // Clear the milliseconds field. 338 cal.set(Calendar.MILLISECOND, 0); 339 340 String description; 341 342 // Check milliseconds one second before the transition. 343 description = "01:59:59 - March 9th 2014"; 344 cal.set(2014, Calendar.MARCH, 9, 1, 59, 59); 345 checkMillis(cal, description, 1394359199000L); 346 347 // Outside DST time. 348 checkOutsideDst(cal, description); 349 350 // Check milliseconds at the transition point but using an invalid wall clock 351 // (02:00 - 02:59:59.999) do not actually exist. 352 description = "02:00:00 - March 9th 2014"; 353 cal.set(2014, Calendar.MARCH, 9, 2, 0, 0); 354 355 // OpenJDK Failure: 356 // This fails on OpenJDK when running with SimpleTimeZone (or any custom TimeZone 357 // implementation). It incorrectly calculates the time in millis to be 1394355600000. 358 // That is because GregorianCalendar treats the implementation that underpins 359 // TimeZone.getTimeZone(String) specially and the code that runs for other classes does 360 // not handle the invalid wall clock period on entry to DST properly. 361 checkMillis(cal, description, 1394359200000L); 362 363 // Invalid wall clock but treated as being inside DST time. 364 checkInsideDst(cal, description); 365 366 // Check milliseconds at the first valid wall clock time after transition, 03:00 - should 367 // be treated the same as 02:00. 368 description = "03:00:00 - March 9th 2014"; 369 cal.set(2014, Calendar.MARCH, 9, 3, 0, 0); 370 checkMillis(cal, description, 1394359200000L); 371 372 // Valid wall clock treated as being inside DST time. 373 checkInsideDst(cal, description); 374 375 // Check milliseconds at the last invalid wall clock time, 02:59:59.999. 376 description = "02:59:59.999 - March 9th 2014"; 377 cal.set(2014, Calendar.MARCH, 9, 2, 59, 59); 378 cal.set(Calendar.MILLISECOND, 999); 379 checkMillis(cal, description, 1394362799999L); 380 381 // Invalid wall clock but treated as being inside DST time. 382 checkInsideDst(cal, description); 383 384 // Check milliseconds at 03:59:59.999 - should be treated the same as 02:59:59.999 385 description = "03:59:59.999 - March 9th 2014"; 386 cal.set(2014, Calendar.MARCH, 9, 3, 59, 59); 387 cal.set(Calendar.MILLISECOND, 999); 388 checkMillis(cal, description, 1394362799999L); 389 390 // Valid wall clock treated as being inside DST time. 391 checkInsideDst(cal, description); 392 } 393 394 private void checkMillis(Calendar cal, String description, long expectedMillis) { 395 assertEquals("Incorrect millis: " + description, expectedMillis, cal.getTimeInMillis()); 396 } 397 398 private void checkOutsideDst(Calendar cal, String description) { 399 TimeZone timeZone = cal.getTimeZone(); 400 checkOutsideDst(cal, description, timeZone.getRawOffset()); 401 } 402 403 private void checkOutsideDst(Calendar cal, String description, int expectedZoneOffset) { 404 checkDstFields(cal, description, expectedZoneOffset, 0); 405 } 406 407 private void checkInsideDst(Calendar cal, String description) { 408 TimeZone timeZone = cal.getTimeZone(); 409 checkDstFields(cal, description, timeZone.getRawOffset(), timeZone.getDSTSavings()); 410 } 411 412 private void checkDstFields(Calendar cal, String description, int expectedZoneOffset, int expectedDstOffset) { 413 assertEquals("Incorrect ZONE_OFFSET: " + description, expectedZoneOffset, cal.get(Calendar.ZONE_OFFSET)); 414 assertEquals("Incorrect DST_OFFSET: " + description, expectedDstOffset, cal.get(Calendar.DST_OFFSET)); 415 } 416 417 /** 418 * A custom {@link TimeZone} implementation. 419 * 420 * <p>Used to show the behavior of {@link GregorianCalendar} when provided with a custom 421 * implementation of {@link TimeZone}, i.e. one that is unknown to the runtime, 422 */ 423 private static class DelegatingTimeZone extends TimeZone { 424 425 private final TimeZone timeZone; 426 427 public DelegatingTimeZone(TimeZone timeZone) { 428 this.timeZone = timeZone; 429 } 430 431 @Override 432 public int getOffset(int era, int year, int month, int day, int dayOfWeek, 433 int milliseconds) { 434 return timeZone.getOffset(era, year, month, day, dayOfWeek, milliseconds); 435 } 436 437 @Override 438 public int getOffset(long date) { 439 return timeZone.getOffset(date); 440 } 441 442 @Override 443 public void setRawOffset(int offsetMillis) { 444 timeZone.setRawOffset(offsetMillis); 445 } 446 447 @Override 448 public int getRawOffset() { 449 return timeZone.getRawOffset(); 450 } 451 452 @Override 453 public String getID() { 454 return timeZone.getID(); 455 } 456 457 @Override 458 public void setID(String ID) { 459 timeZone.setID(ID); 460 } 461 462 @Override 463 public String getDisplayName(boolean daylightTime, int style, Locale locale) { 464 return timeZone.getDisplayName(daylightTime, style, locale); 465 } 466 467 @Override 468 public int getDSTSavings() { 469 return timeZone.getDSTSavings(); 470 } 471 472 @Override 473 public boolean useDaylightTime() { 474 return timeZone.useDaylightTime(); 475 } 476 477 @Override 478 public boolean observesDaylightTime() { 479 return timeZone.observesDaylightTime(); 480 } 481 482 @Override 483 public boolean inDaylightTime(Date date) { 484 return timeZone.inDaylightTime(date); 485 } 486 487 @Override 488 public boolean hasSameRules(TimeZone other) { 489 return timeZone.hasSameRules(other); 490 } 491 } 492 493 private static int hours(int count) { 494 return HOUR_IN_MILLIS * count; 495 } 496} 497