blob: 489a83e03f6827ef299612ca481000fd58371463 [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>BaseCalendar</code> provides basic calendar calculation
33 * functions to support the Julian, Gregorian, and Gregorian-based
34 * calendar systems.
35 *
36 * @author Masayoshi Okutsu
37 * @since 1.5
38 */
39
40public abstract class BaseCalendar extends AbstractCalendar {
41
42 public static final int JANUARY = 1;
43 public static final int FEBRUARY = 2;
44 public static final int MARCH = 3;
45 public static final int APRIL = 4;
46 public static final int MAY = 5;
47 public static final int JUNE = 6;
48 public static final int JULY = 7;
49 public static final int AUGUST = 8;
50 public static final int SEPTEMBER = 9;
51 public static final int OCTOBER = 10;
52 public static final int NOVEMBER = 11;
53 public static final int DECEMBER = 12;
54
55 // day of week constants
56 public static final int SUNDAY = 1;
57 public static final int MONDAY = 2;
58 public static final int TUESDAY = 3;
59 public static final int WEDNESDAY = 4;
60 public static final int THURSDAY = 5;
61 public static final int FRIDAY = 6;
62 public static final int SATURDAY = 7;
63
64 // The base Gregorian year of FIXED_DATES[]
65 private static final int BASE_YEAR = 1970;
66
67 // Pre-calculated fixed dates of January 1 from BASE_YEAR
68 // (Gregorian). This table covers all the years that can be
69 // supported by the POSIX time_t (32-bit) after the Epoch. Note
70 // that the data type is int[].
71 private static final int[] FIXED_DATES = {
72 719163, // 1970
73 719528, // 1971
74 719893, // 1972
75 720259, // 1973
76 720624, // 1974
77 720989, // 1975
78 721354, // 1976
79 721720, // 1977
80 722085, // 1978
81 722450, // 1979
82 722815, // 1980
83 723181, // 1981
84 723546, // 1982
85 723911, // 1983
86 724276, // 1984
87 724642, // 1985
88 725007, // 1986
89 725372, // 1987
90 725737, // 1988
91 726103, // 1989
92 726468, // 1990
93 726833, // 1991
94 727198, // 1992
95 727564, // 1993
96 727929, // 1994
97 728294, // 1995
98 728659, // 1996
99 729025, // 1997
100 729390, // 1998
101 729755, // 1999
102 730120, // 2000
103 730486, // 2001
104 730851, // 2002
105 731216, // 2003
106 731581, // 2004
107 731947, // 2005
108 732312, // 2006
109 732677, // 2007
110 733042, // 2008
111 733408, // 2009
112 733773, // 2010
113 734138, // 2011
114 734503, // 2012
115 734869, // 2013
116 735234, // 2014
117 735599, // 2015
118 735964, // 2016
119 736330, // 2017
120 736695, // 2018
121 737060, // 2019
122 737425, // 2020
123 737791, // 2021
124 738156, // 2022
125 738521, // 2023
126 738886, // 2024
127 739252, // 2025
128 739617, // 2026
129 739982, // 2027
130 740347, // 2028
131 740713, // 2029
132 741078, // 2030
133 741443, // 2031
134 741808, // 2032
135 742174, // 2033
136 742539, // 2034
137 742904, // 2035
138 743269, // 2036
139 743635, // 2037
140 744000, // 2038
141 744365, // 2039
142 };
143
144 public abstract static class Date extends CalendarDate {
145 protected Date() {
146 super();
147 }
148 protected Date(TimeZone zone) {
149 super(zone);
150 }
151
152 public Date setNormalizedDate(int normalizedYear, int month, int dayOfMonth) {
153 setNormalizedYear(normalizedYear);
154 setMonth(month).setDayOfMonth(dayOfMonth);
155 return this;
156 }
157
158 public abstract int getNormalizedYear();
159
160 public abstract void setNormalizedYear(int normalizedYear);
161
162 // Cache for the fixed date of January 1 and year length of the
163 // cachedYear. A simple benchmark showed 7% performance
164 // improvement with >90% cache hit. The initial values are for Gregorian.
165 int cachedYear = 2004;
166 long cachedFixedDateJan1 = 731581L;
167 long cachedFixedDateNextJan1 = cachedFixedDateJan1 + 366;
168
169 protected final boolean hit(int year) {
170 return year == cachedYear;
171 }
172
173 protected final boolean hit(long fixedDate) {
174 return (fixedDate >= cachedFixedDateJan1 &&
175 fixedDate < cachedFixedDateNextJan1);
176 }
177 protected int getCachedYear() {
178 return cachedYear;
179 }
180
181 protected long getCachedJan1() {
182 return cachedFixedDateJan1;
183 }
184
185 protected void setCache(int year, long jan1, int len) {
186 cachedYear = year;
187 cachedFixedDateJan1 = jan1;
188 cachedFixedDateNextJan1 = jan1 + len;
189 }
190 }
191
192 public boolean validate(CalendarDate date) {
193 Date bdate = (Date) date;
194 if (bdate.isNormalized()) {
195 return true;
196 }
197 int month = bdate.getMonth();
198 if (month < JANUARY || month > DECEMBER) {
199 return false;
200 }
201 int d = bdate.getDayOfMonth();
202 if (d <= 0 || d > getMonthLength(bdate.getNormalizedYear(), month)) {
203 return false;
204 }
205 int dow = bdate.getDayOfWeek();
206 if (dow != bdate.FIELD_UNDEFINED && dow != getDayOfWeek(bdate)) {
207 return false;
208 }
209
210 if (!validateTime(date)) {
211 return false;
212 }
213
214 bdate.setNormalized(true);
215 return true;
216 }
217
218 public boolean normalize(CalendarDate date) {
219 if (date.isNormalized()) {
220 return true;
221 }
222
223 Date bdate = (Date) date;
224 TimeZone zi = bdate.getZone();
225
226 // If the date has a time zone, then we need to recalculate
227 // the calendar fields. Let getTime() do it.
228 if (zi != null) {
229 getTime(date);
230 return true;
231 }
232
233 int days = normalizeTime(bdate);
234 normalizeMonth(bdate);
235 long d = (long)bdate.getDayOfMonth() + days;
236 int m = bdate.getMonth();
237 int y = bdate.getNormalizedYear();
238 int ml = getMonthLength(y, m);
239
240 if (!(d > 0 && d <= ml)) {
241 if (d <= 0 && d > -28) {
242 ml = getMonthLength(y, --m);
243 d += ml;
244 bdate.setDayOfMonth((int) d);
245 if (m == 0) {
246 m = DECEMBER;
247 bdate.setNormalizedYear(y - 1);
248 }
249 bdate.setMonth(m);
250 } else if (d > ml && d < (ml + 28)) {
251 d -= ml;
252 ++m;
253 bdate.setDayOfMonth((int)d);
254 if (m > DECEMBER) {
255 bdate.setNormalizedYear(y + 1);
256 m = JANUARY;
257 }
258 bdate.setMonth(m);
259 } else {
260 long fixedDate = d + getFixedDate(y, m, 1, bdate) - 1L;
261 getCalendarDateFromFixedDate(bdate, fixedDate);
262 }
263 } else {
264 bdate.setDayOfWeek(getDayOfWeek(bdate));
265 }
266 date.setLeapYear(isLeapYear(bdate.getNormalizedYear()));
267 date.setZoneOffset(0);
268 date.setDaylightSaving(0);
269 bdate.setNormalized(true);
270 return true;
271 }
272
273 void normalizeMonth(CalendarDate date) {
274 Date bdate = (Date) date;
275 int year = bdate.getNormalizedYear();
276 long month = bdate.getMonth();
277 if (month <= 0) {
278 long xm = 1L - month;
279 year -= (int)((xm / 12) + 1);
280 month = 13 - (xm % 12);
281 bdate.setNormalizedYear(year);
282 bdate.setMonth((int) month);
283 } else if (month > DECEMBER) {
284 year += (int)((month - 1) / 12);
285 month = ((month - 1)) % 12 + 1;
286 bdate.setNormalizedYear(year);
287 bdate.setMonth((int) month);
288 }
289 }
290
291 /**
292 * Returns 366 if the specified date is in a leap year, or 365
293 * otherwise This method does not perform the normalization with
294 * the specified <code>CalendarDate</code>. The
295 * <code>CalendarDate</code> must be normalized to get a correct
296 * value.
297 *
298 * @param a <code>CalendarDate</code>
299 * @return a year length in days
300 * @throws ClassCastException if the specified date is not a
301 * {@link BaseCalendar.Date}
302 */
303 public int getYearLength(CalendarDate date) {
304 return isLeapYear(((Date)date).getNormalizedYear()) ? 366 : 365;
305 }
306
307 public int getYearLengthInMonths(CalendarDate date) {
308 return 12;
309 }
310
311 static final int[] DAYS_IN_MONTH
312 // 12 1 2 3 4 5 6 7 8 9 10 11 12
313 = { 31, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31};
314 static final int[] ACCUMULATED_DAYS_IN_MONTH
315 // 12/1 1/1 2/1 3/1 4/1 5/1 6/1 7/1 8/1 9/1 10/1 11/1 12/1
316 = { -30, 0, 31, 59, 90,120,151,181,212,243, 273, 304, 334};
317
318 static final int[] ACCUMULATED_DAYS_IN_MONTH_LEAP
319 // 12/1 1/1 2/1 3/1 4/1 5/1 6/1 7/1 8/1 9/1 10/1 11/1 12/1
320 = { -30, 0, 31, 59+1, 90+1,120+1,151+1,181+1,212+1,243+1, 273+1, 304+1, 334+1};
321
322 public int getMonthLength(CalendarDate date) {
323 Date gdate = (Date) date;
324 int month = gdate.getMonth();
325 if (month < JANUARY || month > DECEMBER) {
326 throw new IllegalArgumentException("Illegal month value: " + month);
327 }
328 return getMonthLength(gdate.getNormalizedYear(), month);
329 }
330
331 // accepts 0 (December in the previous year) to 12.
332 private final int getMonthLength(int year, int month) {
333 int days = DAYS_IN_MONTH[month];
334 if (month == FEBRUARY && isLeapYear(year)) {
335 days++;
336 }
337 return days;
338 }
339
340 public long getDayOfYear(CalendarDate date) {
341 return getDayOfYear(((Date)date).getNormalizedYear(),
342 date.getMonth(),
343 date.getDayOfMonth());
344 }
345
346 final long getDayOfYear(int year, int month, int dayOfMonth) {
347 return (long) dayOfMonth
348 + (isLeapYear(year) ?
349 ACCUMULATED_DAYS_IN_MONTH_LEAP[month] : ACCUMULATED_DAYS_IN_MONTH[month]);
350 }
351
352 // protected
353 public long getFixedDate(CalendarDate date) {
354 if (!date.isNormalized()) {
355 normalizeMonth(date);
356 }
357 return getFixedDate(((Date)date).getNormalizedYear(),
358 date.getMonth(),
359 date.getDayOfMonth(),
360 (BaseCalendar.Date) date);
361 }
362
363 // public for java.util.GregorianCalendar
364 public long getFixedDate(int year, int month, int dayOfMonth, BaseCalendar.Date cache) {
365 boolean isJan1 = month == JANUARY && dayOfMonth == 1;
366
367 // Look up the one year cache
368 if (cache != null && cache.hit(year)) {
369 if (isJan1) {
370 return cache.getCachedJan1();
371 }
372 return cache.getCachedJan1() + getDayOfYear(year, month, dayOfMonth) - 1;
373 }
374
375 // Look up the pre-calculated fixed date table
376 int n = year - BASE_YEAR;
377 if (n >= 0 && n < FIXED_DATES.length) {
378 long jan1 = FIXED_DATES[n];
379 if (cache != null) {
380 cache.setCache(year, jan1, isLeapYear(year) ? 366 : 365);
381 }
382 return isJan1 ? jan1 : jan1 + getDayOfYear(year, month, dayOfMonth) - 1;
383 }
384
385 long prevyear = (long)year - 1;
386 long days = dayOfMonth;
387
388 if (prevyear >= 0) {
389 days += (365 * prevyear)
390 + (prevyear / 4)
391 - (prevyear / 100)
392 + (prevyear / 400)
393 + ((367 * month - 362) / 12);
394 } else {
395 days += (365 * prevyear)
396 + CalendarUtils.floorDivide(prevyear, 4)
397 - CalendarUtils.floorDivide(prevyear, 100)
398 + CalendarUtils.floorDivide(prevyear, 400)
399 + CalendarUtils.floorDivide((367 * month - 362), 12);
400 }
401
402 if (month > FEBRUARY) {
403 days -= isLeapYear(year) ? 1 : 2;
404 }
405
406 // If it's January 1, update the cache.
407 if (cache != null && isJan1) {
408 cache.setCache(year, days, isLeapYear(year) ? 366 : 365);
409 }
410
411 return days;
412 }
413
414 /**
415 * Calculates calendar fields and store them in the specified
416 * <code>CalendarDate</code>.
417 */
418 // should be 'protected'
419 public void getCalendarDateFromFixedDate(CalendarDate date,
420 long fixedDate) {
421 Date gdate = (Date) date;
422 int year;
423 long jan1;
424 boolean isLeap;
425 if (gdate.hit(fixedDate)) {
426 year = gdate.getCachedYear();
427 jan1 = gdate.getCachedJan1();
428 isLeap = isLeapYear(year);
429 } else {
430 // Looking up FIXED_DATES[] here didn't improve performance
431 // much. So we calculate year and jan1. getFixedDate()
432 // will look up FIXED_DATES[] actually.
433 year = getGregorianYearFromFixedDate(fixedDate);
434 jan1 = getFixedDate(year, JANUARY, 1, null);
435 isLeap = isLeapYear(year);
436 // Update the cache data
437 gdate.setCache (year, jan1, isLeap ? 366 : 365);
438 }
439
440 int priorDays = (int)(fixedDate - jan1);
441 long mar1 = jan1 + 31 + 28;
442 if (isLeap) {
443 ++mar1;
444 }
445 if (fixedDate >= mar1) {
446 priorDays += isLeap ? 1 : 2;
447 }
448 int month = 12 * priorDays + 373;
449 if (month > 0) {
450 month /= 367;
451 } else {
452 month = CalendarUtils.floorDivide(month, 367);
453 }
454 long month1 = jan1 + ACCUMULATED_DAYS_IN_MONTH[month];
455 if (isLeap && month >= MARCH) {
456 ++month1;
457 }
458 int dayOfMonth = (int)(fixedDate - month1) + 1;
459 int dayOfWeek = getDayOfWeekFromFixedDate(fixedDate);
460 assert dayOfWeek > 0 : "negative day of week " + dayOfWeek;
461 gdate.setNormalizedYear(year);
462 gdate.setMonth(month);
463 gdate.setDayOfMonth(dayOfMonth);
464 gdate.setDayOfWeek(dayOfWeek);
465 gdate.setLeapYear(isLeap);
466 gdate.setNormalized(true);
467 }
468
469 /**
470 * Returns the day of week of the given Gregorian date.
471 */
472 public int getDayOfWeek(CalendarDate date) {
473 long fixedDate = getFixedDate(date);
474 return getDayOfWeekFromFixedDate(fixedDate);
475 }
476
477 public static final int getDayOfWeekFromFixedDate(long fixedDate) {
478 // The fixed day 1 (January 1, 1 Gregorian) is Monday.
479 if (fixedDate >= 0) {
480 return (int)(fixedDate % 7) + SUNDAY;
481 }
482 return (int)CalendarUtils.mod(fixedDate, 7) + SUNDAY;
483 }
484
485 public int getYearFromFixedDate(long fixedDate) {
486 return getGregorianYearFromFixedDate(fixedDate);
487 }
488
489 /**
490 * Returns the Gregorian year number of the given fixed date.
491 */
492 final int getGregorianYearFromFixedDate(long fixedDate) {
493 long d0;
494 int d1, d2, d3, d4;
495 int n400, n100, n4, n1;
496 int year;
497
498 if (fixedDate > 0) {
499 d0 = fixedDate - 1;
500 n400 = (int)(d0 / 146097);
501 d1 = (int)(d0 % 146097);
502 n100 = d1 / 36524;
503 d2 = d1 % 36524;
504 n4 = d2 / 1461;
505 d3 = d2 % 1461;
506 n1 = d3 / 365;
507 d4 = (d3 % 365) + 1;
508 } else {
509 d0 = fixedDate - 1;
510 n400 = (int)CalendarUtils.floorDivide(d0, 146097L);
511 d1 = (int)CalendarUtils.mod(d0, 146097L);
512 n100 = CalendarUtils.floorDivide(d1, 36524);
513 d2 = CalendarUtils.mod(d1, 36524);
514 n4 = CalendarUtils.floorDivide(d2, 1461);
515 d3 = CalendarUtils.mod(d2, 1461);
516 n1 = CalendarUtils.floorDivide(d3, 365);
517 d4 = CalendarUtils.mod(d3, 365) + 1;
518 }
519 year = 400 * n400 + 100 * n100 + 4 * n4 + n1;
520 if (!(n100 == 4 || n1 == 4)) {
521 ++year;
522 }
523 return year;
524 }
525
526 /**
527 * @return true if the specified year is a Gregorian leap year, or
528 * false otherwise.
529 * @see BaseCalendar#isGregorianLeapYear
530 */
531 protected boolean isLeapYear(CalendarDate date) {
532 return isLeapYear(((Date)date).getNormalizedYear());
533 }
534
535 boolean isLeapYear(int normalizedYear) {
536 return CalendarUtils.isGregorianLeapYear(normalizedYear);
537 }
538}