J. Duke | 319a3b9 | 2007-12-01 00:00:00 +0000 | [diff] [blame^] | 1 | /* |
| 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 | |
| 26 | package sun.util.calendar; |
| 27 | |
| 28 | import java.util.Locale; |
| 29 | import 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 | |
| 40 | public 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 | } |