Files
Aislo/common_util/common_util_time.ts
T
eomsangdonandClaude Sonnet 5.5 59610fecf1 feat(시간 표시): 화면 시각 = 보는 사람 PC 시간대 · 서버 응답 UTC Z · 서버가 만드는 파일 글 = 한국 시각 (PLAN 593-1 · 요구 456)
- 공용 `common_util_time.ts`(날짜 · 시각 · 오늘/어제/n 일 전 · Z 없는 옛 글 = UTC) · `common_util_time.py`(UTC Z 글 · 한국 시각)
- 화면 바꿈: 대시보드(프로젝트 · 휴지통 · 시스템 로그 · 보관함 · 홈 카드) · B09 굳힘 · E03 · E05 · E07 · M02 호표 이력 · 목록 고친 날 · 지난 판
- 서버: 응답 datetime = UTC + Z(main.py) · 로컬 시각 저장 → UTC · 대시보드 경과일 = UTC 기준 · 엑셀 날짜 · 파일 이름 = 한국 시각
- 시험: test_593_time(서울 · 뉴욕 · Z 없는 옛 글 · 서버 글)

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-30 10:03:35 +09:00

73 lines
3.3 KiB
TypeScript

/* =============================================================================
* common_util_time.ts
* 화면 시간 표시 — 저장·전송은 UTC 하나, 보일 때만 보는 사람 PC 시간대로 바꿈.
* 서버 글(「2026-10-30 01:02:03」 · 「…T01:02:03」 = 시간대 표시 없음)은 UTC 로 읽음.
* 짝: `common_util/common_util_time.py`(서버가 만드는 파일 글 = 한국 시각 고정).
* ========================================================================== */
const DAY_MS = 24 * 3600 * 1000;
const HAS_ZONE = /(Z|[+-]\d\d:?\d\d)$/;
/** 서버 시각 글 → 밀리초(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}Z`);
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);