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 (int i = 0; i < eras.length; i++) {
68                 if (eras[i].equals(eraName)) {
69                     return eras[i];
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             zoneOffset = zi.getOffset(millis);
127             offsets[0] = zi.getRawOffset();
128             offsets[1] = zoneOffset - offsets[0];
129 
130             // We need to calculate the given millis and time zone
131             // offset separately for java.util.GregorianCalendar
132             // compatibility. (i.e., millis + zoneOffset could cause
133             // overflow or underflow, which must be avoided.) Usually
134             // days should be 0 and ms is in the range of -13:00 to
135             // +14:00. However, we need to deal with extreme cases.
136             days = zoneOffset / DAY_IN_MILLIS;
137             ms = zoneOffset % DAY_IN_MILLIS;
138             saving = offsets[1];
139         }
140         date.setZoneOffset(zoneOffset);
141         date.setDaylightSaving(saving);
142 
143         days += millis / DAY_IN_MILLIS;
144         ms += (int) (millis % DAY_IN_MILLIS);
145         if (ms >= DAY_IN_MILLIS) {
146             // at most ms is (DAY_IN_MILLIS - 1) * 2.
147             ms -= DAY_IN_MILLIS;
148             ++days;
149         } else {
150             // at most ms is (1 - DAY_IN_MILLIS) * 2. Adding one
151             // DAY_IN_MILLIS results in still negative.
152             while (ms < 0) {
153                 ms += DAY_IN_MILLIS;
154                 --days;
155             }
156         }
157 
158         // convert to fixed date (offset from Jan. 1, 1 (Gregorian))
159         days += EPOCH_OFFSET;
160 
161         // calculate date fields from the fixed date
162         getCalendarDateFromFixedDate(date, days);
163 
164         // calculate time fields from the time of day
165         setTimeOfDay(date, ms);
166         date.setLeapYear(isLeapYear(date));
167         date.setNormalized(true);
168         return date;
169     }
170 
getTime(CalendarDate date)171     public long getTime(CalendarDate date) {
172         long gd = getFixedDate(date);
173         long ms = (gd - EPOCH_OFFSET) * DAY_IN_MILLIS + getTimeOfDay(date);
174         int zoneOffset = 0;
175         TimeZone zi = date.getZone();
176         if (zi != null) {
177             if (date.isNormalized()) {
178                 return ms - date.getZoneOffset();
179             }
180             // adjust time zone and daylight saving
181             int[] offsets = new int[2];
182             if (date.isStandardTime()) {
183                 // 1) 2:30am during starting-DST transition is
184                 //    intrepreted as 2:30am ST
185                 // 2) 5:00pm during DST is still interpreted as 5:00pm ST
186                 // 3) 1:30am during ending-DST transition is interpreted
187                 //    as 1:30am ST (after transition)
188                 zoneOffset = zi.getOffset(ms - zi.getRawOffset());
189             } else {
190                 // 1) 2:30am during starting-DST transition is
191                 //    intrepreted as 3:30am DT
192                 // 2) 5:00pm during DST is intrepreted as 5:00pm DT
193                 // 3) 1:30am during ending-DST transition is interpreted
194                 //    as 1:30am DT/0:30am ST (before transition)
195                 zoneOffset = zi.getOffset(ms - zi.getRawOffset());
196             }
197         }
198         ms -= zoneOffset;
199         getCalendarDate(ms, date);
200         return ms;
201     }
202 
getTimeOfDay(CalendarDate date)203     protected long getTimeOfDay(CalendarDate date) {
204         long fraction = date.getTimeOfDay();
205         if (fraction != CalendarDate.TIME_UNDEFINED) {
206             return fraction;
207         }
208         fraction = getTimeOfDayValue(date);
209         date.setTimeOfDay(fraction);
210         return fraction;
211     }
212 
getTimeOfDayValue(CalendarDate date)213     public long getTimeOfDayValue(CalendarDate date) {
214         long fraction = date.getHours();
215         fraction *= 60;
216         fraction += date.getMinutes();
217         fraction *= 60;
218         fraction += date.getSeconds();
219         fraction *= 1000;
220         fraction += date.getMillis();
221         return fraction;
222     }
223 
setTimeOfDay(CalendarDate cdate, int fraction)224     public CalendarDate setTimeOfDay(CalendarDate cdate, int fraction) {
225         if (fraction < 0) {
226             throw new IllegalArgumentException();
227         }
228         boolean normalizedState = cdate.isNormalized();
229         int time = fraction;
230         int hours = time / HOUR_IN_MILLIS;
231         time %= HOUR_IN_MILLIS;
232         int minutes = time / MINUTE_IN_MILLIS;
233         time %= MINUTE_IN_MILLIS;
234         int seconds = time / SECOND_IN_MILLIS;
235         time %= SECOND_IN_MILLIS;
236         cdate.setHours(hours);
237         cdate.setMinutes(minutes);
238         cdate.setSeconds(seconds);
239         cdate.setMillis(time);
240         cdate.setTimeOfDay(fraction);
241         if (hours < 24 && normalizedState) {
242             // If this time of day setting doesn't affect the date,
243             // then restore the normalized state.
244             cdate.setNormalized(normalizedState);
245         }
246         return cdate;
247     }
248 
249     /**
250      * Returns 7 in this default implementation.
251      *
252      * @return 7
253      */
getWeekLength()254     public int getWeekLength() {
255         return 7;
256     }
257 
isLeapYear(CalendarDate date)258     protected abstract boolean isLeapYear(CalendarDate date);
259 
getNthDayOfWeek(int nth, int dayOfWeek, CalendarDate date)260     public CalendarDate getNthDayOfWeek(int nth, int dayOfWeek, CalendarDate date) {
261         CalendarDate ndate = (CalendarDate) date.clone();
262         normalize(ndate);
263         long fd = getFixedDate(ndate);
264         long nfd;
265         if (nth > 0) {
266             nfd = 7 * nth + getDayOfWeekDateBefore(fd, dayOfWeek);
267         } else {
268             nfd = 7 * nth + getDayOfWeekDateAfter(fd, dayOfWeek);
269         }
270         getCalendarDateFromFixedDate(ndate, nfd);
271         return ndate;
272     }
273 
274     /**
275      * Returns a date of the given day of week before the given fixed
276      * date.
277      *
278      * @param fixedDate the fixed date
279      * @param dayOfWeek the day of week
280      * @return the calculated date
281      */
getDayOfWeekDateBefore(long fixedDate, int dayOfWeek)282     static long getDayOfWeekDateBefore(long fixedDate, int dayOfWeek) {
283         return getDayOfWeekDateOnOrBefore(fixedDate - 1, dayOfWeek);
284     }
285 
286     /**
287      * Returns a date of the given day of week that is closest to and
288      * after the given fixed date.
289      *
290      * @param fixedDate the fixed date
291      * @param dayOfWeek the day of week
292      * @return the calculated date
293      */
getDayOfWeekDateAfter(long fixedDate, int dayOfWeek)294     static long getDayOfWeekDateAfter(long fixedDate, int dayOfWeek) {
295         return getDayOfWeekDateOnOrBefore(fixedDate + 7, dayOfWeek);
296     }
297 
298     /**
299      * Returns a date of the given day of week on or before the given fixed
300      * date.
301      *
302      * @param fixedDate the fixed date
303      * @param dayOfWeek the day of week
304      * @return the calculated date
305      */
306     // public for java.util.GregorianCalendar
getDayOfWeekDateOnOrBefore(long fixedDate, int dayOfWeek)307     public static long getDayOfWeekDateOnOrBefore(long fixedDate, int dayOfWeek) {
308         long fd = fixedDate - (dayOfWeek - 1);
309         if (fd >= 0) {
310             return fixedDate - (fd % 7);
311         }
312         return fixedDate - CalendarUtils.mod(fd, 7);
313     }
314 
315     /**
316      * Returns the fixed date calculated with the specified calendar
317      * date. If the specified date is not normalized, its date fields
318      * are normalized.
319      *
320      * @param date a <code>CalendarDate</code> with which the fixed
321      * date is calculated
322      * @return the calculated fixed date
323      * @see AbstractCalendar.html#fixed_date
324      */
getFixedDate(CalendarDate date)325     protected abstract long getFixedDate(CalendarDate date);
326 
327     /**
328      * Calculates calendar fields from the specified fixed date. This
329      * method stores the calculated calendar field values in the specified
330      * <code>CalendarDate</code>.
331      *
332      * @param date a <code>CalendarDate</code> to stored the
333      * calculated calendar fields.
334      * @param fixedDate a fixed date to calculate calendar fields
335      * @see AbstractCalendar.html#fixed_date
336      */
getCalendarDateFromFixedDate(CalendarDate date, long fixedDate)337     protected abstract void getCalendarDateFromFixedDate(CalendarDate date,
338                                                          long fixedDate);
339 
validateTime(CalendarDate date)340     public boolean validateTime(CalendarDate date) {
341         int t = date.getHours();
342         if (t < 0 || t >= 24) {
343             return false;
344         }
345         t = date.getMinutes();
346         if (t < 0 || t >= 60) {
347             return false;
348         }
349         t = date.getSeconds();
350         // TODO: Leap second support.
351         if (t < 0 || t >= 60) {
352             return false;
353         }
354         t = date.getMillis();
355         if (t < 0 || t >= 1000) {
356             return false;
357         }
358         return true;
359     }
360 
361 
normalizeTime(CalendarDate date)362     int normalizeTime(CalendarDate date) {
363         long fraction = getTimeOfDay(date);
364         long days = 0;
365 
366         if (fraction >= DAY_IN_MILLIS) {
367             days = fraction / DAY_IN_MILLIS;
368             fraction %= DAY_IN_MILLIS;
369         } else if (fraction < 0) {
370             days = CalendarUtils.floorDivide(fraction, DAY_IN_MILLIS);
371             if (days != 0) {
372                 fraction -= DAY_IN_MILLIS * days; // mod(fraction, DAY_IN_MILLIS)
373             }
374         }
375         if (days != 0) {
376             date.setTimeOfDay(fraction);
377         }
378         date.setMillis((int)(fraction % 1000));
379         fraction /= 1000;
380         date.setSeconds((int)(fraction % 60));
381         fraction /= 60;
382         date.setMinutes((int)(fraction % 60));
383         date.setHours((int)(fraction / 60));
384         return (int)days;
385     }
386 }
387