/* * 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; } }