This commit is contained in:
@@ -0,0 +1,87 @@
|
||||
const DEFAULT_TIMEZONE = "Asia/Shanghai";
|
||||
let appTimezone = DEFAULT_TIMEZONE;
|
||||
|
||||
export function getAppTimezone(): string {
|
||||
return appTimezone;
|
||||
}
|
||||
|
||||
export function setAppTimezone(timezone: string): void {
|
||||
try {
|
||||
new Intl.DateTimeFormat("en", { timeZone: timezone }).format();
|
||||
appTimezone = timezone;
|
||||
} catch {
|
||||
appTimezone = DEFAULT_TIMEZONE;
|
||||
}
|
||||
}
|
||||
|
||||
export function formatDateTime(value: Date | number | string, timezone = appTimezone): string {
|
||||
return new Intl.DateTimeFormat("zh-CN", {
|
||||
dateStyle: "medium",
|
||||
timeStyle: "short",
|
||||
timeZone: timezone,
|
||||
}).format(value instanceof Date || typeof value === "number" ? new Date(value) : new Date(value));
|
||||
}
|
||||
|
||||
export const formatDate = formatDateTime;
|
||||
export const dateText = formatDateTime;
|
||||
|
||||
export function toDateInputValue(value: Date | number | string, timezone = appTimezone): string {
|
||||
const date = value instanceof Date || typeof value === "number" ? new Date(value) : new Date(value);
|
||||
const parts = new Intl.DateTimeFormat("en-CA", {
|
||||
timeZone: timezone,
|
||||
year: "numeric",
|
||||
month: "2-digit",
|
||||
day: "2-digit",
|
||||
hour: "2-digit",
|
||||
minute: "2-digit",
|
||||
hourCycle: "h23",
|
||||
}).formatToParts(date);
|
||||
const values = Object.fromEntries(parts.map((part) => [part.type, part.value]));
|
||||
return `${values.year}-${values.month}-${values.day}T${values.hour}:${values.minute}`;
|
||||
}
|
||||
|
||||
export const formatDateTimeInput = toDateInputValue;
|
||||
|
||||
export function fromDateInput(value: string, timezone = appTimezone): string {
|
||||
const match = /^(\d{4})-(\d{2})-(\d{2})[ T](\d{2}):(\d{2})$/.exec(value.trim());
|
||||
if (!match) throw new Error("请选择有效的支付日期和时间");
|
||||
const year = Number(match[1]);
|
||||
const month = Number(match[2]);
|
||||
const day = Number(match[3]);
|
||||
const hour = Number(match[4]);
|
||||
const minute = Number(match[5]);
|
||||
const calendarProbe = new Date(0);
|
||||
calendarProbe.setUTCFullYear(year, month - 1, day);
|
||||
calendarProbe.setUTCHours(0, 0, 0, 0);
|
||||
if (month < 1 || month > 12 || day < 1 || day > 31 || calendarProbe.getUTCFullYear() !== year || calendarProbe.getUTCMonth() !== month - 1 || calendarProbe.getUTCDate() !== day || hour < 0 || hour > 23 || minute < 0 || minute > 59) {
|
||||
throw new Error("支付时间无效");
|
||||
}
|
||||
|
||||
// Interpret the form value as a wall-clock time in the configured zone.
|
||||
const wallUtc = Date.UTC(year, month - 1, day, hour, minute);
|
||||
const observedParts = new Intl.DateTimeFormat("en-CA", {
|
||||
timeZone: timezone,
|
||||
year: "numeric",
|
||||
month: "2-digit",
|
||||
day: "2-digit",
|
||||
hour: "2-digit",
|
||||
minute: "2-digit",
|
||||
hourCycle: "h23",
|
||||
}).formatToParts(new Date(wallUtc));
|
||||
const observed = Object.fromEntries(observedParts.map((part) => [part.type, part.value]));
|
||||
const observedUtc = Date.UTC(Number(observed.year), Number(observed.month) - 1, Number(observed.day), Number(observed.hour), Number(observed.minute));
|
||||
return new Date(wallUtc + wallUtc - observedUtc).toISOString();
|
||||
}
|
||||
|
||||
export const dateFromInput = fromDateInput;
|
||||
|
||||
export function currentMonth(timezone = appTimezone): string {
|
||||
const parts = Object.fromEntries(new Intl.DateTimeFormat("en-CA", {
|
||||
timeZone: timezone,
|
||||
year: "numeric",
|
||||
month: "2-digit",
|
||||
}).formatToParts(new Date()).map((part) => [part.type, part.value]));
|
||||
return `${parts.year}-${parts.month}`;
|
||||
}
|
||||
|
||||
export const monthNow = currentMonth;
|
||||
@@ -0,0 +1,24 @@
|
||||
export function formatMoney(cents: number, currency = "CNY", locale = "zh-CN"): string {
|
||||
return new Intl.NumberFormat(locale, {
|
||||
style: "currency",
|
||||
currency,
|
||||
minimumFractionDigits: 2,
|
||||
maximumFractionDigits: 2,
|
||||
}).format(cents / 100);
|
||||
}
|
||||
|
||||
export const money = formatMoney;
|
||||
|
||||
export function centsToAmount(cents: number): string {
|
||||
return (cents / 100).toFixed(2);
|
||||
}
|
||||
|
||||
export function amountToCents(value: string): number {
|
||||
const match = /^(\d+)(?:\.(\d{1,2}))?$/.exec(value.trim());
|
||||
if (!match) throw new Error("INVALID_AMOUNT");
|
||||
const cents = Number(match[1]) * 100 + Number((match[2] ?? "").padEnd(2, "0"));
|
||||
if (!Number.isSafeInteger(cents) || cents <= 0) throw new Error("INVALID_AMOUNT");
|
||||
return cents;
|
||||
}
|
||||
|
||||
export const yuanToCents = amountToCents;
|
||||
Reference in New Issue
Block a user