This commit is contained in:
wchino
2026-06-13 17:38:25 +08:00
parent e7e938f261
commit 7b33d9f9fa
190 changed files with 23222 additions and 4336 deletions

View File

@@ -0,0 +1,153 @@
export const PLATFORM_TIME_ZONE = 'Asia/Kuala_Lumpur';
export const PLATFORM_TIME_ZONE_OFFSET_MINUTES = 8 * 60;
export const PLATFORM_TIME_ZONE_OFFSET_LABEL = 'UTC+8';
const PICKER_DATETIME_RE = /^(\d{4})-(\d{2})-(\d{2})T(\d{2}):(\d{2})(?::(\d{2}))?$/;
function pad2(value) {
return String(value).padStart(2, '0');
}
function validDate(value) {
if (value == null)
return null;
const date = value instanceof Date ? value : new Date(value);
return Number.isNaN(date.getTime()) ? null : date;
}
function formatDateTime(date, locale, options) {
try {
return new Intl.DateTimeFormat(locale || 'en-US', options).format(date);
}
catch {
return new Intl.DateTimeFormat('en-US', options).format(date);
}
}
function parsePickerParts(value) {
const match = PICKER_DATETIME_RE.exec(value.trim());
if (!match)
return null;
const [, y, mo, d, h, mi, s = '00'] = match;
const parts = {
year: Number(y),
month: Number(mo),
day: Number(d),
hour: Number(h),
minute: Number(mi),
second: Number(s),
};
const wallAsUtc = new Date(Date.UTC(parts.year, parts.month - 1, parts.day, parts.hour, parts.minute, parts.second));
if (wallAsUtc.getUTCFullYear() !== parts.year ||
wallAsUtc.getUTCMonth() !== parts.month - 1 ||
wallAsUtc.getUTCDate() !== parts.day ||
wallAsUtc.getUTCHours() !== parts.hour ||
wallAsUtc.getUTCMinutes() !== parts.minute ||
wallAsUtc.getUTCSeconds() !== parts.second) {
return null;
}
return parts;
}
export function platformPickerDateTimeToIso(value) {
const trimmed = value.trim();
if (!trimmed)
return '';
const parts = parsePickerParts(trimmed);
if (!parts)
return trimmed;
const platformWallMs = Date.UTC(parts.year, parts.month - 1, parts.day, parts.hour, parts.minute, parts.second);
return new Date(platformWallMs - PLATFORM_TIME_ZONE_OFFSET_MINUTES * 60 * 1000).toISOString();
}
export function isoToPlatformPickerDateTime(value) {
if (!value?.trim())
return '';
const date = validDate(value);
if (!date)
return value.slice(0, 19);
const platform = new Date(date.getTime() + PLATFORM_TIME_ZONE_OFFSET_MINUTES * 60 * 1000);
return [
platform.getUTCFullYear(),
pad2(platform.getUTCMonth() + 1),
pad2(platform.getUTCDate()),
].join('-') + `T${pad2(platform.getUTCHours())}:${pad2(platform.getUTCMinutes())}:${pad2(platform.getUTCSeconds())}`;
}
export function isValidIsoDateTime(value) {
return validDate(value) !== null;
}
export function formatPlatformMatchDateTime(value, locale = 'en-US') {
const date = validDate(value);
if (!date)
return '';
const formatted = formatDateTime(date, locale, {
timeZone: PLATFORM_TIME_ZONE,
year: 'numeric',
month: '2-digit',
day: '2-digit',
hour: '2-digit',
minute: '2-digit',
});
return `${formatted} ${PLATFORM_TIME_ZONE_OFFSET_LABEL}`;
}
export function getLocalGmtOffsetLabel(value = new Date()) {
const date = validDate(value) ?? new Date();
const offsetMinutes = -date.getTimezoneOffset();
const sign = offsetMinutes >= 0 ? '+' : '-';
const abs = Math.abs(offsetMinutes);
const hours = Math.floor(abs / 60);
const minutes = abs % 60;
return minutes === 0 ? `GMT${sign}${hours}` : `GMT${sign}${hours}:${pad2(minutes)}`;
}
export function isSameLocalCalendarDay(value, now = new Date()) {
const date = validDate(value);
if (!date)
return false;
return (date.getFullYear() === now.getFullYear() &&
date.getMonth() === now.getMonth() &&
date.getDate() === now.getDate());
}
export function isInLocalToday(value, now = new Date()) {
const date = validDate(value);
if (!date)
return false;
const start = new Date(now);
start.setHours(0, 0, 0, 0);
const end = new Date(start);
end.setDate(end.getDate() + 1);
return date >= start && date < end;
}
export function isAfterLocalToday(value, now = new Date()) {
const date = validDate(value);
if (!date)
return false;
const end = new Date(now);
end.setHours(0, 0, 0, 0);
end.setDate(end.getDate() + 1);
return date >= end;
}
export function formatLocalMatchDateTime(value, locale = 'en-US', options = {}) {
const date = validDate(value);
if (!date)
return '';
const variant = options.variant ?? 'compact';
const time = formatDateTime(date, locale, {
hour: '2-digit',
minute: '2-digit',
...(options.includeSeconds ? { second: '2-digit' } : {}),
});
let text;
if (variant === 'compact') {
if (options.todayLabel && isSameLocalCalendarDay(date)) {
text = `${options.todayLabel} ${time}`;
}
else {
const day = formatDateTime(date, locale, { month: 'numeric', day: 'numeric' });
text = `${day} ${time}`;
}
}
else {
const day = formatDateTime(date, locale, {
year: 'numeric',
month: '2-digit',
day: '2-digit',
});
text = `${day} ${time}`;
}
if (options.includeTimeZone === false)
return text;
return `${text} ${getLocalGmtOffsetLabel(date)}`;
}