export class CalendarDate {
year: i32;
month: i32; // 1 ~ 12
dayOfMonth: i32; // 1 ~ 31
dayOfWeek: i32; // 1 ~ 7
hours: i32;
minutes: i32;
seconds: i32;
milliseconds: i32;
isNormalized: bool;
millis: i64; // The date milliseconds
timeOfDay: i32; // the time of day
fixedDate: i32; // the time of days;
timeZone: TimeZone; // the local time zone
constructor(millis: i64, timeZone: TimeZone) {
this.millis = millis;
this.isNormalized = false;
this.timeZone = timeZone;
}
}
/**
* Represents a time zone for use with a Gregorian calendar.
* The class holds an offset from GMT, called raw offset, and start
* and end rules for a daylight saving time schedule
*/
export class TimeZone {
ID: string;
/**
* The raw GMT offset in milliseconds between this zone and GMT.
* Negative offsets are to the west of Greenwich.
* To obtain local standard time, add the offset to GMT time.
*/
rawOffset: i32;
constructor(ID: string, rawOffset: i32) {
this.ID = ID;
this.rawOffset = rawOffset;
}
static UTC_TIME_ZONE: TimeZone = new TimeZone("UTC", 0);
}
class CalendarUtils {
static SECOND_IN_MILLIS: i32 = 1000;
static MINUTE_IN_MILLIS: i32 = 60000; // 1000 * 60
static HOUR_IN_MILLIS: i32 = 3600000; // 1000 * 60 * 60
static DAY_IN_MILLIS: i32 = 86400000; // 1000 * 60 * 60 * 24
static DAY_IN_HOURS: i32 = 24;
static YEAR_IN_MONTHS: i32 = 12;
static JANUARY: i32 = 1;
static FEBRUARY: i32 = 2;
static MARCH: i32 = 3;
static APRIL: i32 = 4;
static MAY: i32 = 5;
static JUNE: i32 = 6;
static JULY: i32 = 7;
static AUGUST: i32 = 8;
static SEPTEMBER: i32 = 9;
static OCTOBER: i32 = 10;
static NOVEMBER: i32 = 11;
static DECEMBER: i32 = 12;
static BASE_YEAR: i32 = 1970;
// day of week constants
static SUNDAY: i32 = 1;
static MONDAY: i32 = 2;
static TUESDAY: i32 = 3;
static WEDNESDAY: i32 = 4;
static THURSDAY: i32 = 5;
static FRIDAY: i32 = 6;
static SATURDAY: i32 = 7;
// 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
static ACCUMULATED_DAYS_IN_MONTH: i32[ ] = [-30, 0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334];
static FIXED_DATES: i32[] = [
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
];
// The number of days between January 1, 1 and January 1, 1970 (Gregorian)
static EPOCH_OFFSET: i32 = 719163;
static floorDivide(n: i32, d: i32): i32 {
return ((n >= 0) ? (n / d) : (((n + 1) / d) - 1));
}
static mod(x: i32, y: i32): i32 {
return (x - y * CalendarUtils.floorDivide(x, y));
}
@inline
static isLeapYear(year: i32): bool {
return (year & 3) == 0 && ((year % 25) != 0 || (year & 15) == 0);
}
@inline
static countLeaps(year: i32): i32 {
if (year >= 0) {
return year / 4 - year / 100 + year / 400;
} else {
return CalendarUtils.floorDivide(year, 4)
- CalendarUtils.floorDivide(year, 100)
+ CalendarUtils.floorDivide(year, 400);
}
}
/**
* Returns the Geogorian year number of the given fixed date.
*/
static getYearFromFixedDate(fixedDate: i32): i32 {
var d0: i32;
var d1: i32, d2: i32, d3: i32;
var n400: i32, n100: i32, n4: i32, n1: i32;
var year: i32;
if ( fixedDate > 0) {
d0 = fixedDate - 1;
n400 = d0 / 146097;
d1 = d0 % 146097;
n100 = d1 / 36524;
d2 = d1 % 36524;
n4 = d2 / 1461;
d3 = d2 % 1461;
n1 = d3 / 365;
// d4 = (d3 % 365) + 1;
} else {
d0 = fixedDate - 1;
n400 = CalendarUtils.floorDivide(d0, 146097);
d1 = CalendarUtils.mod(d0, 146097);
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);
// d4 = CalendarUtils.mod(d3, 365) + 1;
}
year = 400 * n400 + 100 * n100 + 4 * n4 + n1;
if (!(n100 == 4 || n1 == 4)) {
++year;
}
return year;
}
static getDayOfWeekFromFixedDate(fixedDate: i32): i32 {
if (fixedDate >= 0) {
return (fixedDate % 7) + CalendarUtils.SUNDAY;
}
return CalendarUtils.mod(fixedDate, 7) + CalendarUtils.SUNDAY;
}
private static hitFixedDateYear(year: i32): bool {
return year >= 1970 && year <= 2039;
}
/**
* Returns the days of the specified date.
* @param year the year of the date
* @param month the month of the date, 1-based
* @param dayOfMonth the day of the month, 1-based
*/
static getFixedDate(year: i32, month: i32, dayOfMonth: i32): i32 {
var isJan1: bool = month == CalendarUtils.JANUARY && dayOfMonth == 1;
if (isJan1 && CalendarUtils.hitFixedDateYear(year)) {
return CalendarUtils.FIXED_DATES[year - CalendarUtils.BASE_YEAR];
}
var prevyear = year - 1;
var days = dayOfMonth;
days += (365 * prevyear) + CalendarUtils.countLeaps(prevyear) + ((367 * month - 362) / 12);
if (month > CalendarUtils.FEBRUARY) {
days -= CalendarUtils.isLeapYear(year) ? 1 : 2;
}
return days;
}
}
function setTimeOfDay(cdate: CalendarDate, fraction: i32): void {
var time = fraction;
var hours = time / CalendarUtils.HOUR_IN_MILLIS;
time %= CalendarUtils.HOUR_IN_MILLIS;
var minutes = time / CalendarUtils.MINUTE_IN_MILLIS;
time %= CalendarUtils.MINUTE_IN_MILLIS;
var seconds = time / CalendarUtils.SECOND_IN_MILLIS;
time %= CalendarUtils.SECOND_IN_MILLIS;
cdate.hours = hours;
cdate.minutes = minutes;
cdate.seconds = seconds;
cdate.milliseconds = time;
cdate.timeOfDay = fraction;
}
export function getCalendarDate(cdate: CalendarDate, millis: i64): CalendarDate {
var ms: i32 = 0; // time of day
var days: i32 = 0; // fixed date
var timezone = cdate.timeZone;
// adjust to local time if 'date' has time zone.
if (timezone != null && timezone.rawOffset != 0) {
let zoneOffset = timezone.rawOffset;
days += ((millis + zoneOffset) / CalendarUtils.DAY_IN_MILLIS);
ms += ((millis + zoneOffset) % CalendarUtils.DAY_IN_MILLIS);
} else {
days = (millis / CalendarUtils.DAY_IN_MILLIS);
ms = (millis % CalendarUtils.DAY_IN_MILLIS);
}
days += CalendarUtils.EPOCH_OFFSET;
cdate.millis = millis;
setCalendarDateFromFixedDate(cdate, days);
setTimeOfDay(cdate, ms);
cdate.isNormalized = true;
return cdate;
}
/**
* Get calendar date, the millis timestamp is UTC timestamp
* @param cdate the date to set
* @param mills the UTC timestamp
*/
function setCalendarDateFromFixedDate(date: CalendarDate, fixedDate: i32): void {
var year: i32 = CalendarUtils.getYearFromFixedDate(fixedDate);
var jan1: i32 = CalendarUtils.getFixedDate(year, CalendarUtils.JANUARY, 1);
var isLeap: bool = CalendarUtils.isLeapYear(year);
var priorDays: i32 = fixedDate - jan1;
var mar1: i32 = jan1 + 31 + 28;
if (isLeap) {
++ mar1;
}
if (fixedDate >= mar1) {
priorDays += isLeap ? 1 : 2;
}
var month: i32 = 12 * priorDays + 373;
if (month > 0) {
month /= 367;
} else {
month = CalendarUtils.floorDivide(month, 367);
}
var accumulated = CalendarUtils.ACCUMULATED_DAYS_IN_MONTH[month];
var month1 = jan1 + accumulated;
if (isLeap && month >= CalendarUtils.MARCH) {
++ month1;
}
var dayOfMonth = (fixedDate - month1) + 1;
var dayOfWeek = CalendarUtils.getDayOfWeekFromFixedDate(fixedDate);
date.year = year;
date.month = month;
date.dayOfMonth = dayOfMonth;
date.dayOfWeek = dayOfWeek;
date.fixedDate = fixedDate;
}
export function setHours(cdate: CalendarDate, hours: i32): void {
if (cdate.hours != hours) {
let oldHours = cdate.hours;
cdate.millis += (hours - oldHours) * CalendarUtils.HOUR_IN_MILLIS;
cdate.isNormalized = false;
}
}
export function setMinutes(cdate: CalendarDate, minutes: i32): void {
if (cdate.minutes != minutes) {
let oldMinutes = cdate.minutes;
cdate.millis += (minutes - oldMinutes) * CalendarUtils.MINUTE_IN_MILLIS;
cdate.isNormalized = false;
}
}
export function setSeconds(cdate: CalendarDate, seconds: i32): void {
if (cdate.seconds != seconds) {
let oldSeconds = cdate.seconds;
cdate.millis += (seconds - oldSeconds) * CalendarUtils.SECOND_IN_MILLIS;
cdate.isNormalized = false;
}
}
export function setMilliseconds(cdate: CalendarDate, milliseconds: i32): void {
if (cdate.milliseconds != milliseconds) {
let oldMilliseconds = cdate.milliseconds;
cdate.millis += (milliseconds - oldMilliseconds);
cdate.isNormalized = false;
}
}
export function setFullYear(cdate: CalendarDate, year: i32): void {
if (cdate.year != year) {
let oldyearJan1 = CalendarUtils.getFixedDate(cdate.year, CalendarUtils.JANUARY, 1);
let newYearJan1 = CalendarUtils.getFixedDate(year, CalendarUtils.JANUARY, 1);
let newFixedDate: i32 = newYearJan1 + (cdate.fixedDate - oldyearJan1);
setCalendarDateFromFixedDate(cdate, newFixedDate);
cdate.millis += (newYearJan1 - oldyearJan1) * CalendarUtils.DAY_IN_MILLIS;
}
}
export function setMonth(cdate: CalendarDate, month: i32): void {
if (cdate.month != month) {
let addyear = CalendarUtils.floorDivide(month, CalendarUtils.YEAR_IN_MONTHS);
let newMonth = CalendarUtils.mod(month, CalendarUtils.YEAR_IN_MONTHS);
let oldDays = cdate.dayOfMonth;
let newMonthDays = CalendarUtils.getFixedDate(cdate.year + addyear, newMonth, 1);
let newFixedDate = newMonthDays + oldDays - 1;
let oldFixedDate = cdate.fixedDate;
setCalendarDateFromFixedDate(cdate, newFixedDate);
cdate.millis += (newFixedDate - oldFixedDate) * CalendarUtils.DAY_IN_MILLIS;
}
}
export function setDate(cdate: CalendarDate, date: i32): void {
if (cdate.dayOfMonth != date) {
let oldDayOfMonth = cdate.dayOfMonth;
let newFixedDate = cdate.fixedDate + date - oldDayOfMonth;
setCalendarDateFromFixedDate(cdate, newFixedDate);
cdate.millis += (date - oldDayOfMonth) * CalendarUtils.DAY_IN_MILLIS;
}
}