blob: 3345ed527fa5eb0ad8d2e393c0f6690fdedc6cea [file] [log] [blame]
/*
* Copyright (C) 2013 Google Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not
* use this file except in compliance with the License. You may obtain a copy of
* the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations under
* the License.
*/
package libcore.java.util;
import java.time.Instant;
import java.time.LocalDateTime;
import java.time.ZoneId;
import java.time.ZonedDateTime;
import java.util.Calendar;
import java.util.Date;
import java.util.GregorianCalendar;
import java.util.Locale;
import java.util.SimpleTimeZone;
import java.util.TimeZone;
import junit.framework.TestCase;
public class GregorianCalendarTest extends TestCase {
private static final TimeZone LOS_ANGELES = TimeZone.getTimeZone("America/Los_Angeles");
private static final TimeZone LONDON = TimeZone.getTimeZone("Europe/London");
private static final int HOUR_IN_MILLIS = 3600000;
private static final SimpleTimeZone CUSTOM_LOS_ANGELES_TIME_ZONE = new SimpleTimeZone(-28800000,
"Custom America/Los_Angeles",
Calendar.MARCH, 9, 0, hours(2),
Calendar.NOVEMBER, 2, 0, hours(2),
hours(1));
private Locale defaultLocale;
@Override
public void setUp() throws Exception {
super.setUp();
defaultLocale = Locale.getDefault();
// Most tests are locale independent, but locale does affect start-of-week.
Locale.setDefault(Locale.US);
}
@Override
public void tearDown() throws Exception {
Locale.setDefault(defaultLocale);
super.tearDown();
}
// Documented a previous difference in behavior between this and the RI, see
// https://code.google.com/p/android/issues/detail?id=61993 for more details.
// Switching to OpenJDK has fixed that issue and so this test has been changed to reflect
// the correct behavior.
public void test_computeFields_dayOfWeekAndWeekOfYearSet() {
Calendar greg = new GregorianCalendar(LOS_ANGELES, Locale.ENGLISH);
// Ensure we use different values to the default ones.
int differentWeekOfYear = greg.get(Calendar.WEEK_OF_YEAR) == 1 ? 2 : 1;
int differentDayOfWeek = greg.get(Calendar.DAY_OF_WEEK) == Calendar.MONDAY
? Calendar.TUESDAY : Calendar.MONDAY;
// Setting WEEK_OF_YEAR and DAY_OF_WEEK with an intervening
// call to computeFields will work.
greg.set(Calendar.WEEK_OF_YEAR, differentWeekOfYear);
assertEquals(differentWeekOfYear, greg.get(Calendar.WEEK_OF_YEAR));
greg.set(Calendar.DAY_OF_WEEK, differentDayOfWeek);
assertEquals(differentWeekOfYear, greg.get(Calendar.WEEK_OF_YEAR));
// Setting WEEK_OF_YEAR after DAY_OF_WEEK with no intervening
// call to computeFields will work.
greg = new GregorianCalendar(LOS_ANGELES, Locale.ENGLISH);
greg.set(Calendar.DAY_OF_WEEK, differentDayOfWeek);
greg.set(Calendar.WEEK_OF_YEAR, differentWeekOfYear);
assertEquals(differentWeekOfYear, greg.get(Calendar.WEEK_OF_YEAR));
assertEquals(differentDayOfWeek, greg.get(Calendar.DAY_OF_WEEK));
// Setting DAY_OF_WEEK after WEEK_OF_YEAR with no intervening
// call to computeFields will work.
greg = new GregorianCalendar(LOS_ANGELES, Locale.ENGLISH);
greg.set(Calendar.WEEK_OF_YEAR, differentWeekOfYear);
greg.set(Calendar.DAY_OF_WEEK, differentDayOfWeek);
assertEquals(differentWeekOfYear, greg.get(Calendar.WEEK_OF_YEAR));
assertEquals(differentDayOfWeek, greg.get(Calendar.DAY_OF_WEEK));
}
/**
* Specialized tests for those fields affected by GregorianCalendar cut over date.
*
* <p>Expands on a regression test created for harmony-2947.
*/
public void test_fieldsAffectedByGregorianCutOver() {
Date date = new Date(Date.parse("Jan 1 00:00:01 GMT 2000"));
assertEquals(946684801000L, date.getTime());
GregorianCalendar gc;
// Test in America/Los_Angeles
gc = new GregorianCalendar(LOS_ANGELES, Locale.ENGLISH);
gc.setGregorianChange(date);
gc.setTime(date);
// Check the date to ensure that it is 18th Dec 1999. The reason this is not 1st Jan 2000
// is that the offset for Los Angeles is GMT-08:00. setGregorianChange() is interpreted as
// a wall time, not an instant. So, the instant that corresponds to
// "1st Jan 2000 00:00:01 GMT" is 8 hours before the Julian/Gregorian switch in LA. The day
// before 1st Jan 2000 (Gregorian Calendar) would be 18th Dec 1999 in the Julian calendar.
// The reason it is not the 31st Dec 1999 is simply a result of the discontinuity that
// occurred when switching calendars. That happened for real when the calendars were
// switched in 1582.
//
// A different year explains why we get very different results for the methods being tested.
assertEquals(1999, gc.get(Calendar.YEAR));
assertEquals(Calendar.DECEMBER, gc.get(Calendar.MONTH));
assertEquals(18, gc.get(Calendar.DAY_OF_MONTH));
assertEquals(50, gc.getActualMaximum(Calendar.WEEK_OF_YEAR));
assertEquals(50, gc.getLeastMaximum(Calendar.WEEK_OF_YEAR));
assertEquals(3, gc.getActualMaximum(Calendar.WEEK_OF_MONTH));
assertEquals(3, gc.getLeastMaximum(Calendar.WEEK_OF_MONTH));
assertEquals(18, gc.getActualMaximum(Calendar.DAY_OF_MONTH));
assertEquals(18, gc.getLeastMaximum(Calendar.DAY_OF_MONTH));
assertEquals(352, gc.getActualMaximum(Calendar.DAY_OF_YEAR));
assertEquals(352, gc.getLeastMaximum(Calendar.DAY_OF_YEAR));
assertEquals(3, gc.getActualMaximum(Calendar.DAY_OF_WEEK_IN_MONTH));
assertEquals(3, gc.getLeastMaximum(Calendar.DAY_OF_WEEK_IN_MONTH));
// Test in Europe/London
gc = new GregorianCalendar(LONDON, Locale.ENGLISH);
gc.setGregorianChange(date);
gc.setTime(date);
// Check the date is actually 1st Jan 2000.
assertEquals(2000, gc.get(Calendar.YEAR));
assertEquals(Calendar.JANUARY, gc.get(Calendar.MONTH));
assertEquals(1, gc.get(Calendar.DAY_OF_MONTH));
assertEquals(53, gc.getActualMaximum(Calendar.WEEK_OF_YEAR));
assertEquals(52, gc.getLeastMaximum(Calendar.WEEK_OF_YEAR));
assertEquals(5, gc.getActualMaximum(Calendar.WEEK_OF_MONTH));
assertEquals(4, gc.getLeastMaximum(Calendar.WEEK_OF_MONTH));
assertEquals(31, gc.getActualMaximum(Calendar.DAY_OF_MONTH));
assertEquals(28, gc.getLeastMaximum(Calendar.DAY_OF_MONTH));
assertEquals(366, gc.getActualMaximum(Calendar.DAY_OF_YEAR));
assertEquals(365, gc.getLeastMaximum(Calendar.DAY_OF_YEAR));
assertEquals(5, gc.getActualMaximum(Calendar.DAY_OF_WEEK_IN_MONTH));
assertEquals(4, gc.getLeastMaximum(Calendar.DAY_OF_WEEK_IN_MONTH));
}
public void test_computeTime_enteringDst_TimeZone_LosAngeles_2014() {
TimeZone timeZone = TimeZone.getTimeZone("America/Los_Angeles");
checkDstLosAngeles2014(timeZone);
}
/**
* This test will fail in the RI.
*
* <p>The AOSP behavior is different for backwards compatibility with previous versions of
* Android.
*
* <p>Search in this file for 'OpenJDK Failure' to see more details.
*/
public void test_computeTime_enteringDst_DelegatingTimeZone_LosAngeles_2014() {
TimeZone timeZone = TimeZone.getTimeZone("America/Los_Angeles");
timeZone = new DelegatingTimeZone(timeZone);
checkDstLosAngeles2014(timeZone);
}
/**
* This test will fail in the RI.
*
* <p>The AOSP behavior is different for backwards compatibility with previous versions of
* Android.
*
* <p>Search in this file for 'OpenJDK Failure' to see more details.
*/
public void test_computeTime_enteringDst_SimpleTimeZone_LosAngeles_2014() {
checkDstLosAngeles2014(CUSTOM_LOS_ANGELES_TIME_ZONE);
}
public void test_computeTime_enteringDst() {
// Get the DST entry time with a ZoneInfo implementation of TimeZone.
TimeZone zoneInfo = TimeZone.getTimeZone("America/Los_Angeles");
long zoneInfoTime = getDstLosAngeles2014(zoneInfo);
// Check that the time is correct.
assertTrue(zoneInfo.inDaylightTime(new Date(zoneInfoTime)));
assertFalse(zoneInfo.inDaylightTime(new Date(zoneInfoTime - 1)));
// Get the DST entry time with a SimpleTimeZone implementation of TimeZone.
SimpleTimeZone simpleTimeZone = new SimpleTimeZone(-28800000,
"Custom America/Los_Angeles",
Calendar.MARCH, 9, 0, 7200000,
Calendar.NOVEMBER, 2, 0, 7200000,
3600000);
long simpleTimeZoneTime = getDstLosAngeles2014(simpleTimeZone);
}
public void test_isWeekDateSupported() {
assertTrue(new GregorianCalendar().isWeekDateSupported());
}
public void test_setWeekDate() {
GregorianCalendar cal = new GregorianCalendar();
// When first week should have at least 4 days
cal.setMinimalDaysInFirstWeek(4);
cal.setWeekDate(2016, 13, Calendar.TUESDAY);
assertEquals(Calendar.TUESDAY, cal.get(Calendar.DAY_OF_WEEK));
assertEquals(29, cal.get(Calendar.DAY_OF_MONTH));
// When first week can have single day
cal.setMinimalDaysInFirstWeek(1);
cal.setWeekDate(2016, 13, Calendar.TUESDAY);
assertEquals(Calendar.TUESDAY, cal.get(Calendar.DAY_OF_WEEK));
assertEquals(22, cal.get(Calendar.DAY_OF_MONTH));
try {
cal.setWeekDate(2016, 13, Calendar.SATURDAY + 1);
fail();
} catch (IllegalArgumentException expected) {
}
try {
cal.setWeekDate(2016, 13, Calendar.SUNDAY - 1);
fail();
} catch (IllegalArgumentException expected) {
}
// in non-lenient mode
cal.setLenient(false);
try {
cal.setWeekDate(2016, 60, Calendar.SATURDAY);
fail();
} catch (IllegalArgumentException expected) {}
try {
cal.setWeekDate(-1, 60, Calendar.SATURDAY);
fail();
} catch (IllegalArgumentException expected) {}
}
public void test_getWeekYear() {
GregorianCalendar cal = new GregorianCalendar();
cal.set(2016, Calendar.MARCH, 29);
assertEquals(2016, cal.getWeekYear());
// With minimal days in first week is set to 4
cal.setMinimalDaysInFirstWeek(4);
cal.set(2016, Calendar.JANUARY, 1);
assertEquals(2015, cal.getWeekYear());
cal.set(2015, Calendar.DECEMBER, 31);
assertEquals(2015, cal.getWeekYear());
// With minimal days in first week is set to 1
cal.setMinimalDaysInFirstWeek(1);
cal.set(2016, Calendar.JANUARY, 1);
assertEquals(2016, cal.getWeekYear());
cal.set(2015, Calendar.DECEMBER, 31);
assertEquals(2016, cal.getWeekYear());
}
public void test_getWeeksInWeekYear() {
GregorianCalendar cal = new GregorianCalendar();
// With minimal days in first week is set to 1
cal.setMinimalDaysInFirstWeek(1);
cal.set(2016, Calendar.JANUARY, 10);
assertEquals(53, cal.getWeeksInWeekYear());
// With minimal days in first week is set to 4
cal.setMinimalDaysInFirstWeek(4);
cal.set(2016, Calendar.JANUARY, 10);
assertEquals(52, cal.getWeeksInWeekYear());
}
public void test_fromZonedDateTime() {
ZonedDateTime zdt = ZonedDateTime.parse("2007-12-03T10:15:30+01:00[Europe/Paris]");
GregorianCalendar calendar = GregorianCalendar.from(zdt);
TimeZone timeZone = TimeZone.getTimeZone("Europe/Paris");
assertEquals(timeZone, calendar.getTimeZone());
assertEquals(2007, calendar.get(Calendar.YEAR));
assertEquals(Calendar.DECEMBER, calendar.get(Calendar.MONTH));
assertEquals(3, calendar.get(Calendar.DAY_OF_MONTH));
assertEquals(10, calendar.get(Calendar.HOUR_OF_DAY));
assertEquals(15, calendar.get(Calendar.MINUTE));
assertEquals(3600 * 1000, calendar.getTimeZone().getRawOffset()); // in milliseconds
}
public void test_fromZonedDateTime_invalidValues() {
ZoneId gmt = ZoneId.of("GMT");
ZonedDateTime[] invalidValues = {
ZonedDateTime.of(LocalDateTime.MAX, gmt),
ZonedDateTime.ofInstant(Instant.ofEpochMilli(Long.MAX_VALUE).plusMillis(1), gmt),
ZonedDateTime.ofInstant(Instant.ofEpochMilli(Long.MIN_VALUE).minusMillis(1), gmt),
ZonedDateTime.of(LocalDateTime.MAX, gmt) };
for (ZonedDateTime invalidValue : invalidValues) {
try {
GregorianCalendar.from(invalidValue);
fail("GregorianCalendar.from() should have failed with " + invalidValue);
} catch (IllegalArgumentException expected) {}
}
}
public void test_toZonedDateTime() {
TimeZone timeZone = TimeZone.getTimeZone("Europe/Paris");
GregorianCalendar calendar = new GregorianCalendar(timeZone);
calendar.set(2007, Calendar.DECEMBER, 3, 10, 15, 30);
calendar.set(Calendar.MILLISECOND, 0);
ZonedDateTime zdt = calendar.toZonedDateTime();
assertEquals(ZonedDateTime.parse("2007-12-03T10:15:30+01:00[Europe/Paris]"), zdt);
}
private long getDstLosAngeles2014(TimeZone timeZone) {
GregorianCalendar cal = new GregorianCalendar(timeZone, Locale.ENGLISH);
cal.set(Calendar.MILLISECOND, 0);
cal.set(2014, Calendar.MARCH, 9, 2, 0, 0);
return cal.getTimeInMillis();
}
private void checkDstLosAngeles2014(TimeZone timeZone) {
Calendar cal = new GregorianCalendar(timeZone, Locale.ENGLISH);
// Clear the milliseconds field.
cal.set(Calendar.MILLISECOND, 0);
String description;
// Check milliseconds one second before the transition.
description = "01:59:59 - March 9th 2014";
cal.set(2014, Calendar.MARCH, 9, 1, 59, 59);
checkMillis(cal, description, 1394359199000L);
// Outside DST time.
checkOutsideDst(cal, description);
// Check milliseconds at the transition point but using an invalid wall clock
// (02:00 - 02:59:59.999) do not actually exist.
description = "02:00:00 - March 9th 2014";
cal.set(2014, Calendar.MARCH, 9, 2, 0, 0);
// OpenJDK Failure:
// This fails on OpenJDK when running with SimpleTimeZone (or any custom TimeZone
// implementation). It incorrectly calculates the time in millis to be 1394355600000.
// That is because GregorianCalendar treats the implementation that underpins
// TimeZone.getTimeZone(String) specially and the code that runs for other classes does
// not handle the invalid wall clock period on entry to DST properly.
checkMillis(cal, description, 1394359200000L);
// Invalid wall clock but treated as being inside DST time.
checkInsideDst(cal, description);
// Check milliseconds at the first valid wall clock time after transition, 03:00 - should
// be treated the same as 02:00.
description = "03:00:00 - March 9th 2014";
cal.set(2014, Calendar.MARCH, 9, 3, 0, 0);
checkMillis(cal, description, 1394359200000L);
// Valid wall clock treated as being inside DST time.
checkInsideDst(cal, description);
// Check milliseconds at the last invalid wall clock time, 02:59:59.999.
description = "02:59:59.999 - March 9th 2014";
cal.set(2014, Calendar.MARCH, 9, 2, 59, 59);
cal.set(Calendar.MILLISECOND, 999);
checkMillis(cal, description, 1394362799999L);
// Invalid wall clock but treated as being inside DST time.
checkInsideDst(cal, description);
// Check milliseconds at 03:59:59.999 - should be treated the same as 02:59:59.999
description = "03:59:59.999 - March 9th 2014";
cal.set(2014, Calendar.MARCH, 9, 3, 59, 59);
cal.set(Calendar.MILLISECOND, 999);
checkMillis(cal, description, 1394362799999L);
// Valid wall clock treated as being inside DST time.
checkInsideDst(cal, description);
}
private void checkMillis(Calendar cal, String description, long expectedMillis) {
assertEquals("Incorrect millis: " + description, expectedMillis, cal.getTimeInMillis());
}
private void checkOutsideDst(Calendar cal, String description) {
TimeZone timeZone = cal.getTimeZone();
checkOutsideDst(cal, description, timeZone.getRawOffset());
}
private void checkOutsideDst(Calendar cal, String description, int expectedZoneOffset) {
checkDstFields(cal, description, expectedZoneOffset, 0);
}
private void checkInsideDst(Calendar cal, String description) {
TimeZone timeZone = cal.getTimeZone();
checkDstFields(cal, description, timeZone.getRawOffset(), timeZone.getDSTSavings());
}
private void checkDstFields(Calendar cal, String description, int expectedZoneOffset, int expectedDstOffset) {
assertEquals("Incorrect ZONE_OFFSET: " + description, expectedZoneOffset, cal.get(Calendar.ZONE_OFFSET));
assertEquals("Incorrect DST_OFFSET: " + description, expectedDstOffset, cal.get(Calendar.DST_OFFSET));
}
/**
* A custom {@link TimeZone} implementation.
*
* <p>Used to show the behavior of {@link GregorianCalendar} when provided with a custom
* implementation of {@link TimeZone}, i.e. one that is unknown to the runtime,
*/
private static class DelegatingTimeZone extends TimeZone {
private final TimeZone timeZone;
public DelegatingTimeZone(TimeZone timeZone) {
this.timeZone = timeZone;
}
@Override
public int getOffset(int era, int year, int month, int day, int dayOfWeek,
int milliseconds) {
return timeZone.getOffset(era, year, month, day, dayOfWeek, milliseconds);
}
@Override
public int getOffset(long date) {
return timeZone.getOffset(date);
}
@Override
public void setRawOffset(int offsetMillis) {
timeZone.setRawOffset(offsetMillis);
}
@Override
public int getRawOffset() {
return timeZone.getRawOffset();
}
@Override
public String getID() {
return timeZone.getID();
}
@Override
public void setID(String ID) {
timeZone.setID(ID);
}
@Override
public String getDisplayName(boolean daylightTime, int style, Locale locale) {
return timeZone.getDisplayName(daylightTime, style, locale);
}
@Override
public int getDSTSavings() {
return timeZone.getDSTSavings();
}
@Override
public boolean useDaylightTime() {
return timeZone.useDaylightTime();
}
@Override
public boolean observesDaylightTime() {
return timeZone.observesDaylightTime();
}
@Override
public boolean inDaylightTime(Date date) {
return timeZone.inDaylightTime(date);
}
@Override
public boolean hasSameRules(TimeZone other) {
return timeZone.hasSameRules(other);
}
}
private static int hours(int count) {
return HOUR_IN_MILLIS * count;
}
}