52 lines
1.8 KiB
TypeScript
52 lines
1.8 KiB
TypeScript
import type { RecurrenceRule } from "@prisma/client";
|
|
|
|
const clampDay = (year: number, monthIndex: number, day: number) => {
|
|
const lastDay = new Date(Date.UTC(year, monthIndex + 1, 0)).getUTCDate();
|
|
return Math.min(day, lastDay);
|
|
};
|
|
|
|
export const addMonthsWithClamp = (date: Date, months: number) => {
|
|
const year = date.getUTCFullYear();
|
|
const month = date.getUTCMonth();
|
|
const day = date.getUTCDate();
|
|
const hour = date.getUTCHours();
|
|
const minute = date.getUTCMinutes();
|
|
const second = date.getUTCSeconds();
|
|
|
|
const targetMonth = month + months;
|
|
const targetYear = year + Math.floor(targetMonth / 12);
|
|
const normalizedMonth = ((targetMonth % 12) + 12) % 12;
|
|
const clampedDay = clampDay(targetYear, normalizedMonth, day);
|
|
|
|
return new Date(Date.UTC(targetYear, normalizedMonth, clampedDay, hour, minute, second));
|
|
};
|
|
|
|
export const addYearsWithClamp = (date: Date, years: number) => {
|
|
const targetYear = date.getUTCFullYear() + years;
|
|
const month = date.getUTCMonth();
|
|
const day = date.getUTCDate();
|
|
const hour = date.getUTCHours();
|
|
const minute = date.getUTCMinutes();
|
|
const second = date.getUTCSeconds();
|
|
const clampedDay = clampDay(targetYear, month, day);
|
|
return new Date(Date.UTC(targetYear, month, clampedDay, hour, minute, second));
|
|
};
|
|
|
|
export const nextDueAt = (dueAt: Date, rule: RecurrenceRule) => {
|
|
const interval = rule.interval ?? 1;
|
|
switch (rule.type) {
|
|
case "hourly":
|
|
return new Date(dueAt.getTime() + interval * 60 * 60 * 1000);
|
|
case "daily":
|
|
return new Date(dueAt.getTime() + interval * 24 * 60 * 60 * 1000);
|
|
case "weekly":
|
|
return new Date(dueAt.getTime() + interval * 7 * 24 * 60 * 60 * 1000);
|
|
case "monthly":
|
|
return addMonthsWithClamp(dueAt, interval);
|
|
case "yearly":
|
|
return addYearsWithClamp(dueAt, interval);
|
|
default:
|
|
return dueAt;
|
|
}
|
|
};
|