/** * lib/kst.mjs — 한국 표준시(KST, UTC+9) 월 경계 계산. * * 월간 사용량(웹 아웃바운드·메일 통수)은 "KST 월" 기준으로 집계·초기화한다(§13.3·§13.4·§17.2). * UTC 로 저장된 시각을 KST 월로 나누려면 9시간을 더해 날짜를 구하고, 월 시작을 다시 UTC 로 돌린다. * 테스트가 고정 시각으로 검증할 수 있게 모든 함수는 `at` 을 인자로 받는다(AriMail usage.test 의 월 경계 오류 재발 방지). */ const KST_OFFSET_MS = 9 * 3600 * 1000; /** at 이 속한 KST 월의 시작(UTC ISO). 예: 2026-10-31T16:00:00Z(=11-01 01:00 KST) → 2026-10-31T15:00:00.000Z */ export function monthStart(at = new Date()) { const k = new Date(new Date(at).getTime() + KST_OFFSET_MS); return new Date(Date.UTC(k.getUTCFullYear(), k.getUTCMonth(), 1) - KST_OFFSET_MS).toISOString(); } /** 다음 KST 월 시작(UTC ISO) = 월간 사용량 초기화 시각 */ export function nextMonthStart(at = new Date()) { const k = new Date(new Date(at).getTime() + KST_OFFSET_MS); return new Date(Date.UTC(k.getUTCFullYear(), k.getUTCMonth() + 1, 1) - KST_OFFSET_MS).toISOString(); } /** KST 기준 YYYY-MM-DD */ export function kstDate(at = new Date()) { return new Date(new Date(at).getTime() + KST_OFFSET_MS).toISOString().slice(0, 10); } /** KST 기준 YYYY-MM */ export function kstMonth(at = new Date()) { return kstDate(at).slice(0, 7); } /** 화면 표시용 "YYYY-MM-DD HH:mm (KST)" */ export function fmtKst(at) { if (!at) return ''; const k = new Date(new Date(at).getTime() + KST_OFFSET_MS); return k.toISOString().slice(0, 16).replace('T', ' ') + ' KST'; }