1// © 2016 and later: Unicode, Inc. and others.
2// License & terms of use: http://www.unicode.org/copyright.html#License
3/**
4 *******************************************************************************
5 * Copyright (C) 2003-2014, International Business Machines Corporation and
6 * others. All Rights Reserved.
7 *******************************************************************************
8 * Partial port from ICU4C's Grego class in i18n/gregoimp.h.
9 *
10 * Methods ported, or moved here from OlsonTimeZone, initially
11 * for work on Jitterbug 5470:
12 *   tzdata2006n Brazil incorrect fall-back date 2009-mar-01
13 * Only the methods necessary for that work are provided - this is not a full
14 * port of ICU4C's Grego class (yet).
15 *
16 * These utilities are used by both OlsonTimeZone and SimpleTimeZone.
17 */
18
19package com.ibm.icu.impl;
20
21import java.util.Locale;
22
23
24/**
25 * A utility class providing proleptic Gregorian calendar functions
26 * used by time zone and calendar code.  Do not instantiate.
27 *
28 * Note:  Unlike GregorianCalendar, all computations performed by this
29 * class occur in the pure proleptic GregorianCalendar.
30 */
31public class Grego {
32
33    // Max/min milliseconds
34    public static final long MIN_MILLIS = -184303902528000000L;
35    public static final long MAX_MILLIS = 183882168921600000L;
36
37    public static final int MILLIS_PER_SECOND = 1000;
38    public static final int MILLIS_PER_MINUTE = 60*MILLIS_PER_SECOND;
39    public static final int MILLIS_PER_HOUR = 60*MILLIS_PER_MINUTE;
40    public static final int MILLIS_PER_DAY = 24*MILLIS_PER_HOUR;
41
42    //  January 1, 1 CE Gregorian
43    private static final int JULIAN_1_CE = 1721426;
44
45    //  January 1, 1970 CE Gregorian
46    private static final int JULIAN_1970_CE = 2440588;
47
48    private static final int[] MONTH_LENGTH = new int[] {
49        31,28,31,30,31,30,31,31,30,31,30,31,
50        31,29,31,30,31,30,31,31,30,31,30,31
51    };
52
53    private static final int[] DAYS_BEFORE = new int[] {
54        0,31,59,90,120,151,181,212,243,273,304,334,
55        0,31,60,91,121,152,182,213,244,274,305,335 };
56
57    /**
58     * Return true if the given year is a leap year.
59     * @param year Gregorian year, with 0 == 1 BCE, -1 == 2 BCE, etc.
60     * @return true if the year is a leap year
61     */
62    public static final boolean isLeapYear(int year) {
63        // year&0x3 == year%4
64        return ((year&0x3) == 0) && ((year%100 != 0) || (year%400 == 0));
65    }
66
67    /**
68     * Return the number of days in the given month.
69     * @param year Gregorian year, with 0 == 1 BCE, -1 == 2 BCE, etc.
70     * @param month 0-based month, with 0==Jan
71     * @return the number of days in the given month
72     */
73    public static final int monthLength(int year, int month) {
74        return MONTH_LENGTH[month + (isLeapYear(year) ? 12 : 0)];
75    }
76
77    /**
78     * Return the length of a previous month of the Gregorian calendar.
79     * @param year Gregorian year, with 0 == 1 BCE, -1 == 2 BCE, etc.
80     * @param month 0-based month, with 0==Jan
81     * @return the number of days in the month previous to the given month
82     */
83    public static final int previousMonthLength(int year, int month) {
84        return (month > 0) ? monthLength(year, month-1) : 31;
85    }
86
87    /**
88     * Convert a year, month, and day-of-month, given in the proleptic
89     * Gregorian calendar, to 1970 epoch days.
90     * @param year Gregorian year, with 0 == 1 BCE, -1 == 2 BCE, etc.
91     * @param month 0-based month, with 0==Jan
92     * @param dom 1-based day of month
93     * @return the day number, with day 0 == Jan 1 1970
94     */
95    public static long fieldsToDay(int year, int month, int dom) {
96        int y = year - 1;
97        long julian =
98            365 * y + floorDivide(y, 4) + (JULIAN_1_CE - 3) +    // Julian cal
99            floorDivide(y, 400) - floorDivide(y, 100) + 2 +   // => Gregorian cal
100            DAYS_BEFORE[month + (isLeapYear(year) ? 12 : 0)] + dom; // => month/dom
101        return julian - JULIAN_1970_CE; // JD => epoch day
102    }
103
104    /**
105     * Return the day of week on the 1970-epoch day
106     * @param day the 1970-epoch day (integral value)
107     * @return the day of week
108     */
109    public static int dayOfWeek(long day) {
110        long[] remainder = new long[1];
111        floorDivide(day + 5 /* Calendar.THURSDAY */, 7, remainder);
112        int dayOfWeek = (int)remainder[0];
113        dayOfWeek = (dayOfWeek == 0) ? 7 : dayOfWeek;
114        return dayOfWeek;
115    }
116
117    public static int[] dayToFields(long day, int[] fields) {
118        if (fields == null || fields.length < 5) {
119            fields = new int[5];
120        }
121        // Convert from 1970 CE epoch to 1 CE epoch (Gregorian calendar)
122        day += JULIAN_1970_CE - JULIAN_1_CE;
123
124        long[] rem = new long[1];
125        long n400 = floorDivide(day, 146097, rem);
126        long n100 = floorDivide(rem[0], 36524, rem);
127        long n4 = floorDivide(rem[0], 1461, rem);
128        long n1 = floorDivide(rem[0], 365, rem);
129
130        int year = (int)(400 * n400 + 100 * n100 + 4 * n4 + n1);
131        int dayOfYear = (int)rem[0];
132        if (n100 == 4 || n1 == 4) {
133            dayOfYear = 365;    // Dec 31 at end of 4- or 400-yr cycle
134        }
135        else {
136            ++year;
137        }
138
139        boolean isLeap = isLeapYear(year);
140        int correction = 0;
141        int march1 = isLeap ? 60 : 59;  // zero-based DOY for March 1
142        if (dayOfYear >= march1) {
143            correction = isLeap ? 1 : 2;
144        }
145        int month = (12 * (dayOfYear + correction) + 6) / 367;  // zero-based month
146        int dayOfMonth = dayOfYear - DAYS_BEFORE[isLeap ? month + 12 : month] + 1; // one-based DOM
147        int dayOfWeek = (int)((day + 2) % 7);  // day 0 is Monday(2)
148        if (dayOfWeek < 1 /* Sunday */) {
149            dayOfWeek += 7;
150        }
151        dayOfYear++; // 1-based day of year
152
153        fields[0] = year;
154        fields[1] = month;
155        fields[2] = dayOfMonth;
156        fields[3] = dayOfWeek;
157        fields[4] = dayOfYear;
158
159        return fields;
160    }
161
162    /*
163     * Convert long time to date/time fields
164     *
165     * result[0] : year
166     * result[1] : month
167     * result[2] : dayOfMonth
168     * result[3] : dayOfWeek
169     * result[4] : dayOfYear
170     * result[5] : millisecond in day
171     */
172    public static int[] timeToFields(long time, int[] fields) {
173        if (fields == null || fields.length < 6) {
174            fields = new int[6];
175        }
176        long[] remainder = new long[1];
177        long day = floorDivide(time, 24*60*60*1000 /* milliseconds per day */, remainder);
178        dayToFields(day, fields);
179        fields[5] = (int)remainder[0];
180        return fields;
181    }
182
183    public static long floorDivide(long numerator, long denominator) {
184        // We do this computation in order to handle
185        // a numerator of Long.MIN_VALUE correctly
186        return (numerator >= 0) ?
187            numerator / denominator :
188            ((numerator + 1) / denominator) - 1;
189    }
190
191    private static long floorDivide(long numerator, long denominator, long[] remainder) {
192        if (numerator >= 0) {
193            remainder[0] = numerator % denominator;
194            return numerator / denominator;
195        }
196        long quotient = ((numerator + 1) / denominator) - 1;
197        remainder[0] = numerator - (quotient * denominator);
198        return quotient;
199    }
200
201    /*
202     * Returns the ordinal number for the specified day of week in the month.
203     * The valid return value is 1, 2, 3, 4 or -1.
204     */
205    public static int getDayOfWeekInMonth(int year, int month, int dayOfMonth) {
206        int weekInMonth = (dayOfMonth + 6)/7;
207        if (weekInMonth == 4) {
208            if (dayOfMonth + 7 > monthLength(year, month)) {
209                weekInMonth = -1;
210            }
211        } else if (weekInMonth == 5) {
212            weekInMonth = -1;
213        }
214        return weekInMonth;
215    }
216
217    /**
218     * Convenient method for formatting time to ISO 8601 style
219     * date string.
220     * @param time long time
221     * @return ISO-8601 date string
222     */
223    public static String timeToString(long time) {
224        int[] fields = timeToFields(time, null);
225        int millis = fields[5];
226        int hour = millis / MILLIS_PER_HOUR;
227        millis = millis % MILLIS_PER_HOUR;
228        int min = millis / MILLIS_PER_MINUTE;
229        millis = millis % MILLIS_PER_MINUTE;
230        int sec = millis / MILLIS_PER_SECOND;
231        millis = millis % MILLIS_PER_SECOND;
232
233        return String.format((Locale)null, "%04d-%02d-%02dT%02d:%02d:%02d.%03dZ",
234                fields[0], fields[1] + 1, fields[2], hour, min, sec, millis);
235    }
236}
237