| /* |
| * 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 (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]; |
| // BEGIN Android-changed: Android doesn't have sun.util.calendar.ZoneInfo. |
| // if (zi instanceof ZoneInfo) { |
| // zoneOffset = ((ZoneInfo)zi).getOffsets(millis, offsets); |
| // } else { |
| zoneOffset = zi.getOffset(millis); |
| offsets[0] = zi.getRawOffset(); |
| offsets[1] = zoneOffset - offsets[0]; |
| // } |
| // END Android-changed: Android doesn't have sun.util.calendar.ZoneInfo. |
| |
| // 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; |
| } |
| } |