Home | History | Annotate | Download | only in impl
      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 
     19 package com.ibm.icu.impl;
     20 
     21 import 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  */
     31 public 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