blob: 4cb15ac7c98fb37fc06057c58898b0edf1676823 [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.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));
// 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);
}
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;
}
}