1 /* 2 * Copyright (c) 2003, 2021, 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} 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 if (month == 13) { 281 year++; 282 month = 1; 283 } 284 bdate.setNormalizedYear(year); 285 bdate.setMonth((int) month); 286 } else if (month > DECEMBER) { 287 year += (int)((month - 1) / 12); 288 month = ((month - 1)) % 12 + 1; 289 bdate.setNormalizedYear(year); 290 bdate.setMonth((int) month); 291 } 292 } 293 294 /** 295 * Returns 366 if the specified date is in a leap year, or 365 296 * otherwise This method does not perform the normalization with 297 * the specified {@code CalendarDate}. The 298 * {@code CalendarDate} must be normalized to get a correct 299 * value. 300 * 301 * @param date a {@code CalendarDate} 302 * @return a year length in days 303 * @throws ClassCastException if the specified date is not a 304 * {@link BaseCalendar.Date} 305 */ getYearLength(CalendarDate date)306 public int getYearLength(CalendarDate date) { 307 return isLeapYear(((Date)date).getNormalizedYear()) ? 366 : 365; 308 } 309 getYearLengthInMonths(CalendarDate date)310 public int getYearLengthInMonths(CalendarDate date) { 311 return 12; 312 } 313 314 static final int[] DAYS_IN_MONTH 315 // 12 1 2 3 4 5 6 7 8 9 10 11 12 316 = { 31, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31}; 317 static final int[] ACCUMULATED_DAYS_IN_MONTH 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, 90,120,151,181,212,243, 273, 304, 334}; 320 321 static final int[] ACCUMULATED_DAYS_IN_MONTH_LEAP 322 // 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 323 = { -30, 0, 31, 59+1, 90+1,120+1,151+1,181+1,212+1,243+1, 273+1, 304+1, 334+1}; 324 getMonthLength(CalendarDate date)325 public int getMonthLength(CalendarDate date) { 326 Date gdate = (Date) date; 327 int month = gdate.getMonth(); 328 if (month < JANUARY || month > DECEMBER) { 329 throw new IllegalArgumentException("Illegal month value: " + month); 330 } 331 return getMonthLength(gdate.getNormalizedYear(), month); 332 } 333 334 // accepts 0 (December in the previous year) to 12. getMonthLength(int year, int month)335 private int getMonthLength(int year, int month) { 336 int days = DAYS_IN_MONTH[month]; 337 if (month == FEBRUARY && isLeapYear(year)) { 338 days++; 339 } 340 return days; 341 } 342 getDayOfYear(CalendarDate date)343 public long getDayOfYear(CalendarDate date) { 344 return getDayOfYear(((Date)date).getNormalizedYear(), 345 date.getMonth(), 346 date.getDayOfMonth()); 347 } 348 getDayOfYear(int year, int month, int dayOfMonth)349 final long getDayOfYear(int year, int month, int dayOfMonth) { 350 return (long) dayOfMonth 351 + (isLeapYear(year) ? 352 ACCUMULATED_DAYS_IN_MONTH_LEAP[month] : ACCUMULATED_DAYS_IN_MONTH[month]); 353 } 354 355 // protected getFixedDate(CalendarDate date)356 public long getFixedDate(CalendarDate date) { 357 if (!date.isNormalized()) { 358 normalizeMonth(date); 359 } 360 return getFixedDate(((Date)date).getNormalizedYear(), 361 date.getMonth(), 362 date.getDayOfMonth(), 363 (BaseCalendar.Date) date); 364 } 365 366 // public for java.util.GregorianCalendar getFixedDate(int year, int month, int dayOfMonth, BaseCalendar.Date cache)367 public long getFixedDate(int year, int month, int dayOfMonth, BaseCalendar.Date cache) { 368 boolean isJan1 = month == JANUARY && dayOfMonth == 1; 369 370 // Look up the one year cache 371 if (cache != null && cache.hit(year)) { 372 if (isJan1) { 373 return cache.getCachedJan1(); 374 } 375 return cache.getCachedJan1() + getDayOfYear(year, month, dayOfMonth) - 1; 376 } 377 378 // Look up the pre-calculated fixed date table 379 int n = year - BASE_YEAR; 380 if (n >= 0 && n < FIXED_DATES.length) { 381 long jan1 = FIXED_DATES[n]; 382 if (cache != null) { 383 cache.setCache(year, jan1, isLeapYear(year) ? 366 : 365); 384 } 385 return isJan1 ? jan1 : jan1 + getDayOfYear(year, month, dayOfMonth) - 1; 386 } 387 388 long prevyear = (long)year - 1; 389 long days = dayOfMonth; 390 391 if (prevyear >= 0) { 392 days += (365 * prevyear) 393 + (prevyear / 4) 394 - (prevyear / 100) 395 + (prevyear / 400) 396 + ((367 * month - 362) / 12); 397 } else { 398 days += (365 * prevyear) 399 + CalendarUtils.floorDivide(prevyear, 4) 400 - CalendarUtils.floorDivide(prevyear, 100) 401 + CalendarUtils.floorDivide(prevyear, 400) 402 + CalendarUtils.floorDivide((367 * month - 362), 12); 403 } 404 405 if (month > FEBRUARY) { 406 days -= isLeapYear(year) ? 1 : 2; 407 } 408 409 // If it's January 1, update the cache. 410 if (cache != null && isJan1) { 411 cache.setCache(year, days, isLeapYear(year) ? 366 : 365); 412 } 413 414 return days; 415 } 416 417 /** 418 * Calculates calendar fields and store them in the specified 419 * {@code CalendarDate}. 420 */ 421 // should be 'protected' getCalendarDateFromFixedDate(CalendarDate date, long fixedDate)422 public void getCalendarDateFromFixedDate(CalendarDate date, 423 long fixedDate) { 424 Date gdate = (Date) date; 425 int year; 426 long jan1; 427 boolean isLeap; 428 if (gdate.hit(fixedDate)) { 429 year = gdate.getCachedYear(); 430 jan1 = gdate.getCachedJan1(); 431 isLeap = isLeapYear(year); 432 } else { 433 // Looking up FIXED_DATES[] here didn't improve performance 434 // much. So we calculate year and jan1. getFixedDate() 435 // will look up FIXED_DATES[] actually. 436 year = getGregorianYearFromFixedDate(fixedDate); 437 jan1 = getFixedDate(year, JANUARY, 1, null); 438 isLeap = isLeapYear(year); 439 // Update the cache data 440 gdate.setCache (year, jan1, isLeap ? 366 : 365); 441 } 442 443 int priorDays = (int)(fixedDate - jan1); 444 long mar1 = jan1 + 31 + 28; 445 if (isLeap) { 446 ++mar1; 447 } 448 if (fixedDate >= mar1) { 449 priorDays += isLeap ? 1 : 2; 450 } 451 int month = 12 * priorDays + 373; 452 if (month > 0) { 453 month /= 367; 454 } else { 455 month = CalendarUtils.floorDivide(month, 367); 456 } 457 long month1 = jan1 + ACCUMULATED_DAYS_IN_MONTH[month]; 458 if (isLeap && month >= MARCH) { 459 ++month1; 460 } 461 int dayOfMonth = (int)(fixedDate - month1) + 1; 462 int dayOfWeek = getDayOfWeekFromFixedDate(fixedDate); 463 assert dayOfWeek > 0 : "negative day of week " + dayOfWeek; 464 gdate.setNormalizedYear(year); 465 gdate.setMonth(month); 466 gdate.setDayOfMonth(dayOfMonth); 467 gdate.setDayOfWeek(dayOfWeek); 468 gdate.setLeapYear(isLeap); 469 gdate.setNormalized(true); 470 } 471 472 /** 473 * Returns the day of week of the given Gregorian date. 474 */ getDayOfWeek(CalendarDate date)475 public int getDayOfWeek(CalendarDate date) { 476 long fixedDate = getFixedDate(date); 477 return getDayOfWeekFromFixedDate(fixedDate); 478 } 479 getDayOfWeekFromFixedDate(long fixedDate)480 public static final int getDayOfWeekFromFixedDate(long fixedDate) { 481 // The fixed day 1 (January 1, 1 Gregorian) is Monday. 482 if (fixedDate >= 0) { 483 return (int)(fixedDate % 7) + SUNDAY; 484 } 485 return (int)CalendarUtils.mod(fixedDate, 7) + SUNDAY; 486 } 487 getYearFromFixedDate(long fixedDate)488 public int getYearFromFixedDate(long fixedDate) { 489 return getGregorianYearFromFixedDate(fixedDate); 490 } 491 492 /** 493 * Returns the Gregorian year number of the given fixed date. 494 */ getGregorianYearFromFixedDate(long fixedDate)495 final int getGregorianYearFromFixedDate(long fixedDate) { 496 long d0; 497 int d1, d2, d3, d4; 498 int n400, n100, n4, n1; 499 int year; 500 501 if (fixedDate > 0) { 502 d0 = fixedDate - 1; 503 n400 = (int)(d0 / 146097); 504 d1 = (int)(d0 % 146097); 505 n100 = d1 / 36524; 506 d2 = d1 % 36524; 507 n4 = d2 / 1461; 508 d3 = d2 % 1461; 509 n1 = d3 / 365; 510 d4 = (d3 % 365) + 1; 511 } else { 512 d0 = fixedDate - 1; 513 n400 = (int)CalendarUtils.floorDivide(d0, 146097L); 514 d1 = (int)CalendarUtils.mod(d0, 146097L); 515 n100 = CalendarUtils.floorDivide(d1, 36524); 516 d2 = CalendarUtils.mod(d1, 36524); 517 n4 = CalendarUtils.floorDivide(d2, 1461); 518 d3 = CalendarUtils.mod(d2, 1461); 519 n1 = CalendarUtils.floorDivide(d3, 365); 520 d4 = CalendarUtils.mod(d3, 365) + 1; 521 } 522 year = 400 * n400 + 100 * n100 + 4 * n4 + n1; 523 if (!(n100 == 4 || n1 == 4)) { 524 ++year; 525 } 526 return year; 527 } 528 529 /** 530 * @return true if the specified year is a Gregorian leap year, or 531 * false otherwise. 532 * @see BaseCalendar#isGregorianLeapYear 533 */ isLeapYear(CalendarDate date)534 protected boolean isLeapYear(CalendarDate date) { 535 return isLeapYear(((Date)date).getNormalizedYear()); 536 } 537 isLeapYear(int normalizedYear)538 boolean isLeapYear(int normalizedYear) { 539 return CalendarUtils.isGregorianLeapYear(normalizedYear); 540 } 541 } 542