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