UNPKG

@zwoninstitute/il-gaemi

Version:

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

469 lines (463 loc) 16.1 kB
// src/date/index.ts import { Temporal } from "@js-temporal/polyfill"; function isWorkday(date, holidayList = [], dayOffWeekdays = [6, 7]) { const plainDate = typeof date === "string" ? 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 = 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" ? Temporal.PlainDate.from(date) : date; return plainDate.dayOfWeek; } function getWeekNum(date) { const plainDate = typeof date === "string" ? 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 = [6, 7]) { let currentDate = typeof date === "string" ? Temporal.PlainDate.from(date) : date; do { currentDate = currentDate.add({ days: 1 }); } while (!isWorkday(currentDate, holidayList, dayOffWeekdays)); return currentDate; } function getPreviousWorkday(date, holidayList = [], dayOffWeekdays = [6, 7]) { let currentDate = typeof date === "string" ? Temporal.PlainDate.from(date) : date; do { currentDate = currentDate.subtract({ days: 1 }); } while (!isWorkday(currentDate, holidayList, dayOffWeekdays)); return currentDate; } // src/timezone/index.ts import { Temporal as Temporal2 } from "@js-temporal/polyfill"; var DEFAULT_TIMEZONE = "Asia/Seoul"; function getNow() { return Temporal2.Now.zonedDateTimeISO(DEFAULT_TIMEZONE); } function getDate(year, month, day) { return Temporal2.PlainDate.from({ year, month, day }).toZonedDateTime(DEFAULT_TIMEZONE); } function getDateTime(year, month, day, hour, minute, second, millisecond, microsecond, nanosecond) { return Temporal2.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 Temporal2.PlainDateTime.from({ year, month, day, hour, minute, second, millisecond, microsecond, nanosecond }).toZonedDateTime("UTC"); } function getDateUTC(year, month, day) { return Temporal2.PlainDate.from({ year, month, day }).toZonedDateTime("UTC"); } function getTime(hour, minute, second, millisecond, microsecond, nanosecond) { return Temporal2.PlainTime.from({ hour, minute, second, millisecond, microsecond, nanosecond }); } function getNowUTC() { return Temporal2.Now.zonedDateTimeISO("UTC"); } function convertToZonedDateTime(date, timeZone = DEFAULT_TIMEZONE) { if (typeof date === "string") { if (date.includes("T")) { return Temporal2.Instant.from(date).toZonedDateTimeISO(timeZone); } const plainDate = Temporal2.PlainDate.from(date); const plainDateTime = plainDate.toPlainDateTime(Temporal2.PlainTime.from("00:00:00")); return plainDateTime.toZonedDateTime(timeZone); } if (date instanceof Temporal2.PlainDateTime) { return date.toZonedDateTime(timeZone); } return date.withTimeZone(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 import { Temporal as Temporal3 } from "@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/format/index.ts function parseFlexibleDateString(dateString) { const trimmed = dateString.trim(); if (trimmed.includes("T") && (trimmed.includes("+") || trimmed.includes("Z") || trimmed.includes("["))) { try { return Temporal3.ZonedDateTime.from(trimmed); } catch { throw new InvalidDateError(trimmed); } } if (trimmed.includes("T")) { try { return Temporal3.PlainDateTime.from(trimmed); } catch { throw new InvalidDateError(trimmed); } } if (/^\d{4}-\d{1,2}-\d{1,2}$/.test(trimmed)) { try { return Temporal3.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 Temporal3.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 Temporal3.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 Temporal3.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 Temporal3.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 Temporal3.PlainDate.from({ year: parseInt(year), month: parseInt(month), day: parseInt(day) }); } catch { throw new InvalidDateError(trimmed); } } try { return Temporal3.PlainDate.from(trimmed); } catch { try { return Temporal3.PlainDateTime.from(trimmed); } catch { try { return Temporal3.ZonedDateTime.from(trimmed); } catch { throw new InvalidDateFormatError(dateString, [ "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)" ]); } } } } 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 Temporal3.PlainDate) { return temporalDate.toString(); } return temporalDate.toPlainDate().toString(); case "time": if (temporalDate instanceof Temporal3.PlainDate) { throw new IncompatibleOperationError("formatting as time", "PlainDate does not contain time information"); } return temporalDate.toPlainTime().toString(); case "datetime": if (temporalDate instanceof Temporal3.PlainDate) { return `${temporalDate.toString()} 00:00:00`; } const plainDateTime = temporalDate instanceof Temporal3.ZonedDateTime ? temporalDate.toPlainDateTime() : temporalDate; return plainDateTime.toString().replace("T", " "); case "iso": if (temporalDate instanceof Temporal3.ZonedDateTime) { return temporalDate.toString(); } if (temporalDate instanceof Temporal3.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 Temporal3.PlainDate ? date : date.toPlainDate(); const plainTime = date instanceof Temporal3.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 Temporal3.ZonedDateTime ? parsed : parsed instanceof Temporal3.PlainDateTime ? parsed.toZonedDateTime("Asia/Seoul") : parsed.toPlainDateTime(Temporal3.PlainTime.from("00:00:00")).toZonedDateTime("Asia/Seoul"); })() : date instanceof Temporal3.ZonedDateTime ? date : date instanceof Temporal3.PlainDateTime ? date.toZonedDateTime("Asia/Seoul") : date.toPlainDateTime(Temporal3.PlainTime.from("00:00:00")).toZonedDateTime("Asia/Seoul"); const base = baseDate || Temporal3.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/transform/index.ts import { Temporal as Temporal4 } from "@js-temporal/polyfill"; function temporalToDate(temporal) { if (!temporal) { return null; } if (temporal instanceof Temporal4.Instant) { return new Date(temporal.epochMilliseconds); } if (temporal instanceof Temporal4.ZonedDateTime) { return new Date(temporal.epochMilliseconds); } if (temporal instanceof Temporal4.PlainDate) { const zonedDateTime = temporal.toZonedDateTime({ timeZone: DEFAULT_TIMEZONE, plainTime: Temporal4.PlainTime.from("00:00:00") }); return new Date(zonedDateTime.epochMilliseconds); } if (temporal instanceof Temporal4.PlainDateTime) { const zonedDateTime = temporal.toZonedDateTime(DEFAULT_TIMEZONE); return new Date(zonedDateTime.epochMilliseconds); } throw new Error(`Unsupported temporal type: ${typeof temporal}`); } function dateToZonedDateTime(date, timeZone = DEFAULT_TIMEZONE) { if (!date) { return null; } return Temporal4.Instant.fromEpochMilliseconds(date.getTime()).toZonedDateTimeISO(timeZone); } function dateToPlainDate(date, timeZone = DEFAULT_TIMEZONE) { if (!date) { return null; } const zonedDateTime = dateToZonedDateTime(date, timeZone); return zonedDateTime?.toPlainDate() || null; } function dateToPlainDateTime(date, timeZone = DEFAULT_TIMEZONE) { if (!date) { return null; } const zonedDateTime = dateToZonedDateTime(date, timeZone); return zonedDateTime?.toPlainDateTime() || null; } // src/index.ts import { Temporal as Temporal5 } from "@js-temporal/polyfill"; export { DEFAULT_TIMEZONE, DateError, IncompatibleOperationError, InvalidDateError, InvalidDateFormatError, MissingParameterError, Temporal5 as Temporal, UnsupportedFormatTypeError, convertToZonedDateTime, dateToPlainDate, dateToPlainDateTime, dateToZonedDateTime, format, formatKorean, formatRelative, fromUTC, getDate, getDateTime, getDateTimeUTC, getDateUTC, getNextWorkday, getNow, getNowUTC, getPreviousWorkday, getTime, getTimeZoneOffset, getWeekDay, getWeekNum, isWorkday, temporalToDate, toUTC }; //# sourceMappingURL=index.mjs.map