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
27package sun.util.calendar;
28
29import java.util.Locale;
30import 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
49public 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
62    protected AbstractCalendar() {
63    }
64
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
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
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
99    protected void setEras(Era[] eras) {
100        this.eras = eras;
101    }
102
103    public CalendarDate getCalendarDate() {
104        return getCalendarDate(System.currentTimeMillis(), newCalendarDate());
105    }
106
107    public CalendarDate getCalendarDate(long millis) {
108        return getCalendarDate(millis, newCalendarDate());
109    }
110
111    public CalendarDate getCalendarDate(long millis, TimeZone zone) {
112        CalendarDate date = newCalendarDate(zone);
113        return getCalendarDate(millis, date);
114    }
115
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
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
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
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
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     */
254    public int getWeekLength() {
255        return 7;
256    }
257
258    protected abstract boolean isLeapYear(CalendarDate date);
259
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     */
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     */
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
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     */
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     */
337    protected abstract void getCalendarDateFromFixedDate(CalendarDate date,
338                                                         long fixedDate);
339
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
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