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