feat: finish datatime utility functions
This commit is contained in:
218
frontend/src/utils/datetime.ts
Normal file
218
frontend/src/utils/datetime.ts
Normal file
@@ -0,0 +1,218 @@
|
||||
|
||||
// YYC MARK:
|
||||
// This file is synchronized with "dt.py".
|
||||
// If this file or dt.py have bugs, all code should be changed together.
|
||||
|
||||
export const MONTH_DAY_COUNT: number[] = [31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31];
|
||||
|
||||
export const MIN_YEAR: number = 1950;
|
||||
export const MAX_YEAR: number = 2200;
|
||||
export const MIN_DATETIME = new Date(Date.UTC(MIN_YEAR, 0, 1, 0, 0, 0, 0));
|
||||
export const MAX_DATETIME = new Date(Date.UTC(MAX_YEAR, 0, 1, 0, 0, 0, 0));
|
||||
export const MIN_TIMESTAMP = Math.floor(MIN_DATETIME.getTime() / 60000);
|
||||
export const MAX_TIMESTAMP = Math.floor(MAX_DATETIME.getTime() / 60000);
|
||||
|
||||
export const DAY1_SPAN: number = 60 * 24;
|
||||
export const DAY7_SPAN: number = 7 * DAY1_SPAN;
|
||||
|
||||
const PRECOMPILED_LOOP_RULES = {
|
||||
year: new RegExp(/^Y([SR]{1})([1-9]\d*)$/),
|
||||
month: new RegExp(/^M([SR]{1})([ABCD]{1})([1-9]\d*)$/),
|
||||
week: new RegExp(/^W([TF]{7})([1-9]\d*)$/),
|
||||
day: new RegExp(/^D([1-9]\d*)$/)
|
||||
};
|
||||
|
||||
const PRECOMPILED_LOOP_STOP_RULES = {
|
||||
infinity: new RegExp(/^F$/),
|
||||
datetime: new RegExp(/^D([1-9]\d*|0)$/),
|
||||
times: new RegExp(/^T([1-9]\d*)$/)
|
||||
};
|
||||
|
||||
// region: Core Functions
|
||||
|
||||
export function resolveLoopRules4UI(strl: string) {
|
||||
|
||||
}
|
||||
|
||||
export function resolveLoopRules2Event(loopRules: string, loopDateTimeStart: number, loopDateTimeEnd: number, eventDateTimeStart: number, eventDateTimeEnd: number, timezoneOffset: number, clampStartDateTime: number) {
|
||||
|
||||
}
|
||||
|
||||
export function resolveLoopRules4Text(strl: string, startDateTime: number, timezoneOffset: number) {
|
||||
|
||||
}
|
||||
|
||||
// endregion
|
||||
|
||||
// region: Utility Functions
|
||||
|
||||
function leapYearCountEx(endYear: number, includeThis: boolean, baseYear: number, includeBase: boolean): number {
|
||||
if (!includeThis) endYear--;
|
||||
if (includeBase) baseYear--;
|
||||
|
||||
let endly = Math.floor(endYear / 4);
|
||||
endly -= Math.floor(endYear / 100);
|
||||
endly += Math.floor(endYear / 400);
|
||||
|
||||
let basely = Math.floor(baseYear / 4);
|
||||
basely -= Math.floor(baseYear / 100);
|
||||
basely += Math.floor(baseYear / 400);
|
||||
|
||||
return (endly - basely);
|
||||
}
|
||||
|
||||
function daysCount(year: number, month: number, day: number): number {
|
||||
let ly = leapYearCountEx(year, false, 1, true);
|
||||
let days = 365 * (year - 1);
|
||||
days += ly;
|
||||
|
||||
for (let index = 1; index < month; index++)
|
||||
days += MONTH_DAY_COUNT[index - 1]!;
|
||||
|
||||
if (month > 2 && isLeapYear(year))
|
||||
days += 1;
|
||||
|
||||
days += day - 1;
|
||||
return days;
|
||||
}
|
||||
|
||||
function monthsCount(year: number, month: number): number {
|
||||
return (year - 1) * 12 + (month - 1);
|
||||
}
|
||||
|
||||
function dayOfWeek(year: number, month: number, day: number): number {
|
||||
return daysCount(year, month, day) % 7;
|
||||
}
|
||||
|
||||
type DayInMonthInfo = [
|
||||
/** The day count to this day, counting from month head to tail. */
|
||||
daysForward: number,
|
||||
/** The day count to this day, counting from month tail to head. */
|
||||
daysBackward: number,
|
||||
/** The count of the week this day located, counting from month head to tail. */
|
||||
weeksForward: number,
|
||||
/** The day count in this week. (for `weeksForward` using) */
|
||||
weeksForwardDayOfWeek: number,
|
||||
/** The count of the week this day located, counting from month tail to head. */
|
||||
weeksBackward: number,
|
||||
/** The day count in this week. (for `weeksBackward` using) */
|
||||
weeksBackwardDayOfWeek: number,
|
||||
];
|
||||
|
||||
function getDayInMonth(year: number, month: number, day: number): DayInMonthInfo {
|
||||
let days = MONTH_DAY_COUNT[month - 1]! + ((month == 2 && isLeapYear(year)) ? 1 : 0);
|
||||
let firstDayOfWeek = dayOfWeek(year, month, 1);
|
||||
let bilateralDayOfWeek = (firstDayOfWeek + day - 1) % 7;
|
||||
|
||||
let dayForwards = day;
|
||||
let dayBackwards = days - day + 1;
|
||||
|
||||
let weeksForward = Math.floor((dayForwards - 1) / 7) + 1;
|
||||
let weeksBackwards = Math.floor((dayBackwards - 1) / 7) + 1;
|
||||
|
||||
return [dayForwards, dayBackwards, weeksForward, bilateralDayOfWeek, weeksBackwards, bilateralDayOfWeek];
|
||||
}
|
||||
|
||||
// YYC MARK:
|
||||
// I use Japanese word youbi (ようび) to present
|
||||
// the concept of "the day of week" exactly.
|
||||
|
||||
type RemanagedDayInMonth = [
|
||||
/** Count forward by days (Day N) */
|
||||
forwardByDayCount: number,
|
||||
/** Count backward by days (Nth day from the end) */
|
||||
backwardByDayCount: number,
|
||||
/** Count forward by weeks (Week N, Youbi X) */
|
||||
forwardByWeekCount: number,
|
||||
/** Count backward by weeks (Nth week from the end, Youbi X) */
|
||||
backwardByWeekCount: number,
|
||||
];
|
||||
|
||||
/**
|
||||
* Pass in an old date and a new month, and it will calculate
|
||||
* the new date corresponding to that old date in the new month
|
||||
* using four different calendar rules:
|
||||
* @param oldYear
|
||||
* @param oldMonth
|
||||
* @param oldDay
|
||||
* @param newYear
|
||||
* @param newMonth
|
||||
* @param isStrict
|
||||
* @returns
|
||||
*/
|
||||
function getRemanagedDayInMonth(oldYear: number, oldMonth: number, oldDay: number, newYear: number, newMonth: number, isStrict: boolean) {
|
||||
let ddata = getDayInMonth(oldYear, oldMonth, oldDay);
|
||||
let mdata = getMonthWeekStatistics(newYear, newMonth);
|
||||
let days = MONTH_DAY_COUNT[newMonth - 1]! + ((newMonth == 2 && isLeapYear(newYear)) ? 1 : 0);
|
||||
let firstDayOfWeek = dayOfWeek(newYear, newMonth, 1);
|
||||
//let lastDayOfWeek = (firstDayOfWeek + days - 1) % 7;
|
||||
|
||||
let methodA = undefined;
|
||||
let methodB = undefined;
|
||||
if (isStrict) {
|
||||
methodA = ddata[0] > days ? undefined : ddata[0];
|
||||
methodB = ddata[1] > days ? undefined : (days - ddata[1] + 1);
|
||||
} else {
|
||||
methodA = Math.min(ddata[0], days);
|
||||
methodB = days - Math.min(ddata[1], days) + 1;
|
||||
}
|
||||
|
||||
let methodC = undefined;
|
||||
if (ddata[2] <= mdata[ddata[3]]! || !isStrict) {
|
||||
let targetWeek = Math.min(ddata[2], mdata[ddata[3]]!);
|
||||
methodC = 1 + (targetWeek - 1) * 7 + ((ddata[3] + 7 - firstDayOfWeek) % 7);
|
||||
}
|
||||
|
||||
let methodD = undefined;
|
||||
if (ddata[4] <= mdata[ddata[5]]! || !isStrict) {
|
||||
// convert to type c and calc
|
||||
let targetWeek = mdata[ddata[5]]! - Math.min(ddata[4], mdata[ddata[5]]!) + 1;
|
||||
methodD = 1 + (targetWeek - 1) * 7 + ((ddata[5] + 7 - firstDayOfWeek) % 7);
|
||||
}
|
||||
|
||||
return [methodA, methodB, methodC, methodD];
|
||||
}
|
||||
|
||||
type MonthWeekStatistics = [
|
||||
/** The count of Youbi1 presented in this month. */
|
||||
youbi1: number,
|
||||
/** The count of Youbi2 presented in this month. */
|
||||
youbi2: number,
|
||||
/** The count of Youbi3 presented in this month. */
|
||||
youbi3: number,
|
||||
/** The count of Youbi4 presented in this month. */
|
||||
youbi4: number,
|
||||
/** The count of Youbi5 presented in this month. */
|
||||
youbi5: number,
|
||||
/** The count of Youbi6 presented in this month. */
|
||||
youbi6: number,
|
||||
/** The count of Youbi7 presented in this month. */
|
||||
youbi7: number,
|
||||
];
|
||||
|
||||
function getMonthWeekStatistics(year: number, month: number): MonthWeekStatistics {
|
||||
let days = MONTH_DAY_COUNT[month - 1]! + ((month == 2 && isLeapYear(year)) ? 1 : 0);
|
||||
let firstDayOfWeek = dayOfWeek(year, month, 1);
|
||||
|
||||
let result: MonthWeekStatistics = [4, 4, 4, 4, 4, 4, 4];
|
||||
let remain = days % 7;
|
||||
let week = firstDayOfWeek;
|
||||
while (remain > 0) {
|
||||
result[week % 7]! += 1;
|
||||
week++;
|
||||
remain--;
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
function isLeapYear(year: number): boolean {
|
||||
let isLeap = false;
|
||||
if (year % 4 == 0) isLeap = true;
|
||||
if (year % 100 == 0) isLeap = false;
|
||||
if (year % 400 == 0) isLeap = true;
|
||||
return isLeap;
|
||||
}
|
||||
|
||||
// endregion
|
||||
|
||||
Reference in New Issue
Block a user