// Version: Feb 2020 // // The following TypeScript program illustrates how easy it is to apply NexCalendar: // // Since software stores dates by its ordinal days from a special date, // it is easy to convert any Gregorian date to NexCalendar date. // interface MonthDay { month: number; day: number; } interface WeekDate { week: number; weekday: number; } interface WeekdayName { longName: string; shortName: string; } class NexCalendar { // Weekday = Day of the Week (1 = Monday ... 7 = Sunday, 8 = Sunday) // Yearday = Ordinal Day of the Year (1 ... 365, 366 = Leap Sunday) static get maxDaysInYear() { return 366 } // 366 is equal to the Leap Sunday static get daysByWeek35() { return (35 * 7 + 1) } // Week 35 is the annual long week with 8 days static get daysInMonths() { return [0, 31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31] } static get daysByMonths() { return [0, 31, 60, 91, 121, 152, 182, 213, 244, 274, 305, 335, 366] } static invalidInt(x: number, max: number) : boolean { if (Number.isInteger(x) && Number.isInteger(max)) return (x < 1 || x > max); else return true; } static getYeardayFromMonthDay(month: number, day: number) : number // month = 1 to 12, day = 1 to 31 { if (this.invalidInt(month, 12) || this.invalidInt(day, this.daysInMonths[month])) return 0; return (this.daysByMonths[month - 1] + day); } static getMonthDay(yearday: number) : MonthDay { if (this.invalidInt(yearday, this.maxDaysInYear)) return {month: 0, day: 0}; var month = 2; if (yearday > this.daysByMonths[4]) month = 6; if (yearday > this.daysByMonths[8]) month = 10; if (yearday > this.daysByMonths[month]) month++; else month--; if (yearday > this.daysByMonths[month]) month++; var day = yearday - this.daysByMonths[month - 1]; return { month, day }; } // week = 1 to 52, weekday = 1 to 7 or 8 for week 35 or 52. static getYeardayFromWeekDate(week: number, weekday: number) : number { if (week === 35 && weekday === 8) return this.daysByWeek35; if (week === 52 && weekday === 8) return this.maxDaysInYear; if (this.invalidInt(week, 52) || this.invalidInt(weekday, 7)) return 0; var yearday = (week - 1) * 7 + weekday; if (week > 35) yearday++; return yearday; } static getWeekDate(yearday: number) : WeekDate { if (this.invalidInt(yearday, this.maxDaysInYear)) return {week: 0, weekday: 0}; // Unknown if (yearday === this.maxDaysInYear) return {week: 52, weekday: 8}; // The last week if (yearday === this.daysByWeek35) return {week: 35, weekday: 8}; // The last week if (yearday >= this.daysByWeek35) yearday--; var week = (1 + Math.floor((yearday - 1) / 7)); var weekday = yearday - ((week - 1) * 7); return { week, weekday }; } static getWeekDateFromMonthDay(month: number, day: number) : WeekDate { return this.getWeekDate(this.getYeardayFromMonthDay(month, day));} static getMonthDayFromWeekDate(week: number, weekday: number) : MonthDay { return this.getMonthDay(this.getYeardayFromWeekDate(week, weekday)); } static getWeekdayName(week: number, weekday: number) : WeekdayName { if ((week === 35 || week === 52) && weekday === 8) weekday = 7; // Special Sunday if (this.invalidInt(week, 52) || this.invalidInt(weekday, 7)) return {longName:"",shortName:""}; var date = new Date(2001,0,weekday) // 1 to 7 = Monday to Sunday var longName = date.toLocaleString(undefined,{weekday:"long"}); var shortName = date.toLocaleString(undefined,{weekday:"short"}); return { longName, shortName }; } static isLeapYear(year: number) : boolean { if( this.invalidInt(year,9999)) return false; return ((year % 4 === 0) && (year % 100 !== 0) || (year % 400 === 0)); } // year = 1 to 9999, month = 1 to 12, day = 1 to 31 static newDate(year: number, month: number = 1, day: number = 1, hour: number = 0, minute: number = 0, second: number = 0, millisecond: number = 0) : Date | undefined { if (!this.isLeapYear(year)) { if (this.invalidInt(month, 12)) return undefined; if (this.invalidInt(day, this.daysInMonths[month])) return undefined; if (month === 12 && day === 31) { } // set the last day of the year else if (month > 1 && day === this.daysInMonths[month]) { month++; day = 1; } else if (month > 2) day++; } return new Date(year, month-1, day, hour, minute, second, millisecond); } // return the default Date String static toString(date ?: Date) : string { if (date instanceof Date && !isNaN(Number(date))) {} else return ""; var diff = 0; var day = date.getDate(); var month = date.getMonth() + 1; var year = date.getFullYear(); var yearbits = Number(date) - Number(new Date(date.getFullYear(), 0, 0)); var yearday = Math.floor(yearbits / (1000 * 60 * 60 * 24)); var weekDate = this.getWeekDate(yearday); var wdayName = this.getWeekdayName(weekDate.week, weekDate.weekday); if (!this.isLeapYear(year) && month > 2) { // the previous day diff = -1; day--; if (day === 0) day = this.daysInMonths[(month - 1)]; } date.setDate(date.getDate() + diff); var parts = date.toString().split(" "); parts[0] = wdayName.shortName; parts[2] = (day < 10 ? "0"+day : ""+day); return parts.join(" "); } } // Three extended functions are provided as DateTime extensions. interface Date { getNexDate(): MonthDay; getNexWeekDate(): WeekDate; getNexWeekdayName(): WeekdayName; } Date.prototype.getNexDate = function() { var year = this.getFullYear(); var month = this.getMonth()+1; var day = this.getDate(); if (NexCalendar.isLeapYear(year) || month < 3) return { year, month, day }; if (day === 1) return {year, month: month - 1, day: NexCalendar.daysInMonths[month - 1]}; else return {year: year, month: month, day: day - 1}; // month = 1 to 12 } Date.prototype.getNexWeekDate = function() { var diff = Number(this) - Number(new Date(this.getFullYear(), 0, 0)); var yearday = Math.floor(diff / (1000 * 60 * 60 * 24)); var wkDate = NexCalendar.getWeekDate(yearday); return {year: this.getFullYear(), week: wkDate.week, weekday: wkDate.weekday}; } Date.prototype.getNexWeekdayName = function() { var diff = Number(this) - Number(new Date(this.getFullYear(), 0, 0)); var yearday = Math.floor(diff / (1000 * 60 * 60 * 24)); var wkDate = NexCalendar.getWeekDate(yearday); return NexCalendar.getWeekdayName(wkDate.week, wkDate.weekday); } // e.g. var date = NexCalendar.newDate(2021,2,29); console.log(NexCalendar.toString(date)); var mthDay = (new Date()).getNexDate(); console.log("Month/day: " + mthDay.month + "/" + mthDay.day); var wkDate = (new Date()).getNexWeekDate(); console.log("Week Date: W" + wkDate.week + "-" + wkDate.weekday); var wkName = (new Date()).getNexWeekdayName(); console.log("Today is " + wkName.longName + " (" + wkName.shortName + ")"); console.log("Current Calendar: " + (new Date()).toString()); console.log("NexCalendar: " + NexCalendar.toString(new Date())); // Copyright © 2020 Dr. Donny C.F. Lai & Justin J.S. Lai. All Rights Reserved.