UNPKG

datezone

Version:

A lightweight and comprehensive date and timeZone utility library for JavaScript.

167 lines 6.79 kB
import { timestampToCalendar } from "./calendar.pub.js"; import { getUTCtoTimezoneOffsetMinutes } from "./offset.pub.js"; import { isDST, isUTC } from "./timezone.pub.js"; /** * To isostring. * * @param ts - The timestamp to format * @param timeZone - Optional timeZone. If not provided, uses the system's local timeZone. * @returns The ISO formatted date string * @see https://datezone.dev/docs/reference/iso#toisostring */ export function toISOString(ts, timeZone) { let dt; let timeZoneOffsetMinutes; if (!timeZone) { const d = new Date(ts); // Fast path for local timeZone dt = { day: d.getDate(), hour: d.getHours(), millisecond: d.getMilliseconds(), minute: d.getMinutes(), month: d.getMonth() + 1, second: d.getSeconds(), timeZoneOffsetMinutes: -d.getTimezoneOffset(), year: d.getFullYear(), }; timeZoneOffsetMinutes = dt.timeZoneOffsetMinutes; } else if (isUTC(timeZone)) { // Fast path for UTC const d = new Date(ts); dt = { day: d.getUTCDate(), hour: d.getUTCHours(), millisecond: d.getUTCMilliseconds(), minute: d.getUTCMinutes(), month: d.getUTCMonth() + 1, second: d.getUTCSeconds(), timeZoneOffsetMinutes: 0, year: d.getUTCFullYear(), }; timeZoneOffsetMinutes = 0; } else if (!isDST(timeZone)) { // Fast path for non-DST timeZones (fixed offset zones) timeZoneOffsetMinutes = getUTCtoTimezoneOffsetMinutes(ts, timeZone); // Calculate timeZone time directly from UTC time + offset const offsetMs = timeZoneOffsetMinutes * 60 * 1000; const zonedTs = ts + offsetMs; const d = new Date(zonedTs); dt = { day: d.getUTCDate(), hour: d.getUTCHours(), millisecond: ts % 1000, minute: d.getUTCMinutes(), month: d.getUTCMonth() + 1, second: d.getUTCSeconds(), timeZoneOffsetMinutes, year: d.getUTCFullYear(), }; } else { // Path for DST timeZones (complex calculation required) const parts = timestampToCalendar(ts, timeZone); // Removed incorrect negative year adjustment timeZoneOffsetMinutes = getUTCtoTimezoneOffsetMinutes(ts, timeZone); dt = { day: parts.day, hour: parts.hour, millisecond: parts.millisecond, minute: parts.minute, month: parts.month, second: parts.second, timeZoneOffsetMinutes, year: parts.year, }; } // Format as ISO string: YYYY-MM-DDTHH:mm:ss.sss±HH:MM or Z for UTC const year = String(dt.year).padStart(4, "0"); const month = String(dt.month).padStart(2, "0"); const day = String(dt.day).padStart(2, "0"); const hour = String(dt.hour).padStart(2, "0"); const minute = String(dt.minute).padStart(2, "0"); const second = String(dt.second).padStart(2, "0"); const ms = String(dt.millisecond).padStart(3, "0"); let offsetString; if (timeZoneOffsetMinutes === 0) { offsetString = "Z"; } else { const sign = timeZoneOffsetMinutes >= 0 ? "+" : "-"; const absOffset = Math.abs(timeZoneOffsetMinutes); const offsetHours = Math.floor(absOffset / 60); const offsetMinutes = absOffset % 60; offsetString = `${sign}${String(offsetHours).padStart(2, "0")}:${String(offsetMinutes).padStart(2, "0")}`; } return `${year}-${month}-${day}T${hour}:${minute}:${second}.${ms}${offsetString}`; } /** * @deprecated Use {@link fromISOString} instead. */ export function parseISO(isoString) { return fromISOString(isoString); } /** * Parse ISO string. * * @param isoString - The ISO string to parse. * @returns The timestamp. * @see https://datezone.dev/docs/reference/iso#fromisostring */ export function fromISOString(isoString) { // Fast-path 1: explicit UTC (trailing 'Z') → rely on native parser which is highly optimized if (isoString.endsWith("Z")) { return Date.parse(isoString); } // Detect a numeric UTC offset at the end of the string (e.g. "+05:30" or "-0800") // The colon between hour/minute is optional because both "+HH:MM" and "+HHMM" are valid ISO-8601 forms. const tzMatch = isoString.match(/([+-])(\d{2}):?(\d{2})$/); // Fast-path 2: no explicit offset → treat as local time. Native Date constructor is fastest here. if (!tzMatch) { return new Date(isoString).getTime(); } // --- Manual parsing for strings that contain an explicit numeric offset --- // Separate the main date-time part from the offset so we can parse the fields quickly. const dateTimePart = isoString.slice(0, tzMatch.index ?? undefined); // Split "YYYY-MM-DD" and "HH:mm:ss.sss" components const [datePartRaw, timePartRaw] = dateTimePart.split("T"); const datePart = datePartRaw; const timePart = timePartRaw ?? "00:00:00"; const [yearStr, monthStr, dayStr] = datePart.split("-"); // Time part can be HH or HH:mm or HH:mm:ss or HH:mm:ss.sss const timeSegments = timePart.split(":"); const hourStr = timeSegments[0] ?? "0"; const minuteStr = timeSegments[1] ?? "0"; const secondAndMsSegment = timeSegments[2] ?? "0"; let secondStr = "0"; let msStr = "0"; if (secondAndMsSegment.includes(".")) { const [secPart, msPart] = secondAndMsSegment.split("."); secondStr = secPart; msStr = msPart; } else if (timeSegments.length > 2) { secondStr = secondAndMsSegment; // there was a seconds field without milliseconds } // Convert parts to numbers (Number constructor is faster than parseInt for valid numeric strings) const year = Number(yearStr); const month = Number(monthStr); const day = Number(dayStr); const hour = Number(hourStr); const minute = Number(minuteStr); const second = Number(secondStr); // Ensure we always have exactly 3 fractional millisecond digits (pad / truncate) const millisecond = Number(`${msStr}000`.slice(0, 3)); // Calculate the offset in minutes const sign = tzMatch[1] === "+" ? 1 : -1; const offsetHour = Number(tzMatch[2]); const offsetMinute = Number(tzMatch[3]); const offsetTotalMinutes = sign * (offsetHour * 60 + offsetMinute); // Create a UTC timestamp from the parsed components then subtract the offset to convert to UTC const utcTs = Date.UTC(year, month - 1, day, hour, minute, second, millisecond) - offsetTotalMinutes * 60 * 1000; return utcTs; } //# sourceMappingURL=iso.pub.js.map