406 lines
14 KiB
Java
406 lines
14 KiB
Java
/*
|
|
* Copyright (C) 2014 The Android Open Source Project
|
|
* 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 (Era era : eras) {
|
|
if (era.getName().equals(eraName)) {
|
|
return era;
|
|
}
|
|
}
|
|
}
|
|
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];
|
|
// BEGIN Android-changed: Android doesn't have sun.util.calendar.ZoneInfo.
|
|
// if (zi instanceof ZoneInfo) {
|
|
// zoneOffset = ((ZoneInfo)zi).getOffsets(millis, offsets);
|
|
if (zi instanceof libcore.util.ZoneInfo) {
|
|
zoneOffset = ((libcore.util.ZoneInfo) zi).getOffsetsByUtcTime(millis, offsets);
|
|
// END Android-changed: Android doesn't have sun.util.calendar.ZoneInfo.
|
|
} 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)
|
|
// Android-changed: Android doesn't have sun.util.calendar.ZoneInfo.
|
|
// 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)
|
|
// Android-changed: Android doesn't have sun.util.calendar.ZoneInfo.
|
|
// 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;
|
|
}
|
|
}
|