### Motivation While using the browser default locale is often good enough, we probably want more full control over locales, especially when we allow users to choose their own locale — it's a poor experience to not have dates and numbers formatted consistently with your chosen locale. ### Changes Ensures (almost) all instances of `<sl-format-date>` as well as `Intl.*` have the correct locale passed into them from our Lit localization system. The one exception here is in `frontend/src/utils/number.ts` where ordinal suffixes aren't localized, so the locale is hardcoded to `en` — I'll revisit this in the future.
185 lines
4.3 KiB
TypeScript
185 lines
4.3 KiB
TypeScript
import { parseCron } from "@cheap-glitch/mi-cron";
|
|
import { msg, str } from "@lit/localize";
|
|
|
|
import { getLocale } from "./localization";
|
|
import * as numberUtils from "./number";
|
|
|
|
export const getNextDate = parseCron.nextDate;
|
|
|
|
export type ScheduleInterval = "daily" | "weekly" | "monthly";
|
|
|
|
/**
|
|
* Parse interval from cron expression
|
|
**/
|
|
export function getScheduleInterval(schedule: string): ScheduleInterval {
|
|
const [_minute, _hour, dayOfMonth, _month, dayOfWeek] = schedule.split(" ");
|
|
if (dayOfMonth === "*") {
|
|
if (dayOfWeek === "*") {
|
|
return "daily";
|
|
}
|
|
return "weekly";
|
|
}
|
|
return "monthly";
|
|
}
|
|
|
|
/**
|
|
* Get human-friendly date from cron expression
|
|
* Example: "Monday, December 12, 2022 at 12:00 AM PST"
|
|
**/
|
|
export function humanizeNextDate(
|
|
schedule: string,
|
|
options: { length?: "short" } = {},
|
|
): string {
|
|
const nextDate = parseCron.nextDate(schedule);
|
|
|
|
if (!nextDate) return "";
|
|
|
|
if (options.length === "short") {
|
|
return nextDate.toLocaleString(undefined, {
|
|
month: "numeric",
|
|
day: "numeric",
|
|
year: "numeric",
|
|
hour: "numeric",
|
|
minute: "numeric",
|
|
});
|
|
}
|
|
|
|
return nextDate.toLocaleString(undefined, {
|
|
weekday: "long",
|
|
month: "long",
|
|
day: "numeric",
|
|
year: "numeric",
|
|
hour: "numeric",
|
|
minute: "numeric",
|
|
timeZoneName: "short",
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Get human-friendly schedule from cron expression
|
|
* Example: "Every day at 9:30 AM CDT"
|
|
**/
|
|
export function humanizeSchedule(
|
|
schedule: string,
|
|
options: { length?: "short" } = {},
|
|
numberFormatter = numberUtils.numberFormatter,
|
|
): string {
|
|
const interval = getScheduleInterval(schedule);
|
|
const parsed = parseCron(schedule);
|
|
if (!parsed) {
|
|
// Invalid date
|
|
return "";
|
|
}
|
|
const { days } = parsed;
|
|
const nextDate = parseCron.nextDate(schedule)!;
|
|
const formattedWeekDay = nextDate.toLocaleString(undefined, {
|
|
weekday: "long",
|
|
});
|
|
|
|
let intervalMsg = "";
|
|
|
|
if (options.length === "short") {
|
|
switch (interval) {
|
|
case "daily": {
|
|
const formattedTime = nextDate.toLocaleString(undefined, {
|
|
minute: "numeric",
|
|
hour: "numeric",
|
|
});
|
|
intervalMsg = msg(str`${formattedTime} daily`);
|
|
break;
|
|
}
|
|
case "weekly":
|
|
intervalMsg = msg(str`Every ${formattedWeekDay}`);
|
|
break;
|
|
case "monthly": {
|
|
const { format } = numberFormatter(getLocale());
|
|
intervalMsg = msg(
|
|
str`Monthly on the ${format(days[0], { ordinal: true })}`,
|
|
);
|
|
|
|
break;
|
|
}
|
|
|
|
default:
|
|
break;
|
|
}
|
|
} else {
|
|
const formattedTime = nextDate.toLocaleString(undefined, {
|
|
minute: "numeric",
|
|
hour: "numeric",
|
|
timeZoneName: "short",
|
|
});
|
|
|
|
switch (interval) {
|
|
case "daily":
|
|
intervalMsg = msg(str`Every day at ${formattedTime}`);
|
|
break;
|
|
case "weekly":
|
|
intervalMsg = msg(
|
|
str`Every ${formattedWeekDay}
|
|
at ${formattedTime}`,
|
|
);
|
|
break;
|
|
case "monthly":
|
|
intervalMsg = msg(
|
|
str`On day ${nextDate.getDate()} of the month at ${formattedTime}`,
|
|
);
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
|
|
return intervalMsg;
|
|
}
|
|
|
|
/**
|
|
* Get schedule as UTC cron job expression
|
|
* https://kubernetes.io/docs/concepts/workloads/controllers/cron-jobs/#cron-schedule-syntax
|
|
**/
|
|
export function getUTCSchedule({
|
|
interval,
|
|
minute,
|
|
hour,
|
|
period,
|
|
dayOfWeek,
|
|
dayOfMonth,
|
|
}: {
|
|
interval: ScheduleInterval;
|
|
minute: number | string;
|
|
hour: number | string;
|
|
period: "AM" | "PM";
|
|
dayOfWeek?: number;
|
|
dayOfMonth?: number;
|
|
}): string {
|
|
const localDate = new Date();
|
|
|
|
// Convert 12-hr to 24-hr time
|
|
let periodOffset = 0;
|
|
|
|
if (hour === 12) {
|
|
if (period === "AM") {
|
|
periodOffset = -12;
|
|
}
|
|
} else if (period === "PM") {
|
|
periodOffset = 12;
|
|
}
|
|
|
|
localDate.setHours(+hour + periodOffset);
|
|
localDate.setMinutes(+minute);
|
|
|
|
if (interval === "monthly" && dayOfMonth) {
|
|
localDate.setDate(dayOfMonth);
|
|
} else if (interval == "weekly" && dayOfWeek) {
|
|
localDate.setDate(localDate.getDate() + dayOfWeek - localDate.getDay());
|
|
}
|
|
|
|
const date = interval === "monthly" ? localDate.getUTCDate() : "*";
|
|
const day = interval === "weekly" ? localDate.getUTCDay() : "*";
|
|
const month = "*";
|
|
|
|
const schedule = `${localDate.getUTCMinutes()} ${localDate.getUTCHours()} ${date} ${month} ${day}`;
|
|
|
|
return schedule;
|
|
}
|