/* ============================================================================= * common_util_time.ts * 화면 시간 표시 — 저장·전송은 UTC 하나, 보일 때만 보는 사람 PC 시간대로 바꿈. * 서버가 내는 시각은 끝 Z(UTC) · 시간대 표시 없는 옛 기록(「2026-10-30 10:02:03」 · 「…T10:02:03」)은 * 로컬(한국 시각, +09:00)로 적힌 것이라 한국 시각으로 읽음. * 짝: `common_util/common_util_time.py`(서버가 만드는 파일 글 = 한국 시각 고정). * ========================================================================== */ const DAY_MS = 24 * 3600 * 1000; const HAS_ZONE = /(Z|[+-]\d\d:?\d\d)$/; const OLD_LOCAL_ZONE = "+09:00"; // 옛 기록 = 한국 시각 /** 시각 글 → 밀리초(UTC 기준) · 시간대 표시 없는 옛 글 = 한국 시각 · 못 읽으면 null */ export function parseUtcMs(value: string | number | null | undefined): number | null { if (value === null || value === undefined || value === "") return null; if (typeof value === "number") return Number.isFinite(value) ? value : null; const iso = value.trim().replace(" ", "T"); if (!iso.includes("T")) return null; // 날짜만 = 시각이 아니라 바꾸지 않음 const ms = Date.parse(HAS_ZONE.test(iso) ? iso : `${iso}${OLD_LOCAL_ZONE}`); return Number.isNaN(ms) ? null : ms; } const two = (n: number): string => String(n).padStart(2, "0"); function dateOf(d: Date): string { return `${d.getFullYear()}-${two(d.getMonth() + 1)}-${two(d.getDate())}`; } /** 날짜만 「YYYY-MM-DD」(보는 PC 시간대) · 빈 값 · 못 읽음 = empty */ export function dateText(value: string | null | undefined, empty = "-"): string { const ms = parseUtcMs(value); if (ms !== null) return dateOf(new Date(ms)); return /^\d{4}-\d\d-\d\d$/.test(String(value ?? "").trim()) ? String(value).trim() : empty; } /** 시:분 「HH:MM」(보는 PC 시간대) · 빈 값 · 못 읽음 = 빈 글 */ export function timeText(value: string | null | undefined): string { const ms = parseUtcMs(value); if (ms === null) return ""; const d = new Date(ms); return `${two(d.getHours())}:${two(d.getMinutes())}`; } /** 날짜 + 시각 「YYYY-MM-DD HH:MM」(보는 PC 시간대) · 빈 값 · 못 읽음 = empty */ export function dateTimeText(value: string | null | undefined, empty = "—"): string { const ms = parseUtcMs(value); if (ms === null) return empty; const d = new Date(ms); return `${dateOf(d)} ${two(d.getHours())}:${two(d.getMinutes())}`; } /** 달력 날짜(보는 PC 시간대) 차이 — 오늘 0 · 어제 1 · 미래 음수 */ export function calendarDaysAgo(value: string | null | undefined, now = Date.now()): number | null { const ms = parseUtcMs(value); if (ms === null) return null; const day = (t: number): number => { const d = new Date(t); return Date.UTC(d.getFullYear(), d.getMonth(), d.getDate()); }; return Math.round((day(now) - day(ms)) / DAY_MS); } /** 「오늘」 · 「어제」 · 「n 일 전」(7 일 미만) · 그 밖 날짜 · 빈 값 = empty */ export function agoText(value: string | null | undefined, empty = "-", now = Date.now()): string { const days = calendarDaysAgo(value, now); if (days === null) return empty; if (days <= 0) return "오늘"; if (days === 1) return "어제"; if (days < 7) return `${days} 일 전`; return dateText(value, empty); } /** 옛 이름 — 확정 시각 등(`dateTimeText` 와 같음) */ export const kstText = (utc: string | null | undefined): string => dateTimeText(utc);