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