/*
* @(#)GregorianCalendar.java 1.21 97/07/15
*
* (C) Copyright Taligent, Inc. 1996-1997 - All Rights Reserved
* (C) Copyright IBM Corp. 1996-1997 - All Rights Reserved
*
* Portions copyright (c) 1996 Sun Microsystems, Inc. All Rights Reserved.
*
* The original version of this source code and documentation is copyrighted
* and owned by Taligent, Inc., a wholly-owned subsidiary of IBM. These
* materials are provided under terms of a License Agreement between Taligent
* and Sun. This technology is protected by multiple US and International
* patents. This notice and attribution to Taligent may not be removed.
* Taligent is a registered trademark of Taligent, Inc.
*
* Permission to use, copy, modify, and distribute this software
* and its documentation for NON-COMMERCIAL purposes and without
* fee is hereby granted provided that this copyright notice
* appears in all copies. Please refer to the file "copyright.html"
* for further important copyright and licensing information.
*
* SUN MAKES NO REPRESENTATIONS OR WARRANTIES ABOUT THE SUITABILITY OF
* THE SOFTWARE, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED
* TO THE IMPLIED WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
* PARTICULAR PURPOSE, OR NON-INFRINGEMENT. SUN SHALL NOT BE LIABLE FOR
* ANY DAMAGES SUFFERED BY LICENSEE AS A RESULT OF USING, MODIFYING OR
* DISTRIBUTING THIS SOFTWARE OR ITS DERIVATIVES.
*
*/
package java.util;
/**
* GregorianCalendar
is a concrete subclass of
* Calendar
* and provides the standard calendar used by most of the world.
*
*
* The standard (Gregorian) calendar has 2 eras, BC and AD. * *
* This implementation handles a single discontinuity, which corresponds * by default to the date the Gregorian calendar was instituted (October 15, * 1582 in some countries, later in others). This cutover date may be changed * by the caller. * *
* Prior to the institution of the Gregorian calendar, New Year's Day was * March 25. To avoid confusion, this calendar always uses January 1. A manual * adjustment may be made if desired for dates that are prior to the Gregorian * changeover and which fall between January 1 and March 24. * *
* The current implementation handles dates in a wide range, from * 4716 BC up to 5000000 AD. Dates outside of that range * will throw an IllegalArgumentException. This range should be broadened * in the future. * *
* Example: *
** * @see Calendar * @see TimeZone * @version 1.21 07/15/97 * @author David Goldsmith, Mark Davis, Chen-Lieh Huang, Alan Liu */ public class GregorianCalendar extends Calendar { // Internal notes: // This algorithm is based on the one presented on pp. 10-12 of // "Numerical Recipes in C", William H. Press, et. al., Cambridge // University Press 1988, ISBN 0-521-35465-X. /** * Useful constant for GregorianCalendar. */ public static final int BC = 0; /** * Useful constant for GregorianCalendar. */ public static final int AD = 1; // Note that the Julian date used here is not a true Julian date, since // it is measured from midnight, not noon. private static final long julianDayOffset = 2440588; private static final int millisPerDay = 24 * 60 * 60 * 1000; private static final int NUM_DAYS[] = {0,31,59,90,120,151,181,212,243,273,304,334}; // 0-based, for day-in-year private static final int LEAP_NUM_DAYS[] = {0,31,60,91,121,152,182,213,244,274,305,335}; // 0-based, for day-in-year private static final int MONTH_LENGTH[] = {31,28,31,30,31,30,31,31,30,31,30,31}; // 0-based private static final int LEAP_MONTH_LENGTH[] = {31,29,31,30,31,30,31,31,30,31,30,31}; // 0-based // This is measured from the standard epoch, not in Julian Days. // Default is 00:00:00 local time, October 15, 1582. private long gregorianCutover = -12219292800000L; // The onset of the Julian calendar is 45 B.C. The Julian day // number for the start of the year 45 B.C. is 1712653. We compute // the Julian onset as epoch-based millis. Note that this number is // useful for rough comparison purposes only; it's not exact. [LIU] private static long JULIAN_ONSET = (1712653 - julianDayOffset) * millisPerDay; // The earliest date we can handle for computing into fields is January 1, // 4716 B.C. This limit is the same regardless of when the Gregorian // cutover is, because it is before all cutovers. If we try to compute the // fields for a date before this date, we get nonsense values. The // following constant encodes this date as millis from the epoch. [LIU] private static long EARLIEST_USABLE_MILLIS = -210993120000000L; private static int EARLIEST_USABLE_YEAR = 4716; /** * Converts time as milliseconds to Julian date. * @param millis the given milliseconds. * @return the Julian date number. */ private static final long millisToJulianDay(long millis) { if (millis >= 0) return julianDayOffset + (millis / millisPerDay); else return julianDayOffset + ((millis - millisPerDay + 1) / millisPerDay); } /** * Converts Julian date to time as milliseconds. * @param julian the given Julian date number. * @return time as milliseconds. */ private static final long julianDayToMillis(long julian) { return (julian - julianDayOffset) * millisPerDay; } /** * Constructs a default GregorianCalendar using the current time * in the default time zone with the default locale. */ public GregorianCalendar() { this(TimeZone.getDefault(), Locale.getDefault()); } /** * Constructs a GregorianCalendar based on the current time * in the given time zone with the default locale. * @param zone the given time zone. */ public GregorianCalendar(TimeZone zone) { this(zone, Locale.getDefault()); } /** * Constructs a GregorianCalendar based on the current time * in the default time zone with the given locale. * @param aLocale the given locale. */ public GregorianCalendar(Locale aLocale) { this(TimeZone.getDefault(), aLocale); } /** * Constructs a GregorianCalendar based on the current time * in the given time zone with the given locale. * @param zone the given time zone. * @param aLocale the given locale. */ public GregorianCalendar(TimeZone zone, Locale aLocale) { super(zone, aLocale); setTimeInMillis(System.currentTimeMillis()); } /** * Constructs a GregorianCalendar with the given date set * in the default time zone with the default locale. * @param year the value used to set the YEAR time field in the calendar. * @param month the value used to set the MONTH time field in the calendar. * Month value is 0-based. e.g., 0 for January. * @param date the value used to set the DATE time field in the calendar. */ public GregorianCalendar(int year, int month, int date) { super(TimeZone.getDefault(), Locale.getDefault()); this.set(Calendar.ERA, AD); this.set(Calendar.YEAR, year); this.set(Calendar.MONTH, month); this.set(Calendar.DATE, date); } /** * Constructs a GregorianCalendar with the given date * and time set for the default time zone with the default locale. * @param year the value used to set the YEAR time field in the calendar. * @param month the value used to set the MONTH time field in the calendar. * Month value is 0-based. e.g., 0 for January. * @param date the value used to set the DATE time field in the calendar. * @param hour the value used to set the HOUR_OF_DAY time field * in the calendar. * @param minute the value used to set the MINUTE time field * in the calendar. */ public GregorianCalendar(int year, int month, int date, int hour, int minute) { super(TimeZone.getDefault(), Locale.getDefault()); this.set(Calendar.ERA, AD); this.set(Calendar.YEAR, year); this.set(Calendar.MONTH, month); this.set(Calendar.DATE, date); this.set(Calendar.HOUR_OF_DAY, hour); this.set(Calendar.MINUTE, minute); } /** * Constructs a GregorianCalendar with the given date * and time set for the default time zone with the default locale. * @param year the value used to set the YEAR time field in the calendar. * @param month the value used to set the MONTH time field in the calendar. * Month value is 0-based. e.g., 0 for January. * @param date the value used to set the DATE time field in the calendar. * @param hour the value used to set the HOUR_OF_DAY time field * in the calendar. * @param minute the value used to set the MINUTE time field * in the calendar. * @param second the value used to set the SECOND time field * in the calendar. */ public GregorianCalendar(int year, int month, int date, int hour, int minute, int second) { super(TimeZone.getDefault(), Locale.getDefault()); this.set(Calendar.ERA, AD); this.set(Calendar.YEAR, year); this.set(Calendar.MONTH, month); this.set(Calendar.DATE, date); this.set(Calendar.HOUR_OF_DAY, hour); this.set(Calendar.MINUTE, minute); this.set(Calendar.SECOND, second); } /** * Sets the GregorianCalendar change date. This is the point when the * switch from Julian dates to Gregorian dates occurred. Default is * 00:00:00 local time, October 15, 1582. Previous to this time and date * will be Julian dates. * * @param date the given Gregorian cutover date. */ public void setGregorianChange(Date date) { gregorianCutover = date.getTime(); } /** * Gets the Gregorian Calendar change date. This is the point when the * switch from Julian dates to Gregorian dates occurred. Default is * 00:00:00 local time, October 15, 1582. Previous to * this time and date will be Julian dates. * @return the Gregorian cutover time for this calendar. */ public final Date getGregorianChange() { return new Date(gregorianCutover); } private static final int julianDayToDayOfWeek(long julian) { // If julian is negative, then julian%7 will be negative, so we adjust // accordingly. We add 1 because Julian day 0 is Monday. int dayOfWeek = (int)((julian + 1) % 7); return dayOfWeek + ((dayOfWeek < 0) ? (7 + SUNDAY) : SUNDAY); } /** * Convert the time as milliseconds to the "big" fields. Millis must be * given as local wall millis to get the correct local day. For example, * if it is 11:30 pm Standard, and DST is in effect, the correct DST millis * must be passed in to get the right date. * * Fields that are completed by this method: ERA, YEAR, MONTH, DATE, * DAY_OF_WEEK, DAY_OF_YEAR, WEEK_OF_YEAR, WEEK_OF_MONTH, * DAY_OF_WEEK_IN_MONTH. */ private final void timeToFields(long theTime) { int year, month, date, dayOfWeek, dayOfYear, weekCount, era = AD; // The following algorithm only works for dates from January 1, 4716 BC // onwards. We throw an IllegalArgumentException if the date is earlier // than this. if (theTime < EARLIEST_USABLE_MILLIS) throw new IllegalArgumentException("GregorianCalendar does not handle dates before 4716 BC"); //--------------------------------------------------------------------- // BEGIN modified caldat() //--------------------------------------------------------------------- // The following variable names are somewhat cryptic. Unfortunately, // they are from the original program cited above, and no explanation // for their meaning is given. Given that the algorithm is cryptic too, // perhaps it doesn't matter... long ja, jb, jd; long jc, je; // changed from int to fix number overflow problem. long julian = millisToJulianDay(theTime); if (theTime >= gregorianCutover) { long jalpha = (long) (((double) (julian - 1867216) - 0.25) / 36524.25); ja = julian + 1 + jalpha - (long) (0.25 * jalpha); } else { ja = julian; } jb = ja + 1524; jc = (long) (6680.0 + ((double) (jb - 2439870) - 122.1) / 365.25); jd = (long) (365*jc + (0.25 * jc)); je = (long) ((jb-jd)/30.6001); date = (int) (jb-jd-(long) (30.6001 * je)); month = (int) je - 1; if (month > 12) month -= 12; // Removed; this isn't needed, and in fact should be "if (month < 1)" if // it is needed. [LIU] // else if (month < 0) // added by CLH // month += 12; // added by CLH, 8-7-96 year = (int) (jc-4715); if (month > 2) --year; if (year <= 0) { era = BC; year = 1-year; } //--------------------------------------------------------------------- // END modified caldat() //--------------------------------------------------------------------- internalSet(ERA, era); internalSet(YEAR, year); internalSet(MONTH, month-1); // 0-based internalSet(DATE, date); dayOfWeek = julianDayToDayOfWeek(julian); internalSet(DAY_OF_WEEK, dayOfWeek); // CLH, 8-7-96 if (isLeapYear(year)) dayOfYear = LEAP_NUM_DAYS[month-1] + date; // month: 0-based else dayOfYear = NUM_DAYS[month-1] + date; // month: 0-based internalSet(DAY_OF_YEAR, dayOfYear); internalSet(WEEK_OF_YEAR, weekNumber(dayOfYear, dayOfWeek)); internalSet(WEEK_OF_MONTH, weekNumber(date, dayOfWeek)); internalSet(DAY_OF_WEEK_IN_MONTH, (date-1) / 7 + 1); } /** * Return the week number of a day, within a period. This may be the week number in * a year, or the week number in a month. Usually this will be a value >= 1, but if * some initial days of the period are excluded from week 1, because * minimalDaysInFirstWeek is > 1, then the week number will be zero for those * initial days. Requires the day of week for the given date in order to determine * the day of week of the first day of the period. * * @param dayOfPeriod Day-of-year or day-of-month. Should be 1 for first day of period. * @param day Day-of-week for given dayOfPeriod. 1-based with 1=Sunday. * @return Week number, one-based, or zero if the day falls in part of the * month before the first week, when there are days before the first * week because the minimum days in the first week is more than one. */ private int weekNumber(int dayOfPeriod, int dayOfWeek) { // Determine the day of the week of the first day of the period // in question (either a year or a month). Zero represents the // first day of the week on this calendar. int periodStartDayOfWeek = (dayOfWeek - getFirstDayOfWeek() - dayOfPeriod + 1) % 7; if (periodStartDayOfWeek < 0) periodStartDayOfWeek += 7; // Compute the week number. Initially, ignore the first week, which // may be fractional (or may not be). We add periodStartDayOfWeek in // order to fill out the first week, if it is fractional. int weekNo = (dayOfPeriod + periodStartDayOfWeek - 1)/7; // If the first week is long enough, then count it. If // the minimal days in the first week is one, or if the period start // is zero, we always increment weekNo. if ((7 - periodStartDayOfWeek) >= getMinimalDaysInFirstWeek()) ++weekNo; return weekNo; } /** * Determines if the given year is a leap year. Returns true if the * given year is a leap year. * @param year the given year. * @return true if the given year is a leap year; false otherwise. */ public boolean isLeapYear(int year) { // Compute the rough millis for the year. We only need this number to be // good enough to compare it against JULIAN_ONSET. long equivalent_millis = (long)((year - 1970) * 365.2422 * millisPerDay); // No leap years before onset of Julian calendar if (equivalent_millis < JULIAN_ONSET) return false; return (equivalent_millis > gregorianCutover) ? ((year%4 == 0) && ((year%100 != 0) || (year%400 == 0))) : // Gregorian (year%4 == 0); // Julian } /** * Overrides Calendar * Converts UTC as milliseconds to time field values. * The time is not * recomputed first; to recompute the time, then the fields, call the ** // get the supported ids for GMT-08:00 (Pacific Standard Time) * String[] ids = TimeZone.getAvailableIDs(-8 * 60 * 60 * 1000); * // if no ids were returned, something is wrong. get out. * if (ids.length == 0) * System.exit(0); * * // begin output * System.out.println("Current Time"); * * // create a Pacific Standard Time time zone * SimpleTimeZone pdt = new SimpleTimeZone(-8 * 60 * 60 * 1000, ids[0]); * * // set up rules for daylight savings time * pdt.setStartRule(Calendar.APRIL, 1, Calendar.SUNDAY, 2 * 60 * 60 * 1000); * pdt.setEndRule(Calendar.OCTOBER, -1, Calendar.SUNDAY, 2 * 60 * 60 * 1000); * * // create a GregorianCalendar with the Pacific Daylight time zone * // and the current date and time * Calendar calendar = new GregorianCalendar(pdt); * Date trialTime = new Date(); * calendar.setTime(trialTime); * * // print out a bunch of interesting things * System.out.println("ERA: " + calendar.get(Calendar.ERA)); * System.out.println("YEAR: " + calendar.get(Calendar.YEAR)); * System.out.println("MONTH: " + calendar.get(Calendar.MONTH)); * System.out.println("WEEK_OF_YEAR: " + calendar.get(Calendar.WEEK_OF_YEAR)); * System.out.println("WEEK_OF_MONTH: " + calendar.get(Calendar.WEEK_OF_MONTH)); * System.out.println("DATE: " + calendar.get(Calendar.DATE)); * System.out.println("DAY_OF_MONTH: " + calendar.get(Calendar.DAY_OF_MONTH)); * System.out.println("DAY_OF_YEAR: " + calendar.get(Calendar.DAY_OF_YEAR)); * System.out.println("DAY_OF_WEEK: " + calendar.get(Calendar.DAY_OF_WEEK)); * System.out.println("DAY_OF_WEEK_IN_MONTH: " * + calendar.get(Calendar.DAY_OF_WEEK_IN_MONTH)); * System.out.println("AM_PM: " + calendar.get(Calendar.AM_PM)); * System.out.println("HOUR: " + calendar.get(Calendar.HOUR)); * System.out.println("HOUR_OF_DAY: " + calendar.get(Calendar.HOUR_OF_DAY)); * System.out.println("MINUTE: " + calendar.get(Calendar.MINUTE)); * System.out.println("SECOND: " + calendar.get(Calendar.SECOND)); * System.out.println("MILLISECOND: " + calendar.get(Calendar.MILLISECOND)); * System.out.println("ZONE_OFFSET: " * + (calendar.get(Calendar.ZONE_OFFSET)/(60*60*1000))); * System.out.println("DST_OFFSET: " * + (calendar.get(Calendar.DST_OFFSET)/(60*60*1000))); * System.out.println("Current Time, with hour reset to 3"); * calendar.clear(Calendar.HOUR_OF_DAY); // so doesn't override * calendar.set(Calendar.HOUR, 3); * System.out.println("ERA: " + calendar.get(Calendar.ERA)); * System.out.println("YEAR: " + calendar.get(Calendar.YEAR)); * System.out.println("MONTH: " + calendar.get(Calendar.MONTH)); * System.out.println("WEEK_OF_YEAR: " + calendar.get(Calendar.WEEK_OF_YEAR)); * System.out.println("WEEK_OF_MONTH: " + calendar.get(Calendar.WEEK_OF_MONTH)); * System.out.println("DATE: " + calendar.get(Calendar.DATE)); * System.out.println("DAY_OF_MONTH: " + calendar.get(Calendar.DAY_OF_MONTH)); * System.out.println("DAY_OF_YEAR: " + calendar.get(Calendar.DAY_OF_YEAR)); * System.out.println("DAY_OF_WEEK: " + calendar.get(Calendar.DAY_OF_WEEK)); * System.out.println("DAY_OF_WEEK_IN_MONTH: " * + calendar.get(Calendar.DAY_OF_WEEK_IN_MONTH)); * System.out.println("AM_PM: " + calendar.get(Calendar.AM_PM)); * System.out.println("HOUR: " + calendar.get(Calendar.HOUR)); * System.out.println("HOUR_OF_DAY: " + calendar.get(Calendar.HOUR_OF_DAY)); * System.out.println("MINUTE: " + calendar.get(Calendar.MINUTE)); * System.out.println("SECOND: " + calendar.get(Calendar.SECOND)); * System.out.println("MILLISECOND: " + calendar.get(Calendar.MILLISECOND)); * System.out.println("ZONE_OFFSET: " * + (calendar.get(Calendar.ZONE_OFFSET)/(60*60*1000))); // in hours * System.out.println("DST_OFFSET: " * + (calendar.get(Calendar.DST_OFFSET)/(60*60*1000))); // in hours **
complete
method.
* @see Calendar#complete
*/
protected void computeFields()
{
if (areFieldsSet) return;
int gmtOffset = getTimeZone().getRawOffset();
long localMillis = time + gmtOffset;
// Time to fields takes the wall millis (Standard or DST).
timeToFields(localMillis);
int era = internalGet(Calendar.ERA);
int year = internalGet(Calendar.YEAR);
int month = internalGet(Calendar.MONTH);
int date = internalGet(Calendar.DATE);
int dayOfWeek = internalGet(Calendar.DAY_OF_WEEK);
long days = (long) (localMillis / millisPerDay);
int millisInDay = (int) (localMillis - (days * millisPerDay));
if (millisInDay < 0) millisInDay += millisPerDay;
// Call getOffset() to get the TimeZone offset. The millisInDay value must
// be standard local millis.
int dstOffset = getTimeZone().getOffset(era,year,month,date,dayOfWeek,millisInDay) -
gmtOffset;
// Adjust our millisInDay for DST, if necessary.
millisInDay += dstOffset;
// If DST has pushed us into the next day, we must call timeToFields() again.
// This happens in DST between 12:00 am and 1:00 am every day. The call to
// timeToFields() will give the wrong day, since the Standard time is in the
// previous day.
if (millisInDay >= millisPerDay)
{
millisInDay -= millisPerDay;
localMillis += dstOffset;
timeToFields(localMillis);
}
// Fill in all time-related fields based on millisInDay. Call internalSet()
// so as not to perturb flags.
internalSet(Calendar.MILLISECOND, millisInDay % 1000);
millisInDay /= 1000;
internalSet(Calendar.SECOND, millisInDay % 60);
millisInDay /= 60;
internalSet(Calendar.MINUTE, millisInDay % 60);
millisInDay /= 60;
internalSet(Calendar.HOUR_OF_DAY, millisInDay);
internalSet(Calendar.AM_PM, millisInDay / 12);
internalSet(Calendar.HOUR, millisInDay % 12);
internalSet(Calendar.ZONE_OFFSET, gmtOffset);
internalSet(Calendar.DST_OFFSET, dstOffset);
userSetZoneOffset = false;
userSetDSTOffset = false;
areFieldsSet = true;
areAllFieldsSet = true;
// Careful here: We are manually setting the isSet[] flags to true, so we
// must be sure that the above code actually does set all these fields.
for (int i=0; i* Field names Minimum Greatest Minimum Least Maximum Maximum * ----------- ------- ---------------- ------------- ------- * ERA 0 0 1 1 * YEAR 1 1 5,000,000 5,000,000 * MONTH 0 0 11 11 * WEEK_OF_YEAR 0 0 53 54 * WEEK_OF_MONTH 0 0 4 6 * DAY_OF_MONTH 1 1 28 31 * DAY_OF_YEAR 1 1 365 366 * DAY_OF_WEEK 1 1 7 7 * DAY_OF_WEEK_IN_MONTH -1 -1 4 6 * AM_PM 0 0 1 1 * HOUR 0 0 11 12 * HOUR_OF_DAY 0 0 23 23 * MINUTE 0 0 59 59 * SECOND 0 0 59 59 * MILLISECOND 0 0 999 999 * ZONE_OFFSET -12*60*60*1000 -12*60*60*1000 12*60*60*1000 12*60*60*1000 * DST_OFFSET 0 0 1*60*60*1000 1*60*60*1000 **/ private static final int MinValues[] = {0,1,0,0,0,1,1,1,-1,0,0,0,0,0,0,-12*60*60*1000,0}; private static final int GreatestMinValues[] = {0,1,0,0,0,1,1,1,-1,0,0,0,0,0,0,-12*60*60*1000,0};// same as MinValues private static final int LeastMaxValues[] = {1,5000000,11,53,4,28,365,7,4,1,11,23,59,59,999, 12*60*60*1000,1*60*60*1000}; private static final int MaxValues[] = {1,5000000,11,54,6,31,366,7,6,1,12,23,59,59,999, 12*60*60*1000,1*60*60*1000}; /** * Returns minimum value for the given field. * e.g. for Gregorian DAY_OF_MONTH, 1 * Please see Calendar.getMinimum for descriptions on parameters and * the return value. */ public int getMinimum(int field) { return MinValues[field]; } /** * Returns maximum value for the given field. * e.g. for Gregorian DAY_OF_MONTH, 31 * Please see Calendar.getMaximum for descriptions on parameters and * the return value. */ public int getMaximum(int field) { return MaxValues[field]; } /** * Returns highest minimum value for the given field if varies. * Otherwise same as getMinimum(). For Gregorian, no difference. * Please see Calendar.getGreatestMinimum for descriptions on parameters * and the return value. */ public int getGreatestMinimum(int field) { return GreatestMinValues[field]; } /** * Returns lowest maximum value for the given field if varies. * Otherwise same as getMaximum(). For Gregorian DAY_OF_MONTH, 28 * Please see Calendar.getLeastMaximum for descriptions on parameters and * the return value. */ public int getLeastMaximum(int field) { return LeastMaxValues[field]; } // Useful millisecond constants private static final long ONE_SECOND = 1000; private static final long ONE_MINUTE = 60*ONE_SECOND; private static final long ONE_HOUR = 60*ONE_MINUTE; private static final long ONE_DAY = 24*ONE_HOUR; private static final long ONE_WEEK = 7*ONE_DAY; // Proclaim serialization compatiblity with JDK 1.1 static final long serialVersionUID = -8125100834729963327L; }