1
0

feat: finish datatime utility functions

This commit is contained in:
2026-06-30 16:28:06 +08:00
parent fcc022d070
commit 18f73b3084

View 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