1 /*
2  * Copyright (C) 2014 The Android Open Source Project
3  * Copyright (c) 2003, 2004, Oracle and/or its affiliates. All rights reserved.
4  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
5  *
6  * This code is free software; you can redistribute it and/or modify it
7  * under the terms of the GNU General Public License version 2 only, as
8  * published by the Free Software Foundation.  Oracle designates this
9  * particular file as subject to the "Classpath" exception as provided
10  * by Oracle in the LICENSE file that accompanied this code.
11  *
12  * This code is distributed in the hope that it will be useful, but WITHOUT
13  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
14  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
15  * version 2 for more details (a copy is included in the LICENSE file that
16  * accompanied this code).
17  *
18  * You should have received a copy of the GNU General Public License version
19  * 2 along with this work; if not, write to the Free Software Foundation,
20  * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
21  *
22  * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
23  * or visit www.oracle.com if you need additional information or have any
24  * questions.
25  */
26 
27 package sun.util.calendar;
28 
29 import java.util.Locale;
30 import java.util.TimeZone;
31 
32 /**
33  * The <code>AbstractCalendar</code> class provides a framework for
34  * implementing a concrete calendar system.
35  *
36  * <p><a name="fixed_date"></a><B>Fixed Date</B><br>
37  *
38  * For implementing a concrete calendar system, each calendar must
39  * have the common date numbering, starting from midnight the onset of
40  * Monday, January 1, 1 (Gregorian). It is called a <I>fixed date</I>
41  * in this class. January 1, 1 (Gregorian) is fixed date 1. (See
42  * Nachum Dershowitz and Edward M. Reingold, <I>CALENDRICAL
43  * CALCULATION The Millennium Edition</I>, Section 1.2 for details.)
44  *
45  * @author Masayoshi Okutsu
46  * @since 1.5
47  */
48 
49 public abstract class AbstractCalendar extends CalendarSystem {
50 
51     // The constants assume no leap seconds support.
52     static final int SECOND_IN_MILLIS = 1000;
53     static final int MINUTE_IN_MILLIS = SECOND_IN_MILLIS * 60;
54     static final int HOUR_IN_MILLIS = MINUTE_IN_MILLIS * 60;
55     static final int DAY_IN_MILLIS = HOUR_IN_MILLIS * 24;
56 
57     // The number of days between January 1, 1 and January 1, 1970 (Gregorian)
58     static final int EPOCH_OFFSET = 719163;
59 
60     private Era[] eras;
61 
AbstractCalendar()62     protected AbstractCalendar() {
63     }
64 
getEra(String eraName)65     public Era getEra(String eraName) {
66         if (eras != null) {
67             for (Era era : eras) {
68                 if (era.getName().equals(eraName)) {
69                     return era;
70                 }
71             }
72         }
73         return null;
74     }
75 
getEras()76     public Era[] getEras() {
77         Era[] e = null;
78         if (eras != null) {
79             e = new Era[eras.length];
80             System.arraycopy(eras, 0, e, 0, eras.length);
81         }
82         return e;
83     }
84 
setEra(CalendarDate date, String eraName)85     public void setEra(CalendarDate date, String eraName) {
86         if (eras == null) {
87             return; // should report an error???
88         }
89         for (int i = 0; i < eras.length; i++) {
90             Era e = eras[i];
91             if (e != null && e.getName().equals(eraName)) {
92                 date.setEra(e);
93                 return;
94             }
95         }
96         throw new IllegalArgumentException("unknown era name: " + eraName);
97     }
98 
setEras(Era[] eras)99     protected void setEras(Era[] eras) {
100         this.eras = eras;
101     }
102 
getCalendarDate()103     public CalendarDate getCalendarDate() {
104         return getCalendarDate(System.currentTimeMillis(), newCalendarDate());
105     }
106 
getCalendarDate(long millis)107     public CalendarDate getCalendarDate(long millis) {
108         return getCalendarDate(millis, newCalendarDate());
109     }
110 
getCalendarDate(long millis, TimeZone zone)111     public CalendarDate getCalendarDate(long millis, TimeZone zone) {
112         CalendarDate date = newCalendarDate(zone);
113         return getCalendarDate(millis, date);
114     }
115 
getCalendarDate(long millis, CalendarDate date)116     public CalendarDate getCalendarDate(long millis, CalendarDate date) {
117         int ms = 0;             // time of day
118         int zoneOffset = 0;
119         int saving = 0;
120         long days = 0;          // fixed date
121 
122         // adjust to local time if `date' has time zone.
123         TimeZone zi = date.getZone();
124         if (zi != null) {
125             int[] offsets = new int[2];
126             // BEGIN Android-changed: Android doesn't have sun.util.calendar.ZoneInfo.
127             // if (zi instanceof ZoneInfo) {
128             //    zoneOffset = ((ZoneInfo)zi).getOffsets(millis, offsets);
129             if (zi instanceof libcore.util.ZoneInfo) {
130                 zoneOffset = ((libcore.util.ZoneInfo) zi).getOffsetsByUtcTime(millis, offsets);
131             // END Android-changed: Android doesn't have sun.util.calendar.ZoneInfo.
132             } else {
133                 zoneOffset = zi.getOffset(millis);
134                 offsets[0] = zi.getRawOffset();
135                 offsets[1] = zoneOffset - offsets[0];
136             }
137 
138             // We need to calculate the given millis and time zone
139             // offset separately for java.util.GregorianCalendar
140             // compatibility. (i.e., millis + zoneOffset could cause
141             // overflow or underflow, which must be avoided.) Usually
142             // days should be 0 and ms is in the range of -13:00 to
143             // +14:00. However, we need to deal with extreme cases.
144             days = zoneOffset / DAY_IN_MILLIS;
145             ms = zoneOffset % DAY_IN_MILLIS;
146             saving = offsets[1];
147         }
148         date.setZoneOffset(zoneOffset);
149         date.setDaylightSaving(saving);
150 
151         days += millis / DAY_IN_MILLIS;
152         ms += (int) (millis % DAY_IN_MILLIS);
153         if (ms >= DAY_IN_MILLIS) {
154             // at most ms is (DAY_IN_MILLIS - 1) * 2.
155             ms -= DAY_IN_MILLIS;
156             ++days;
157         } else {
158             // at most ms is (1 - DAY_IN_MILLIS) * 2. Adding one
159             // DAY_IN_MILLIS results in still negative.
160             while (ms < 0) {
161                 ms += DAY_IN_MILLIS;
162                 --days;
163             }
164         }
165 
166         // convert to fixed date (offset from Jan. 1, 1 (Gregorian))
167         days += EPOCH_OFFSET;
168 
169         // calculate date fields from the fixed date
170         getCalendarDateFromFixedDate(date, days);
171 
172         // calculate time fields from the time of day
173         setTimeOfDay(date, ms);
174         date.setLeapYear(isLeapYear(date));
175         date.setNormalized(true);
176         return date;
177     }
178 
getTime(CalendarDate date)179     public long getTime(CalendarDate date) {
180         long gd = getFixedDate(date);
181         long ms = (gd - EPOCH_OFFSET) * DAY_IN_MILLIS + getTimeOfDay(date);
182         int zoneOffset = 0;
183         TimeZone zi = date.getZone();
184         if (zi != null) {
185             if (date.isNormalized()) {
186                 return ms - date.getZoneOffset();
187             }
188             // adjust time zone and daylight saving
189             int[] offsets = new int[2];
190             if (date.isStandardTime()) {
191                 // 1) 2:30am during starting-DST transition is
192                 //    intrepreted as 2:30am ST
193                 // 2) 5:00pm during DST is still interpreted as 5:00pm ST
194                 // 3) 1:30am during ending-DST transition is interpreted
195                 //    as 1:30am ST (after transition)
196                 // Android-changed: Android doesn't have sun.util.calendar.ZoneInfo.
197                 // if (zi instanceof ZoneInfo) {
198                 //     ((ZoneInfo)zi).getOffsetsByStandard(ms, offsets);
199                 //     zoneOffset = offsets[0];
200                 // } else {
201                     zoneOffset = zi.getOffset(ms - zi.getRawOffset());
202                 // }
203             } else {
204                 // 1) 2:30am during starting-DST transition is
205                 //    intrepreted as 3:30am DT
206                 // 2) 5:00pm during DST is intrepreted as 5:00pm DT
207                 // 3) 1:30am during ending-DST transition is interpreted
208                 //    as 1:30am DT/0:30am ST (before transition)
209                 // Android-changed: Android doesn't have sun.util.calendar.ZoneInfo.
210                 // if (zi instanceof ZoneInfo) {
211                 //     zoneOffset = ((ZoneInfo)zi).getOffsetsByWall(ms, offsets);
212                 // } else {
213                     zoneOffset = zi.getOffset(ms - zi.getRawOffset());
214                 // }
215             }
216         }
217         ms -= zoneOffset;
218         getCalendarDate(ms, date);
219         return ms;
220     }
221 
getTimeOfDay(CalendarDate date)222     protected long getTimeOfDay(CalendarDate date) {
223         long fraction = date.getTimeOfDay();
224         if (fraction != CalendarDate.TIME_UNDEFINED) {
225             return fraction;
226         }
227         fraction = getTimeOfDayValue(date);
228         date.setTimeOfDay(fraction);
229         return fraction;
230     }
231 
getTimeOfDayValue(CalendarDate date)232     public long getTimeOfDayValue(CalendarDate date) {
233         long fraction = date.getHours();
234         fraction *= 60;
235         fraction += date.getMinutes();
236         fraction *= 60;
237         fraction += date.getSeconds();
238         fraction *= 1000;
239         fraction += date.getMillis();
240         return fraction;
241     }
242 
setTimeOfDay(CalendarDate cdate, int fraction)243     public CalendarDate setTimeOfDay(CalendarDate cdate, int fraction) {
244         if (fraction < 0) {
245             throw new IllegalArgumentException();
246         }
247         boolean normalizedState = cdate.isNormalized();
248         int time = fraction;
249         int hours = time / HOUR_IN_MILLIS;
250         time %= HOUR_IN_MILLIS;
251         int minutes = time / MINUTE_IN_MILLIS;
252         time %= MINUTE_IN_MILLIS;
253         int seconds = time / SECOND_IN_MILLIS;
254         time %= SECOND_IN_MILLIS;
255         cdate.setHours(hours);
256         cdate.setMinutes(minutes);
257         cdate.setSeconds(seconds);
258         cdate.setMillis(time);
259         cdate.setTimeOfDay(fraction);
260         if (hours < 24 && normalizedState) {
261             // If this time of day setting doesn't affect the date,
262             // then restore the normalized state.
263             cdate.setNormalized(normalizedState);
264         }
265         return cdate;
266     }
267 
268     /**
269      * Returns 7 in this default implementation.
270      *
271      * @return 7
272      */
getWeekLength()273     public int getWeekLength() {
274         return 7;
275     }
276 
isLeapYear(CalendarDate date)277     protected abstract boolean isLeapYear(CalendarDate date);
278 
getNthDayOfWeek(int nth, int dayOfWeek, CalendarDate date)279     public CalendarDate getNthDayOfWeek(int nth, int dayOfWeek, CalendarDate date) {
280         CalendarDate ndate = (CalendarDate) date.clone();
281         normalize(ndate);
282         long fd = getFixedDate(ndate);
283         long nfd;
284         if (nth > 0) {
285             nfd = 7 * nth + getDayOfWeekDateBefore(fd, dayOfWeek);
286         } else {
287             nfd = 7 * nth + getDayOfWeekDateAfter(fd, dayOfWeek);
288         }
289         getCalendarDateFromFixedDate(ndate, nfd);
290         return ndate;
291     }
292 
293     /**
294      * Returns a date of the given day of week before the given fixed
295      * date.
296      *
297      * @param fixedDate the fixed date
298      * @param dayOfWeek the day of week
299      * @return the calculated date
300      */
getDayOfWeekDateBefore(long fixedDate, int dayOfWeek)301     static long getDayOfWeekDateBefore(long fixedDate, int dayOfWeek) {
302         return getDayOfWeekDateOnOrBefore(fixedDate - 1, dayOfWeek);
303     }
304 
305     /**
306      * Returns a date of the given day of week that is closest to and
307      * after the given fixed date.
308      *
309      * @param fixedDate the fixed date
310      * @param dayOfWeek the day of week
311      * @return the calculated date
312      */
getDayOfWeekDateAfter(long fixedDate, int dayOfWeek)313     static long getDayOfWeekDateAfter(long fixedDate, int dayOfWeek) {
314         return getDayOfWeekDateOnOrBefore(fixedDate + 7, dayOfWeek);
315     }
316 
317     /**
318      * Returns a date of the given day of week on or before the given fixed
319      * date.
320      *
321      * @param fixedDate the fixed date
322      * @param dayOfWeek the day of week
323      * @return the calculated date
324      */
325     // public for java.util.GregorianCalendar
getDayOfWeekDateOnOrBefore(long fixedDate, int dayOfWeek)326     public static long getDayOfWeekDateOnOrBefore(long fixedDate, int dayOfWeek) {
327         long fd = fixedDate - (dayOfWeek - 1);
328         if (fd >= 0) {
329             return fixedDate - (fd % 7);
330         }
331         return fixedDate - CalendarUtils.mod(fd, 7);
332     }
333 
334     /**
335      * Returns the fixed date calculated with the specified calendar
336      * date. If the specified date is not normalized, its date fields
337      * are normalized.
338      *
339      * @param date a <code>CalendarDate</code> with which the fixed
340      * date is calculated
341      * @return the calculated fixed date
342      * @see AbstractCalendar.html#fixed_date
343      */
getFixedDate(CalendarDate date)344     protected abstract long getFixedDate(CalendarDate date);
345 
346     /**
347      * Calculates calendar fields from the specified fixed date. This
348      * method stores the calculated calendar field values in the specified
349      * <code>CalendarDate</code>.
350      *
351      * @param date a <code>CalendarDate</code> to stored the
352      * calculated calendar fields.
353      * @param fixedDate a fixed date to calculate calendar fields
354      * @see AbstractCalendar.html#fixed_date
355      */
getCalendarDateFromFixedDate(CalendarDate date, long fixedDate)356     protected abstract void getCalendarDateFromFixedDate(CalendarDate date,
357                                                          long fixedDate);
358 
validateTime(CalendarDate date)359     public boolean validateTime(CalendarDate date) {
360         int t = date.getHours();
361         if (t < 0 || t >= 24) {
362             return false;
363         }
364         t = date.getMinutes();
365         if (t < 0 || t >= 60) {
366             return false;
367         }
368         t = date.getSeconds();
369         // TODO: Leap second support.
370         if (t < 0 || t >= 60) {
371             return false;
372         }
373         t = date.getMillis();
374         if (t < 0 || t >= 1000) {
375             return false;
376         }
377         return true;
378     }
379 
380 
normalizeTime(CalendarDate date)381     int normalizeTime(CalendarDate date) {
382         long fraction = getTimeOfDay(date);
383         long days = 0;
384 
385         if (fraction >= DAY_IN_MILLIS) {
386             days = fraction / DAY_IN_MILLIS;
387             fraction %= DAY_IN_MILLIS;
388         } else if (fraction < 0) {
389             days = CalendarUtils.floorDivide(fraction, DAY_IN_MILLIS);
390             if (days != 0) {
391                 fraction -= DAY_IN_MILLIS * days; // mod(fraction, DAY_IN_MILLIS)
392             }
393         }
394         if (days != 0) {
395             date.setTimeOfDay(fraction);
396         }
397         date.setMillis((int)(fraction % 1000));
398         fraction /= 1000;
399         date.setSeconds((int)(fraction % 60));
400         fraction /= 60;
401         date.setMinutes((int)(fraction % 60));
402         date.setHours((int)(fraction / 60));
403         return (int)days;
404     }
405 }
406