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} 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 {
 144         protected Date() {
 145             super();
 146         }
 147         protected Date(TimeZone zone) {
 148             super(zone);
 149         }
 150 
 151         public Date setNormalizedDate(int normalizedYear, int month, int dayOfMonth) {
 152             setNormalizedYear(normalizedYear);
 153             setMonth(month).setDayOfMonth(dayOfMonth);
 154             return this;
 155         }
 156 
 157         public abstract int getNormalizedYear();
 158 
 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 
 168         protected final boolean hit(int year) {
 169             return year == cachedYear;
 170         }
 171 
 172         protected final boolean hit(long fixedDate) {
 173             return (fixedDate >= cachedFixedDateJan1 &&
 174                     fixedDate < cachedFixedDateNextJan1);
 175         }
 176         protected int getCachedYear() {
 177             return cachedYear;
 178         }
 179 
 180         protected long getCachedJan1() {
 181             return cachedFixedDateJan1;
 182         }
 183 
 184         protected void setCache(int year, long jan1, int len) {
 185             cachedYear = year;
 186             cachedFixedDateJan1 = jan1;
 187             cachedFixedDateNextJan1 = jan1 + len;
 188         }
 189     }
 190 
 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 
 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 
 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}. The
 294      * {@code CalendarDate} must be normalized to get a correct
 295      * value.
 296      *
 297      * @param date a {@code CalendarDate}
 298      * @return a year length in days
 299      * @throws ClassCastException if the specified date is not a
 300      * {@link BaseCalendar.Date}
 301      */
 302     public int getYearLength(CalendarDate date) {
 303         return isLeapYear(((Date)date).getNormalizedYear()) ? 366 : 365;
 304     }
 305 
 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 
 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.
 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 
 339     public long getDayOfYear(CalendarDate date) {
 340         return getDayOfYear(((Date)date).getNormalizedYear(),
 341                             date.getMonth(),
 342                             date.getDayOfMonth());
 343     }
 344 
 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
 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
 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}.
 416      */
 417     // should be 'protected'
 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      */
 471     public int getDayOfWeek(CalendarDate date) {
 472         long fixedDate = getFixedDate(date);
 473         return getDayOfWeekFromFixedDate(fixedDate);
 474     }
 475 
 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 
 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      */
 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      */
 530     protected boolean isLeapYear(CalendarDate date) {
 531         return isLeapYear(((Date)date).getNormalizedYear());
 532     }
 533 
 534     boolean isLeapYear(int normalizedYear) {
 535         return CalendarUtils.isGregorianLeapYear(normalizedYear);
 536     }
 537 }