blob: 85dddd42c5bffaaa7ef29e8927384bd5641a23e1 [file] [log] [blame]
/*
* Copyright (c) 2003, 2004, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation. Oracle designates this
* particular file as subject to the "Classpath" exception as provided
* by Oracle in the LICENSE file that accompanied this code.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*/
package sun.util.calendar;
import java.util.Locale;
import java.util.TimeZone;
/**
* The <code>AbstractCalendar</code> class provides a framework for
* implementing a concrete calendar system.
*
* <p><a name="fixed_date"></a><B>Fixed Date</B><br>
*
* For implementing a concrete calendar system, each calendar must
* have the common date numbering, starting from midnight the onset of
* Monday, January 1, 1 (Gregorian). It is called a <I>fixed date</I>
* in this class. January 1, 1 (Gregorian) is fixed date 1. (See
* Nachum Dershowitz and Edward M. Reingold, <I>CALENDRICAL
* CALCULATION The Millennium Edition</I>, Section 1.2 for details.)
*
* @author Masayoshi Okutsu
* @since 1.5
*/
public abstract class AbstractCalendar extends CalendarSystem {
// The constants assume no leap seconds support.
static final int SECOND_IN_MILLIS = 1000;
static final int MINUTE_IN_MILLIS = SECOND_IN_MILLIS * 60;
static final int HOUR_IN_MILLIS = MINUTE_IN_MILLIS * 60;
static final int DAY_IN_MILLIS = HOUR_IN_MILLIS * 24;
// The number of days between January 1, 1 and January 1, 1970 (Gregorian)
static final int EPOCH_OFFSET = 719163;
private Era[] eras;
protected AbstractCalendar() {
}
public Era getEra(String eraName) {
if (eras != null) {
for (int i = 0; i < eras.length; i++) {
if (eras[i].equals(eraName)) {
return eras[i];
}
}
}
return null;
}
public Era[] getEras() {
Era[] e = null;
if (eras != null) {
e = new Era[eras.length];
System.arraycopy(eras, 0, e, 0, eras.length);
}
return e;
}
public void setEra(CalendarDate date, String eraName) {
if (eras == null) {
return; // should report an error???
}
for (int i = 0; i < eras.length; i++) {
Era e = eras[i];
if (e != null && e.getName().equals(eraName)) {
date.setEra(e);
return;
}
}
throw new IllegalArgumentException("unknown era name: " + eraName);
}
protected void setEras(Era[] eras) {
this.eras = eras;
}
public CalendarDate getCalendarDate() {
return getCalendarDate(System.currentTimeMillis(), newCalendarDate());
}
public CalendarDate getCalendarDate(long millis) {
return getCalendarDate(millis, newCalendarDate());
}
public CalendarDate getCalendarDate(long millis, TimeZone zone) {
CalendarDate date = newCalendarDate(zone);
return getCalendarDate(millis, date);
}
public CalendarDate getCalendarDate(long millis, CalendarDate date) {
int ms = 0; // time of day
int zoneOffset = 0;
int saving = 0;
long days = 0; // fixed date
// adjust to local time if `date' has time zone.
TimeZone zi = date.getZone();
if (zi != null) {
int[] offsets = new int[2];
if (zi instanceof ZoneInfo) {
zoneOffset = ((ZoneInfo)zi).getOffsets(millis, offsets);
} else {
zoneOffset = zi.getOffset(millis);
offsets[0] = zi.getRawOffset();
offsets[1] = zoneOffset - offsets[0];
}
// We need to calculate the given millis and time zone
// offset separately for java.util.GregorianCalendar
// compatibility. (i.e., millis + zoneOffset could cause
// overflow or underflow, which must be avoided.) Usually
// days should be 0 and ms is in the range of -13:00 to
// +14:00. However, we need to deal with extreme cases.
days = zoneOffset / DAY_IN_MILLIS;
ms = zoneOffset % DAY_IN_MILLIS;
saving = offsets[1];
}
date.setZoneOffset(zoneOffset);
date.setDaylightSaving(saving);
days += millis / DAY_IN_MILLIS;
ms += (int) (millis % DAY_IN_MILLIS);
if (ms >= DAY_IN_MILLIS) {
// at most ms is (DAY_IN_MILLIS - 1) * 2.
ms -= DAY_IN_MILLIS;
++days;
} else {
// at most ms is (1 - DAY_IN_MILLIS) * 2. Adding one
// DAY_IN_MILLIS results in still negative.
while (ms < 0) {
ms += DAY_IN_MILLIS;
--days;
}
}
// convert to fixed date (offset from Jan. 1, 1 (Gregorian))
days += EPOCH_OFFSET;
// calculate date fields from the fixed date
getCalendarDateFromFixedDate(date, days);
// calculate time fields from the time of day
setTimeOfDay(date, ms);
date.setLeapYear(isLeapYear(date));
date.setNormalized(true);
return date;
}
public long getTime(CalendarDate date) {
long gd = getFixedDate(date);
long ms = (gd - EPOCH_OFFSET) * DAY_IN_MILLIS + getTimeOfDay(date);
int zoneOffset = 0;
TimeZone zi = date.getZone();
if (zi != null) {
if (date.isNormalized()) {
return ms - date.getZoneOffset();
}
// adjust time zone and daylight saving
int[] offsets = new int[2];
if (date.isStandardTime()) {
// 1) 2:30am during starting-DST transition is
// intrepreted as 2:30am ST
// 2) 5:00pm during DST is still interpreted as 5:00pm ST
// 3) 1:30am during ending-DST transition is interpreted
// as 1:30am ST (after transition)
if (zi instanceof ZoneInfo) {
((ZoneInfo)zi).getOffsetsByStandard(ms, offsets);
zoneOffset = offsets[0];
} else {
zoneOffset = zi.getOffset(ms - zi.getRawOffset());
}
} else {
// 1) 2:30am during starting-DST transition is
// intrepreted as 3:30am DT
// 2) 5:00pm during DST is intrepreted as 5:00pm DT
// 3) 1:30am during ending-DST transition is interpreted
// as 1:30am DT/0:30am ST (before transition)
if (zi instanceof ZoneInfo) {
zoneOffset = ((ZoneInfo)zi).getOffsetsByWall(ms, offsets);
} else {
zoneOffset = zi.getOffset(ms - zi.getRawOffset());
}
}
}
ms -= zoneOffset;
getCalendarDate(ms, date);
return ms;
}
protected long getTimeOfDay(CalendarDate date) {
long fraction = date.getTimeOfDay();
if (fraction != CalendarDate.TIME_UNDEFINED) {
return fraction;
}
fraction = getTimeOfDayValue(date);
date.setTimeOfDay(fraction);
return fraction;
}
public long getTimeOfDayValue(CalendarDate date) {
long fraction = date.getHours();
fraction *= 60;
fraction += date.getMinutes();
fraction *= 60;
fraction += date.getSeconds();
fraction *= 1000;
fraction += date.getMillis();
return fraction;
}
public CalendarDate setTimeOfDay(CalendarDate cdate, int fraction) {
if (fraction < 0) {
throw new IllegalArgumentException();
}
boolean normalizedState = cdate.isNormalized();
int time = fraction;
int hours = time / HOUR_IN_MILLIS;
time %= HOUR_IN_MILLIS;
int minutes = time / MINUTE_IN_MILLIS;
time %= MINUTE_IN_MILLIS;
int seconds = time / SECOND_IN_MILLIS;
time %= SECOND_IN_MILLIS;
cdate.setHours(hours);
cdate.setMinutes(minutes);
cdate.setSeconds(seconds);
cdate.setMillis(time);
cdate.setTimeOfDay(fraction);
if (hours < 24 && normalizedState) {
// If this time of day setting doesn't affect the date,
// then restore the normalized state.
cdate.setNormalized(normalizedState);
}
return cdate;
}
/**
* Returns 7 in this default implementation.
*
* @return 7
*/
public int getWeekLength() {
return 7;
}
protected abstract boolean isLeapYear(CalendarDate date);
public CalendarDate getNthDayOfWeek(int nth, int dayOfWeek, CalendarDate date) {
CalendarDate ndate = (CalendarDate) date.clone();
normalize(ndate);
long fd = getFixedDate(ndate);
long nfd;
if (nth > 0) {
nfd = 7 * nth + getDayOfWeekDateBefore(fd, dayOfWeek);
} else {
nfd = 7 * nth + getDayOfWeekDateAfter(fd, dayOfWeek);
}
getCalendarDateFromFixedDate(ndate, nfd);
return ndate;
}
/**
* Returns a date of the given day of week before the given fixed
* date.
*
* @param fixedDate the fixed date
* @param dayOfWeek the day of week
* @return the calculated date
*/
static long getDayOfWeekDateBefore(long fixedDate, int dayOfWeek) {
return getDayOfWeekDateOnOrBefore(fixedDate - 1, dayOfWeek);
}
/**
* Returns a date of the given day of week that is closest to and
* after the given fixed date.
*
* @param fixedDate the fixed date
* @param dayOfWeek the day of week
* @return the calculated date
*/
static long getDayOfWeekDateAfter(long fixedDate, int dayOfWeek) {
return getDayOfWeekDateOnOrBefore(fixedDate + 7, dayOfWeek);
}
/**
* Returns a date of the given day of week on or before the given fixed
* date.
*
* @param fixedDate the fixed date
* @param dayOfWeek the day of week
* @return the calculated date
*/
// public for java.util.GregorianCalendar
public static long getDayOfWeekDateOnOrBefore(long fixedDate, int dayOfWeek) {
long fd = fixedDate - (dayOfWeek - 1);
if (fd >= 0) {
return fixedDate - (fd % 7);
}
return fixedDate - CalendarUtils.mod(fd, 7);
}
/**
* Returns the fixed date calculated with the specified calendar
* date. If the specified date is not normalized, its date fields
* are normalized.
*
* @param date a <code>CalendarDate</code> with which the fixed
* date is calculated
* @return the calculated fixed date
* @see AbstractCalendar.html#fixed_date
*/
protected abstract long getFixedDate(CalendarDate date);
/**
* Calculates calendar fields from the specified fixed date. This
* method stores the calculated calendar field values in the specified
* <code>CalendarDate</code>.
*
* @param date a <code>CalendarDate</code> to stored the
* calculated calendar fields.
* @param fixedDate a fixed date to calculate calendar fields
* @see AbstractCalendar.html#fixed_date
*/
protected abstract void getCalendarDateFromFixedDate(CalendarDate date,
long fixedDate);
public boolean validateTime(CalendarDate date) {
int t = date.getHours();
if (t < 0 || t >= 24) {
return false;
}
t = date.getMinutes();
if (t < 0 || t >= 60) {
return false;
}
t = date.getSeconds();
// TODO: Leap second support.
if (t < 0 || t >= 60) {
return false;
}
t = date.getMillis();
if (t < 0 || t >= 1000) {
return false;
}
return true;
}
int normalizeTime(CalendarDate date) {
long fraction = getTimeOfDay(date);
long days = 0;
if (fraction >= DAY_IN_MILLIS) {
days = fraction / DAY_IN_MILLIS;
fraction %= DAY_IN_MILLIS;
} else if (fraction < 0) {
days = CalendarUtils.floorDivide(fraction, DAY_IN_MILLIS);
if (days != 0) {
fraction -= DAY_IN_MILLIS * days; // mod(fraction, DAY_IN_MILLIS)
}
}
if (days != 0) {
date.setTimeOfDay(fraction);
}
date.setMillis((int)(fraction % 1000));
fraction /= 1000;
date.setSeconds((int)(fraction % 60));
fraction /= 60;
date.setMinutes((int)(fraction % 60));
date.setHours((int)(fraction / 60));
return (int)days;
}
}