77 lines
2.6 KiB
TypeScript
77 lines
2.6 KiB
TypeScript
export const BUSINESS_TIME_ZONE = "Asia/Shanghai";
|
|
const BUSINESS_OFFSET = "+08:00";
|
|
const DATE_ONLY_PATTERN = /^\d{4}-\d{2}-\d{2}$/;
|
|
const EXPLICIT_OFFSET_PATTERN = /(?:Z|[+-]\d{2}:?\d{2})$/i;
|
|
|
|
export function parseApiDateTime(value?: string | Date | null): Date | null {
|
|
if (!value) return null;
|
|
if (value instanceof Date) return Number.isNaN(value.getTime()) ? null : value;
|
|
const text = value.trim();
|
|
if (!text) return null;
|
|
const isoText = text.includes(" ") ? text.replace(" ", "T") : text;
|
|
const normalized = DATE_ONLY_PATTERN.test(isoText)
|
|
? `${isoText}T00:00:00${BUSINESS_OFFSET}`
|
|
: EXPLICIT_OFFSET_PATTERN.test(isoText)
|
|
? isoText
|
|
: `${isoText}Z`;
|
|
const parsed = new Date(normalized);
|
|
return Number.isNaN(parsed.getTime()) ? null : parsed;
|
|
}
|
|
|
|
function businessParts(value?: string | Date | null) {
|
|
const parsed = parseApiDateTime(value);
|
|
if (!parsed) return null;
|
|
const parts = new Intl.DateTimeFormat("zh-CN", {
|
|
timeZone: BUSINESS_TIME_ZONE,
|
|
year: "numeric",
|
|
month: "2-digit",
|
|
day: "2-digit",
|
|
hour: "2-digit",
|
|
minute: "2-digit",
|
|
second: "2-digit",
|
|
hour12: false,
|
|
hourCycle: "h23",
|
|
}).formatToParts(parsed);
|
|
return Object.fromEntries(parts.map((part) => [part.type, part.value]));
|
|
}
|
|
|
|
export function formatDate(value?: string | Date | null, fallback = "-") {
|
|
const parts = businessParts(value);
|
|
return parts ? `${parts.year}-${parts.month}-${parts.day}` : fallback;
|
|
}
|
|
|
|
export function formatTime(value?: string | Date | null, fallback = "-") {
|
|
const parts = businessParts(value);
|
|
return parts ? `${parts.hour}:${parts.minute}` : fallback;
|
|
}
|
|
|
|
export function formatDateTime(value?: string | Date | null, fallback = "-") {
|
|
const parts = businessParts(value);
|
|
return parts
|
|
? `${parts.year}-${parts.month}-${parts.day} ${parts.hour}:${parts.minute}:${parts.second}`
|
|
: fallback;
|
|
}
|
|
|
|
export function businessDateKey(value?: string | Date | null): string | null {
|
|
const parts = businessParts(value);
|
|
return parts ? `${parts.year}-${parts.month}-${parts.day}` : null;
|
|
}
|
|
|
|
export function formatSessionTime(
|
|
value?: string | Date | null,
|
|
now: Date = new Date(),
|
|
): string {
|
|
const targetKey = businessDateKey(value);
|
|
const todayKey = businessDateKey(now);
|
|
const time = formatTime(value, "");
|
|
if (!targetKey || !todayKey || !time) return "";
|
|
const dayNumber = (key: string) => {
|
|
const [year, month, day] = key.split("-").map(Number);
|
|
return Date.UTC(year, month - 1, day) / 86_400_000;
|
|
};
|
|
const days = dayNumber(todayKey) - dayNumber(targetKey);
|
|
if (days === 0) return time;
|
|
if (days === 1) return `昨天 ${time}`;
|
|
return `${targetKey.slice(5).replace("-", "/")} ${time}`;
|
|
}
|