UNPKG

@zwoninstitute/il-gaemi

Version:

Temporal 기반 Typescript 날짜/시간 유틸리티 패키지

514 lines (505 loc) 18.5 kB
"use strict"; var __defProp = Object.defineProperty; var __getOwnPropDesc = Object.getOwnPropertyDescriptor; var __getOwnPropNames = Object.getOwnPropertyNames; var __hasOwnProp = Object.prototype.hasOwnProperty; var __export = (target, all) => { for (var name in all) __defProp(target, name, { get: all[name], enumerable: true }); }; var __copyProps = (to, from, except, desc) => { if (from && typeof from === "object" || typeof from === "function") { for (let key of __getOwnPropNames(from)) if (!__hasOwnProp.call(to, key) && key !== except) __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable }); } return to; }; var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod); // src/index.ts var index_exports = {}; __export(index_exports, { DEFAULT_TIMEZONE: () => DEFAULT_TIMEZONE, DateError: () => DateError, IncompatibleOperationError: () => IncompatibleOperationError, InvalidDateError: () => InvalidDateError, InvalidDateFormatError: () => InvalidDateFormatError, MissingParameterError: () => MissingParameterError, Temporal: () => import_polyfill5.Temporal, UnsupportedFormatTypeError: () => UnsupportedFormatTypeError, convertToZonedDateTime: () => convertToZonedDateTime, format: () => format, formatKorean: () => formatKorean, formatRelative: () => formatRelative, fromUTC: () => fromUTC, getDate: () => getDate, getDateTime: () => getDateTime, getDateTimeUTC: () => getDateTimeUTC, getDateUTC: () => getDateUTC, getNextWorkday: () => getNextWorkday, getNow: () => getNow, getNowUTC: () => getNowUTC, getPreviousWorkday: () => getPreviousWorkday, getTime: () => getTime, getTimeZoneOffset: () => getTimeZoneOffset, getWeekDay: () => getWeekDay, getWeekNum: () => getWeekNum, isWorkday: () => isWorkday, toUTC: () => toUTC }); module.exports = __toCommonJS(index_exports); // src/date/index.ts var import_polyfill = require("@js-temporal/polyfill"); var DAY_OFF_ENV_KEY = "IL_GAEMI_DAY_OFFS"; var KOREAN_WEEKDAY_MAP = { \uC6D4: 1, \uD654: 2, \uC218: 3, \uBAA9: 4, \uAE08: 5, \uD1A0: 6, \uC77C: 7 }; var FALLBACK_DAY_OFFS = [KOREAN_WEEKDAY_MAP["\uD1A0"]]; var getEnv = () => { if (typeof process === "undefined" || typeof process.env === "undefined") { return null; } return process.env; }; function parseDayTokens(tokens) { const parsed = tokens.map((token) => token.replace(/요일$/u, "").trim()).map((token) => token ? KOREAN_WEEKDAY_MAP[token] : void 0).filter((value) => typeof value === "number"); if (!parsed.length) { return [...FALLBACK_DAY_OFFS]; } return Array.from(new Set(parsed)); } function getConfiguredDayOffWeekdays() { const env = getEnv(); if (!env) { return [...FALLBACK_DAY_OFFS]; } const raw = env[DAY_OFF_ENV_KEY]; if (!raw) { return [...FALLBACK_DAY_OFFS]; } const tokens = raw.split(",").map((token) => token.trim()).filter((token) => token.length > 0); if (!tokens.length) { return [...FALLBACK_DAY_OFFS]; } return parseDayTokens(tokens); } var DEFAULT_DAY_OFF_WEEKDAYS = getConfiguredDayOffWeekdays(); function isWorkday(date, holidayList = [], dayOffWeekdays = DEFAULT_DAY_OFF_WEEKDAYS) { const plainDate = typeof date === "string" ? import_polyfill.Temporal.PlainDate.from(date) : date; const dayOfWeek = plainDate.dayOfWeek; if (dayOffWeekdays.includes(dayOfWeek)) { return false; } const dateString = plainDate.toString(); const isHoliday = holidayList.some((holiday) => { if (holiday.recurring) { const holidayDate = import_polyfill.Temporal.PlainDate.from(holiday.date); return holidayDate.month === plainDate.month && holidayDate.day === plainDate.day; } return holiday.date === dateString; }); return !isHoliday; } function getWeekDay(date) { const plainDate = typeof date === "string" ? import_polyfill.Temporal.PlainDate.from(date) : date; return plainDate.dayOfWeek; } function getWeekNum(date) { const plainDate = typeof date === "string" ? import_polyfill.Temporal.PlainDate.from(date) : date; const dayOfWeek = plainDate.dayOfWeek; const daysFromMonday = dayOfWeek - 1; const mondayOfWeek = plainDate.subtract({ days: daysFromMonday }); const targetMonth = mondayOfWeek.month; const targetYear = mondayOfWeek.year; const firstDayOfMonth = mondayOfWeek.with({ day: 1 }); const firstDayWeekday = firstDayOfMonth.dayOfWeek; const daysToFirstMonday = firstDayWeekday === 1 ? 0 : 8 - firstDayWeekday; const firstMondayOfMonth = firstDayOfMonth.add({ days: daysToFirstMonday }); const daysDiff = mondayOfWeek.since(firstMondayOfMonth).days; const weekNum = Math.floor(daysDiff / 7) + 1; return { month: targetMonth, year: targetYear, weekNum }; } function getNextWorkday(date, holidayList = [], dayOffWeekdays = DEFAULT_DAY_OFF_WEEKDAYS) { let currentDate = typeof date === "string" ? import_polyfill.Temporal.PlainDate.from(date) : date; do { currentDate = currentDate.add({ days: 1 }); } while (!isWorkday(currentDate, holidayList, dayOffWeekdays)); return currentDate; } function getPreviousWorkday(date, holidayList = [], dayOffWeekdays = DEFAULT_DAY_OFF_WEEKDAYS) { let currentDate = typeof date === "string" ? import_polyfill.Temporal.PlainDate.from(date) : date; do { currentDate = currentDate.subtract({ days: 1 }); } while (!isWorkday(currentDate, holidayList, dayOffWeekdays)); return currentDate; } // src/timezone/index.ts var import_polyfill3 = require("@js-temporal/polyfill"); // src/utils/dateParser.ts var import_polyfill2 = require("@js-temporal/polyfill"); // src/errors.ts var DateError = class extends Error { constructor(message) { super(message); this.name = "DateError"; } }; var InvalidDateFormatError = class extends DateError { constructor(dateString, supportedFormats) { const message = supportedFormats ? `Unsupported date format: ${dateString}. Supported formats: ${supportedFormats.join(", ")}` : `Unsupported date format: ${dateString}`; super(message); this.name = "InvalidDateFormatError"; } }; var InvalidDateError = class extends DateError { constructor(dateString) { super(`Invalid date: ${dateString}`); this.name = "InvalidDateError"; } }; var UnsupportedFormatTypeError = class extends DateError { constructor(formatType, supportedTypes) { const message = supportedTypes ? `Unsupported format type: ${formatType}. Supported types: ${supportedTypes.join(", ")}` : `Unsupported format type: ${formatType}`; super(message); this.name = "UnsupportedFormatTypeError"; } }; var MissingParameterError = class extends DateError { constructor(parameterName) { super(`Missing required parameter: ${parameterName}`); this.name = "MissingParameterError"; } }; var IncompatibleOperationError = class extends DateError { constructor(operation, reason) { super(`Incompatible operation: ${operation}. Reason: ${reason}`); this.name = "IncompatibleOperationError"; } }; // src/utils/dateParser.ts function parseFlexibleDateString(dateString) { const trimmed = dateString.trim(); if (trimmed.includes("T") && (trimmed.includes("+") || trimmed.includes("Z") || trimmed.includes("["))) { try { return import_polyfill2.Temporal.ZonedDateTime.from(trimmed); } catch { throw new InvalidDateError(trimmed); } } if (trimmed.includes("T")) { try { return import_polyfill2.Temporal.PlainDateTime.from(trimmed); } catch { throw new InvalidDateError(trimmed); } } if (/^\d{4}-\d{1,2}-\d{1,2}$/.test(trimmed)) { try { return import_polyfill2.Temporal.PlainDate.from(trimmed); } catch { throw new InvalidDateError(trimmed); } } const koreanMatch = trimmed.match(/(\d{4})년\s*(\d{1,2})월\s*(\d{1,2})일/); if (koreanMatch) { const [, year, month, day] = koreanMatch; try { return import_polyfill2.Temporal.PlainDate.from({ year: parseInt(year), month: parseInt(month), day: parseInt(day) }); } catch { throw new InvalidDateError(trimmed); } } const dotMatch = trimmed.match(/^(\d{2,4})\.(\d{1,2})\.(\d{1,2})$/); if (dotMatch) { let [, year, month, day] = dotMatch; if (year.length === 2) { const currentYear = (/* @__PURE__ */ new Date()).getFullYear(); const century = Math.floor(currentYear / 100) * 100; const twoDigitYear = parseInt(year); year = (century + twoDigitYear).toString(); } try { return import_polyfill2.Temporal.PlainDate.from({ year: parseInt(year), month: parseInt(month), day: parseInt(day) }); } catch { throw new InvalidDateError(trimmed); } } const slashMatch = trimmed.match(/^(\d{1,4})\/(\d{1,2})\/(\d{1,4})$/); if (slashMatch) { let [, first, second, third] = slashMatch; let year, month, day; if (first.length === 4) { year = first; month = second; day = third; } else if (third.length <= 2) { month = first; day = second; const currentYear = (/* @__PURE__ */ new Date()).getFullYear(); const century = Math.floor(currentYear / 100) * 100; year = (century + parseInt(third)).toString(); } else { month = first; day = second; year = third; } try { return import_polyfill2.Temporal.PlainDate.from({ year: parseInt(year), month: parseInt(month), day: parseInt(day) }); } catch { throw new InvalidDateError(trimmed); } } const compactMatch = trimmed.match(/^(\d{4})(\d{2})(\d{2})$/); if (compactMatch) { const [, year, month, day] = compactMatch; try { return import_polyfill2.Temporal.PlainDate.from({ year: parseInt(year), month: parseInt(month), day: parseInt(day) }); } catch { throw new InvalidDateError(trimmed); } } const dashMatch = trimmed.match(/^(\d{4})-(\d{1,2})-(\d{1,2})$/); if (dashMatch) { const [, year, month, day] = dashMatch; try { return import_polyfill2.Temporal.PlainDate.from({ year: parseInt(year), month: parseInt(month), day: parseInt(day) }); } catch { throw new InvalidDateError(trimmed); } } try { return import_polyfill2.Temporal.PlainDateTime.from(trimmed); } catch { try { return import_polyfill2.Temporal.PlainDate.from(trimmed); } catch { try { return import_polyfill2.Temporal.ZonedDateTime.from(trimmed); } catch { throw new InvalidDateFormatError(trimmed, [ "ISO 8601 (YYYY-MM-DD)", "ISO DateTime (YYYY-MM-DDTHH:mm:ss)", "ISO with timezone (YYYY-MM-DDTHH:mm:ss+09:00[Asia/Seoul])", "Korean format (2024\uB144 1\uC6D4 15\uC77C)", "Dot separated (2024.01.15)", "Slash separated (2024/01/15)", "Compact format (20240115)" ]); } } } } // src/timezone/index.ts var DEFAULT_TIMEZONE = "Asia/Seoul"; function getNow() { return import_polyfill3.Temporal.Now.zonedDateTimeISO(DEFAULT_TIMEZONE); } function getDate(year, month, day) { return import_polyfill3.Temporal.PlainDate.from({ year, month, day }).toZonedDateTime(DEFAULT_TIMEZONE); } function getDateTime(year, month, day, hour, minute, second, millisecond, microsecond, nanosecond) { return import_polyfill3.Temporal.PlainDateTime.from({ year, month, day, hour, minute, second, millisecond, microsecond, nanosecond }).toZonedDateTime(DEFAULT_TIMEZONE); } function getDateTimeUTC(year, month, day, hour, minute, second, millisecond, microsecond, nanosecond) { return import_polyfill3.Temporal.PlainDateTime.from({ year, month, day, hour, minute, second, millisecond, microsecond, nanosecond }).toZonedDateTime("UTC"); } function getDateUTC(year, month, day) { return import_polyfill3.Temporal.PlainDate.from({ year, month, day }).toZonedDateTime("UTC"); } function getTime(hour, minute, second, millisecond, microsecond, nanosecond) { return import_polyfill3.Temporal.PlainTime.from({ hour, minute, second, millisecond, microsecond, nanosecond }); } function getNowUTC() { return import_polyfill3.Temporal.Now.zonedDateTimeISO("UTC"); } function convertToZonedDateTime(date, timeZone = DEFAULT_TIMEZONE) { const resolved = typeof date === "string" ? parseFlexibleDateString(date) : date; if (resolved instanceof import_polyfill3.Temporal.ZonedDateTime) { return resolved.withTimeZone(timeZone); } if (resolved instanceof import_polyfill3.Temporal.PlainDateTime) { return resolved.toZonedDateTime(timeZone); } const plainDateTime = resolved.toPlainDateTime(import_polyfill3.Temporal.PlainTime.from("00:00:00")); return plainDateTime.toZonedDateTime(timeZone); } function toUTC(zonedDateTime) { return zonedDateTime.withTimeZone("UTC"); } function fromUTC(utcDateTime, timeZone = DEFAULT_TIMEZONE) { return utcDateTime.withTimeZone(timeZone); } function getTimeZoneOffset(fromTimeZone, toTimeZone, referenceDate) { const baseDate = referenceDate || getNow(); const fromZoned = baseDate.withTimeZone(fromTimeZone); const toZoned = baseDate.withTimeZone(toTimeZone); const fromOffset = fromZoned.offsetNanoseconds; const toOffset = toZoned.offsetNanoseconds; return (toOffset - fromOffset) / (1e3 * 1e3 * 1e3 * 60 * 60); } // src/format/index.ts var import_polyfill4 = require("@js-temporal/polyfill"); function format(date, type = "datetime", formatString) { let temporalDate; if (typeof date === "string") { temporalDate = parseFlexibleDateString(date); } else { temporalDate = date; } switch (type) { case "date": if (temporalDate instanceof import_polyfill4.Temporal.PlainDate) { return temporalDate.toString(); } return temporalDate.toPlainDate().toString(); case "time": if (temporalDate instanceof import_polyfill4.Temporal.PlainDate) { throw new IncompatibleOperationError("formatting as time", "PlainDate does not contain time information"); } return temporalDate.toPlainTime().toString(); case "datetime": if (temporalDate instanceof import_polyfill4.Temporal.PlainDate) { return `${temporalDate.toString()} 00:00:00`; } const plainDateTime = temporalDate instanceof import_polyfill4.Temporal.ZonedDateTime ? temporalDate.toPlainDateTime() : temporalDate; return plainDateTime.toString().replace("T", " "); case "iso": if (temporalDate instanceof import_polyfill4.Temporal.ZonedDateTime) { return temporalDate.toString(); } if (temporalDate instanceof import_polyfill4.Temporal.PlainDateTime) { return temporalDate.toString(); } return temporalDate.toString(); case "custom": if (!formatString) { throw new MissingParameterError("formatString"); } return formatCustom(temporalDate, formatString); default: throw new UnsupportedFormatTypeError(type, ["date", "time", "datetime", "iso", "custom"]); } } function formatCustom(date, formatString) { let result = formatString; const plainDate = date instanceof import_polyfill4.Temporal.PlainDate ? date : date.toPlainDate(); const plainTime = date instanceof import_polyfill4.Temporal.PlainDate ? null : date.toPlainTime(); result = result.replace(/YYYY/g, plainDate.year.toString().padStart(4, "0")); result = result.replace(/YY/g, (plainDate.year % 100).toString().padStart(2, "0")); result = result.replace(/MM/g, plainDate.month.toString().padStart(2, "0")); result = result.replace(/M/g, plainDate.month.toString()); result = result.replace(/DD/g, plainDate.day.toString().padStart(2, "0")); result = result.replace(/D/g, plainDate.day.toString()); if (plainTime) { result = result.replace(/HH/g, plainTime.hour.toString().padStart(2, "0")); result = result.replace(/H/g, plainTime.hour.toString()); result = result.replace(/mm/g, plainTime.minute.toString().padStart(2, "0")); result = result.replace(/m/g, plainTime.minute.toString()); result = result.replace(/ss/g, plainTime.second.toString().padStart(2, "0")); result = result.replace(/s/g, plainTime.second.toString()); } return result; } function formatKorean(date) { return format(date, "custom", "YYYY\uB144 M\uC6D4 D\uC77C"); } function formatRelative(date, baseDate) { const targetDate = typeof date === "string" ? (() => { const parsed = parseFlexibleDateString(date); return parsed instanceof import_polyfill4.Temporal.ZonedDateTime ? parsed : parsed instanceof import_polyfill4.Temporal.PlainDateTime ? parsed.toZonedDateTime("Asia/Seoul") : parsed.toPlainDateTime(import_polyfill4.Temporal.PlainTime.from("00:00:00")).toZonedDateTime("Asia/Seoul"); })() : date instanceof import_polyfill4.Temporal.ZonedDateTime ? date : date instanceof import_polyfill4.Temporal.PlainDateTime ? date.toZonedDateTime("Asia/Seoul") : date.toPlainDateTime(import_polyfill4.Temporal.PlainTime.from("00:00:00")).toZonedDateTime("Asia/Seoul"); const base = baseDate || import_polyfill4.Temporal.Now.zonedDateTimeISO("Asia/Seoul"); const duration = targetDate.since(base, { largestUnit: "day" }); const days = duration.days; const hours = duration.hours; const minutes = duration.minutes; if (Math.abs(days) >= 1) { return days > 0 ? `${days}\uC77C \uD6C4` : `${Math.abs(days)}\uC77C \uC804`; } if (Math.abs(hours) >= 1) { return hours > 0 ? `${hours}\uC2DC\uAC04 \uD6C4` : `${Math.abs(hours)}\uC2DC\uAC04 \uC804`; } if (Math.abs(minutes) >= 1) { return minutes > 0 ? `${minutes}\uBD84 \uD6C4` : `${Math.abs(minutes)}\uBD84 \uC804`; } return "\uBC29\uAE08 \uC804"; } // src/index.ts var import_polyfill5 = require("@js-temporal/polyfill"); // Annotate the CommonJS export names for ESM import in node: 0 && (module.exports = { DEFAULT_TIMEZONE, DateError, IncompatibleOperationError, InvalidDateError, InvalidDateFormatError, MissingParameterError, Temporal, UnsupportedFormatTypeError, convertToZonedDateTime, format, formatKorean, formatRelative, fromUTC, getDate, getDateTime, getDateTimeUTC, getDateUTC, getNextWorkday, getNow, getNowUTC, getPreviousWorkday, getTime, getTimeZoneOffset, getWeekDay, getWeekNum, isWorkday, toUTC }); //# sourceMappingURL=index.js.map