| /* | |
| * Copyright (c) 2003, 2024, Oracle and/or its affiliates. All rights reserved. | |
| * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. | |
| * | |
| * This code is free software; you can redistribute it and/or modify it | |
| * under the terms of the GNU General Public License version 2 only, as | |
| * published by the Free Software Foundation. Oracle designates this | |
| * particular file as subject to the "Classpath" exception as provided | |
| * by Oracle in the LICENSE file that accompanied this code. | |
| * | |
| * This code is distributed in the hope that it will be useful, but WITHOUT | |
| * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or | |
| * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License | |
| * version 2 for more details (a copy is included in the LICENSE file that | |
| * accompanied this code). | |
| * | |
| * You should have received a copy of the GNU General Public License version | |
| * 2 along with this work; if not, write to the Free Software Foundation, | |
| * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. | |
| * | |
| * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA | |
| * or visit www.oracle.com if you need additional information or have any | |
| * questions. | |
| */ | |
| package sun.util.calendar; | |
| import java.util.TimeZone; | |
| /** | |
| * The {@code BaseCalendar} provides basic calendar calculation | |
| * functions to support the Julian, Gregorian, and Gregorian-based | |
| * calendar systems. | |
| * | |
| * @author Masayoshi Okutsu | |
| * @since 1.5 | |
| */ | |
| public abstract sealed class BaseCalendar extends AbstractCalendar | |
| permits Gregorian, JulianCalendar, LocalGregorianCalendar { | |
| public static final int JANUARY = 1; | |
| public static final int FEBRUARY = 2; | |
| public static final int MARCH = 3; | |
| public static final int APRIL = 4; | |
| public static final int MAY = 5; | |
| public static final int JUNE = 6; | |
| public static final int JULY = 7; | |
| public static final int AUGUST = 8; | |
| public static final int SEPTEMBER = 9; | |
| public static final int OCTOBER = 10; | |
| public static final int NOVEMBER = 11; | |
| public static final int DECEMBER = 12; | |
| // day of week constants | |
| public static final int SUNDAY = 1; | |
| public static final int MONDAY = 2; | |
| public static final int TUESDAY = 3; | |
| public static final int WEDNESDAY = 4; | |
| public static final int THURSDAY = 5; | |
| public static final int FRIDAY = 6; | |
| public static final int SATURDAY = 7; | |
| // The base Gregorian year of FIXED_DATES[] | |
| private static final int BASE_YEAR = 1970; | |
| // Pre-calculated fixed dates of January 1 from BASE_YEAR | |
| // (Gregorian). This table covers all the years that can be | |
| // supported by the POSIX time_t (32-bit) after the Epoch. Note | |
| // that the data type is int[]. | |
| private static final int[] FIXED_DATES = { | |
| 719163, // 1970 | |
| 719528, // 1971 | |
| 719893, // 1972 | |
| 720259, // 1973 | |
| 720624, // 1974 | |
| 720989, // 1975 | |
| 721354, // 1976 | |
| 721720, // 1977 | |
| 722085, // 1978 | |
| 722450, // 1979 | |
| 722815, // 1980 | |
| 723181, // 1981 | |
| 723546, // 1982 | |
| 723911, // 1983 | |
| 724276, // 1984 | |
| 724642, // 1985 | |
| 725007, // 1986 | |
| 725372, // 1987 | |
| 725737, // 1988 | |
| 726103, // 1989 | |
| 726468, // 1990 | |
| 726833, // 1991 | |
| 727198, // 1992 | |
| 727564, // 1993 | |
| 727929, // 1994 | |
| 728294, // 1995 | |
| 728659, // 1996 | |
| 729025, // 1997 | |
| 729390, // 1998 | |
| 729755, // 1999 | |
| 730120, // 2000 | |
| 730486, // 2001 | |
| 730851, // 2002 | |
| 731216, // 2003 | |
| 731581, // 2004 | |
| 731947, // 2005 | |
| 732312, // 2006 | |
| 732677, // 2007 | |
| 733042, // 2008 | |
| 733408, // 2009 | |
| 733773, // 2010 | |
| 734138, // 2011 | |
| 734503, // 2012 | |
| 734869, // 2013 | |
| 735234, // 2014 | |
| 735599, // 2015 | |
| 735964, // 2016 | |
| 736330, // 2017 | |
| 736695, // 2018 | |
| 737060, // 2019 | |
| 737425, // 2020 | |
| 737791, // 2021 | |
| 738156, // 2022 | |
| 738521, // 2023 | |
| 738886, // 2024 | |
| 739252, // 2025 | |
| 739617, // 2026 | |
| 739982, // 2027 | |
| 740347, // 2028 | |
| 740713, // 2029 | |
| 741078, // 2030 | |
| 741443, // 2031 | |
| 741808, // 2032 | |
| 742174, // 2033 | |
| 742539, // 2034 | |
| 742904, // 2035 | |
| 743269, // 2036 | |
| 743635, // 2037 | |
| 744000, // 2038 | |
| 744365, // 2039 | |
| }; | |
| public abstract static sealed class Date extends CalendarDate | |
| permits Gregorian.Date, ImmutableGregorianDate, JulianCalendar.Date, LocalGregorianCalendar.Date { | |
| protected Date() { | |
| super(); | |
| } | |
| protected Date(TimeZone zone) { | |
| super(zone); | |
| } | |
| public Date setNormalizedDate(int normalizedYear, int month, int dayOfMonth) { | |
| setNormalizedYear(normalizedYear); | |
| setMonth(month).setDayOfMonth(dayOfMonth); | |
| return this; | |
| } | |
| public abstract int getNormalizedYear(); | |
| public abstract void setNormalizedYear(int normalizedYear); | |
| // Cache for the fixed date of January 1 and year length of the | |
| // cachedYear. A simple benchmark showed 7% performance | |
| // improvement with >90% cache hit. The initial values are for Gregorian. | |
| int cachedYear = 2004; | |
| long cachedFixedDateJan1 = 731581L; | |
| long cachedFixedDateNextJan1 = cachedFixedDateJan1 + 366; | |
| protected final boolean hit(int year) { | |
| return year == cachedYear; | |
| } | |
| protected final boolean hit(long fixedDate) { | |
| return (fixedDate >= cachedFixedDateJan1 && | |
| fixedDate < cachedFixedDateNextJan1); | |
| } | |
| protected int getCachedYear() { | |
| return cachedYear; | |
| } | |
| protected long getCachedJan1() { | |
| return cachedFixedDateJan1; | |
| } | |
| protected void setCache(int year, long jan1, int len) { | |
| cachedYear = year; | |
| cachedFixedDateJan1 = jan1; | |
| cachedFixedDateNextJan1 = jan1 + len; | |
| } | |
| } | |
| @Override | |
| public boolean validate(CalendarDate date) { | |
| Date bdate = (Date) date; | |
| if (bdate.isNormalized()) { | |
| return true; | |
| } | |
| int month = bdate.getMonth(); | |
| if (month < JANUARY || month > DECEMBER) { | |
| return false; | |
| } | |
| int d = bdate.getDayOfMonth(); | |
| if (d <= 0 || d > getMonthLength(bdate.getNormalizedYear(), month)) { | |
| return false; | |
| } | |
| int dow = bdate.getDayOfWeek(); | |
| if (dow != Date.FIELD_UNDEFINED && dow != getDayOfWeek(bdate)) { | |
| return false; | |
| } | |
| if (!validateTime(date)) { | |
| return false; | |
| } | |
| bdate.setNormalized(true); | |
| return true; | |
| } | |
| @Override | |
| public boolean normalize(CalendarDate date) { | |
| if (date.isNormalized()) { | |
| return true; | |
| } | |
| Date bdate = (Date) date; | |
| TimeZone zi = bdate.getZone(); | |
| // If the date has a time zone, then we need to recalculate | |
| // the calendar fields. Let getTime() do it. | |
| if (zi != null) { | |
| getTime(date); | |
| return true; | |
| } | |
| int days = normalizeTime(bdate); | |
| normalizeMonth(bdate); | |
| long d = (long)bdate.getDayOfMonth() + days; | |
| int m = bdate.getMonth(); | |
| int y = bdate.getNormalizedYear(); | |
| int ml = getMonthLength(y, m); | |
| if (!(d > 0 && d <= ml)) { | |
| if (d <= 0 && d > -28) { | |
| ml = getMonthLength(y, --m); | |
| d += ml; | |
| bdate.setDayOfMonth((int) d); | |
| if (m == 0) { | |
| m = DECEMBER; | |
| bdate.setNormalizedYear(y - 1); | |
| } | |
| bdate.setMonth(m); | |
| } else if (d > ml && d < (ml + 28)) { | |
| d -= ml; | |
| ++m; | |
| bdate.setDayOfMonth((int)d); | |
| if (m > DECEMBER) { | |
| bdate.setNormalizedYear(y + 1); | |
| m = JANUARY; | |
| } | |
| bdate.setMonth(m); | |
| } else { | |
| long fixedDate = d + getFixedDate(y, m, 1, bdate) - 1L; | |
| getCalendarDateFromFixedDate(bdate, fixedDate); | |
| } | |
| } else { | |
| bdate.setDayOfWeek(getDayOfWeek(bdate)); | |
| } | |
| date.setLeapYear(isLeapYear(bdate.getNormalizedYear())); | |
| date.setZoneOffset(0); | |
| date.setDaylightSaving(0); | |
| bdate.setNormalized(true); | |
| return true; | |
| } | |
| void normalizeMonth(CalendarDate date) { | |
| Date bdate = (Date) date; | |
| int year = bdate.getNormalizedYear(); | |
| long month = bdate.getMonth(); | |
| if (month <= 0) { | |
| long xm = 1L - month; | |
| year -= (int)((xm / 12) + 1); | |
| month = 13 - (xm % 12); | |
| if (month == 13) { | |
| year++; | |
| month = 1; | |
| } | |
| bdate.setNormalizedYear(year); | |
| bdate.setMonth((int) month); | |
| } else if (month > DECEMBER) { | |
| year += (int)((month - 1) / 12); | |
| month = ((month - 1)) % 12 + 1; | |
| bdate.setNormalizedYear(year); | |
| bdate.setMonth((int) month); | |
| } | |
| } | |
| /** | |
| * Returns 366 if the specified date is in a leap year, or 365 | |
| * otherwise This method does not perform the normalization with | |
| * the specified {@code CalendarDate}. The | |
| * {@code CalendarDate} must be normalized to get a correct | |
| * value. | |
| * | |
| * @param date a {@code CalendarDate} | |
| * @return a year length in days | |
| * @throws ClassCastException if the specified date is not a | |
| * {@link BaseCalendar.Date} | |
| */ | |
| @Override | |
| public int getYearLength(CalendarDate date) { | |
| return isLeapYear(((Date)date).getNormalizedYear()) ? 366 : 365; | |
| } | |
| static final int[] DAYS_IN_MONTH | |
| // 12 1 2 3 4 5 6 7 8 9 10 11 12 | |
| = { 31, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31}; | |
| static final int[] ACCUMULATED_DAYS_IN_MONTH | |
| // 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 | |
| = { -30, 0, 31, 59, 90,120,151,181,212,243, 273, 304, 334}; | |
| static final int[] ACCUMULATED_DAYS_IN_MONTH_LEAP | |
| // 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 | |
| = { -30, 0, 31, 59+1, 90+1,120+1,151+1,181+1,212+1,243+1, 273+1, 304+1, 334+1}; | |
| @Override | |
| public int getMonthLength(CalendarDate date) { | |
| Date gdate = (Date) date; | |
| int month = gdate.getMonth(); | |
| if (month < JANUARY || month > DECEMBER) { | |
| throw new IllegalArgumentException("Illegal month value: " + month); | |
| } | |
| return getMonthLength(gdate.getNormalizedYear(), month); | |
| } | |
| // accepts 0 (December in the previous year) to 12. | |
| private int getMonthLength(int year, int month) { | |
| int days = DAYS_IN_MONTH[month]; | |
| if (month == FEBRUARY && isLeapYear(year)) { | |
| days++; | |
| } | |
| return days; | |
| } | |
| public long getDayOfYear(CalendarDate date) { | |
| return getDayOfYear(((Date)date).getNormalizedYear(), | |
| date.getMonth(), | |
| date.getDayOfMonth()); | |
| } | |
| final long getDayOfYear(int year, int month, int dayOfMonth) { | |
| return (long) dayOfMonth | |
| + (isLeapYear(year) ? | |
| ACCUMULATED_DAYS_IN_MONTH_LEAP[month] : ACCUMULATED_DAYS_IN_MONTH[month]); | |
| } | |
| // protected | |
| @Override | |
| public long getFixedDate(CalendarDate date) { | |
| if (!date.isNormalized()) { | |
| normalizeMonth(date); | |
| } | |
| return getFixedDate(((Date)date).getNormalizedYear(), | |
| date.getMonth(), | |
| date.getDayOfMonth(), | |
| (BaseCalendar.Date) date); | |
| } | |
| // public for java.util.GregorianCalendar | |
| public long getFixedDate(int year, int month, int dayOfMonth, BaseCalendar.Date cache) { | |
| boolean isJan1 = month == JANUARY && dayOfMonth == 1; | |
| // Look up the one year cache | |
| if (cache != null && cache.hit(year)) { | |
| if (isJan1) { | |
| return cache.getCachedJan1(); | |
| } | |
| return cache.getCachedJan1() + getDayOfYear(year, month, dayOfMonth) - 1; | |
| } | |
| // Look up the pre-calculated fixed date table | |
| int n = year - BASE_YEAR; | |
| if (n >= 0 && n < FIXED_DATES.length) { | |
| long jan1 = FIXED_DATES[n]; | |
| if (cache != null) { | |
| cache.setCache(year, jan1, isLeapYear(year) ? 366 : 365); | |
| } | |
| return isJan1 ? jan1 : jan1 + getDayOfYear(year, month, dayOfMonth) - 1; | |
| } | |
| long prevyear = (long)year - 1; | |
| long days = dayOfMonth; | |
| if (prevyear >= 0) { | |
| days += (365 * prevyear) | |
| + (prevyear / 4) | |
| - (prevyear / 100) | |
| + (prevyear / 400) | |
| + ((367 * month - 362) / 12); | |
| } else { | |
| days += (365 * prevyear) | |
| + CalendarUtils.floorDivide(prevyear, 4) | |
| - CalendarUtils.floorDivide(prevyear, 100) | |
| + CalendarUtils.floorDivide(prevyear, 400) | |
| + CalendarUtils.floorDivide((367 * month - 362), 12); | |
| } | |
| if (month > FEBRUARY) { | |
| days -= isLeapYear(year) ? 1 : 2; | |
| } | |
| // If it's January 1, update the cache. | |
| if (cache != null && isJan1) { | |
| cache.setCache(year, days, isLeapYear(year) ? 366 : 365); | |
| } | |
| return days; | |
| } | |
| /** | |
| * Calculates calendar fields and store them in the specified | |
| * {@code CalendarDate}. | |
| */ | |
| // should be 'protected' | |
| @Override | |
| public void getCalendarDateFromFixedDate(CalendarDate date, | |
| long fixedDate) { | |
| Date gdate = (Date) date; | |
| int year; | |
| long jan1; | |
| boolean isLeap; | |
| if (gdate.hit(fixedDate)) { | |
| year = gdate.getCachedYear(); | |
| jan1 = gdate.getCachedJan1(); | |
| isLeap = isLeapYear(year); | |
| } else { | |
| // Looking up FIXED_DATES[] here didn't improve performance | |
| // much. So we calculate year and jan1. getFixedDate() | |
| // will look up FIXED_DATES[] actually. | |
| year = getGregorianYearFromFixedDate(fixedDate); | |
| jan1 = getFixedDate(year, JANUARY, 1, null); | |
| isLeap = isLeapYear(year); | |
| // Update the cache data | |
| gdate.setCache (year, jan1, isLeap ? 366 : 365); | |
| } | |
| int priorDays = (int)(fixedDate - jan1); | |
| long mar1 = jan1 + 31 + 28; | |
| if (isLeap) { | |
| ++mar1; | |
| } | |
| if (fixedDate >= mar1) { | |
| priorDays += isLeap ? 1 : 2; | |
| } | |
| int month = 12 * priorDays + 373; | |
| if (month > 0) { | |
| month /= 367; | |
| } else { | |
| month = CalendarUtils.floorDivide(month, 367); | |
| } | |
| long month1 = jan1 + ACCUMULATED_DAYS_IN_MONTH[month]; | |
| if (isLeap && month >= MARCH) { | |
| ++month1; | |
| } | |
| int dayOfMonth = (int)(fixedDate - month1) + 1; | |
| int dayOfWeek = getDayOfWeekFromFixedDate(fixedDate); | |
| assert dayOfWeek > 0 : "negative day of week " + dayOfWeek; | |
| gdate.setNormalizedYear(year); | |
| gdate.setMonth(month); | |
| gdate.setDayOfMonth(dayOfMonth); | |
| gdate.setDayOfWeek(dayOfWeek); | |
| gdate.setLeapYear(isLeap); | |
| gdate.setNormalized(true); | |
| } | |
| /** | |
| * Returns the day of week of the given Gregorian date. | |
| */ | |
| public int getDayOfWeek(CalendarDate date) { | |
| long fixedDate = getFixedDate(date); | |
| return getDayOfWeekFromFixedDate(fixedDate); | |
| } | |
| public static int getDayOfWeekFromFixedDate(long fixedDate) { | |
| // The fixed day 1 (January 1, 1 Gregorian) is Monday. | |
| if (fixedDate >= 0) { | |
| return (int)(fixedDate % 7) + SUNDAY; | |
| } | |
| return (int)CalendarUtils.mod(fixedDate, 7) + SUNDAY; | |
| } | |
| public int getYearFromFixedDate(long fixedDate) { | |
| return getGregorianYearFromFixedDate(fixedDate); | |
| } | |
| /** | |
| * Returns the Gregorian year number of the given fixed date. | |
| */ | |
| final int getGregorianYearFromFixedDate(long fixedDate) { | |
| long d0; | |
| int d1, d2, d3; | |
| int n400, n100, n4, n1; | |
| int year; | |
| if (fixedDate > 0) { | |
| d0 = fixedDate - 1; | |
| n400 = (int)(d0 / 146097); | |
| d1 = (int)(d0 % 146097); | |
| n100 = d1 / 36524; | |
| d2 = d1 % 36524; | |
| n4 = d2 / 1461; | |
| d3 = d2 % 1461; | |
| n1 = d3 / 365; | |
| } else { | |
| d0 = fixedDate - 1; | |
| n400 = (int)CalendarUtils.floorDivide(d0, 146097L); | |
| d1 = (int)CalendarUtils.mod(d0, 146097L); | |
| n100 = CalendarUtils.floorDivide(d1, 36524); | |
| d2 = CalendarUtils.mod(d1, 36524); | |
| n4 = CalendarUtils.floorDivide(d2, 1461); | |
| d3 = CalendarUtils.mod(d2, 1461); | |
| n1 = CalendarUtils.floorDivide(d3, 365); | |
| } | |
| year = 400 * n400 + 100 * n100 + 4 * n4 + n1; | |
| if (!(n100 == 4 || n1 == 4)) { | |
| ++year; | |
| } | |
| return year; | |
| } | |
| /** | |
| * @return true if the specified year is a Gregorian leap year, or | |
| * false otherwise. | |
| * @see CalendarUtils#isGregorianLeapYear | |
| */ | |
| @Override | |
| protected boolean isLeapYear(CalendarDate date) { | |
| return isLeapYear(((Date)date).getNormalizedYear()); | |
| } | |
| boolean isLeapYear(int normalizedYear) { | |
| return CalendarUtils.isGregorianLeapYear(normalizedYear); | |
| } | |
| } |