1 /* 2 * Copyright (c) 2003, 2011, Oracle and/or its affiliates. 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. Oracle designates this 8 * particular file as subject to the "Classpath" exception as provided 9 * by Oracle 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 Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA 22 * or visit www.oracle.com if you need additional information or have any 23 * 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 { Date()145 protected Date() { 146 super(); 147 } Date(TimeZone zone)148 protected Date(TimeZone zone) { 149 super(zone); 150 } 151 setNormalizedDate(int normalizedYear, int month, int dayOfMonth)152 public Date setNormalizedDate(int normalizedYear, int month, int dayOfMonth) { 153 setNormalizedYear(normalizedYear); 154 setMonth(month).setDayOfMonth(dayOfMonth); 155 return this; 156 } 157 getNormalizedYear()158 public abstract int getNormalizedYear(); 159 setNormalizedYear(int normalizedYear)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 hit(int year)169 protected final boolean hit(int year) { 170 return year == cachedYear; 171 } 172 hit(long fixedDate)173 protected final boolean hit(long fixedDate) { 174 return (fixedDate >= cachedFixedDateJan1 && 175 fixedDate < cachedFixedDateNextJan1); 176 } getCachedYear()177 protected int getCachedYear() { 178 return cachedYear; 179 } 180 getCachedJan1()181 protected long getCachedJan1() { 182 return cachedFixedDateJan1; 183 } 184 setCache(int year, long jan1, int len)185 protected void setCache(int year, long jan1, int len) { 186 cachedYear = year; 187 cachedFixedDateJan1 = jan1; 188 cachedFixedDateNextJan1 = jan1 + len; 189 } 190 } 191 validate(CalendarDate date)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 != Date.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 normalize(CalendarDate date)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 normalizeMonth(CalendarDate date)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 */ getYearLength(CalendarDate date)303 public int getYearLength(CalendarDate date) { 304 return isLeapYear(((Date)date).getNormalizedYear()) ? 366 : 365; 305 } 306 getYearLengthInMonths(CalendarDate date)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 getMonthLength(CalendarDate date)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. getMonthLength(int year, int month)332 private 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 getDayOfYear(CalendarDate date)340 public long getDayOfYear(CalendarDate date) { 341 return getDayOfYear(((Date)date).getNormalizedYear(), 342 date.getMonth(), 343 date.getDayOfMonth()); 344 } 345 getDayOfYear(int year, int month, int dayOfMonth)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 getFixedDate(CalendarDate date)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 getFixedDate(int year, int month, int dayOfMonth, BaseCalendar.Date cache)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' getCalendarDateFromFixedDate(CalendarDate date, long fixedDate)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 */ getDayOfWeek(CalendarDate date)472 public int getDayOfWeek(CalendarDate date) { 473 long fixedDate = getFixedDate(date); 474 return getDayOfWeekFromFixedDate(fixedDate); 475 } 476 getDayOfWeekFromFixedDate(long fixedDate)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 getYearFromFixedDate(long fixedDate)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 */ getGregorianYearFromFixedDate(long fixedDate)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 */ isLeapYear(CalendarDate date)531 protected boolean isLeapYear(CalendarDate date) { 532 return isLeapYear(((Date)date).getNormalizedYear()); 533 } 534 isLeapYear(int normalizedYear)535 boolean isLeapYear(int normalizedYear) { 536 return CalendarUtils.isGregorianLeapYear(normalizedYear); 537 } 538 } 539