UNPKG

locutus

Version:

Locutus other languages' standard libraries to JavaScript for fun and educational purposes

210 lines (209 loc) 7 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.Parse = Parse; const GO_PARSE_TOKENS = [ 'Z07:00', '-07:00', '-0700', '2006', 'PM', 'pm', '15', '06', '01', '02', '03', '04', '05', '1', '2', '3', ]; const escapeRegex = (value) => value.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'); const isTokenAt = (layout, index) => GO_PARSE_TOKENS.find((token) => layout.startsWith(token, index)); const tokenRegex = (token) => { switch (token) { case '2006': return '(\\d{4})'; case '06': return '(\\d{2})'; case '01': return '(0[1-9]|1[0-2])'; case '1': return '([1-9]|1[0-2])'; case '02': return '(0[1-9]|[12]\\d|3[01])'; case '2': return '([1-9]|[12]\\d|3[01])'; case '15': return '([01]\\d|2[0-3])'; case '03': return '(0[1-9]|1[0-2])'; case '3': return '([1-9]|1[0-2])'; case '04': return '([0-5]\\d)'; case '05': return '([0-5]\\d)'; case 'PM': return '(AM|PM)'; case 'pm': return '(am|pm)'; case '-0700': return '([+-]\\d{4})'; case '-07:00': return '([+-]\\d{2}:\\d{2})'; case 'Z07:00': return '(Z|[+-]\\d{2}:\\d{2})'; default: return escapeRegex(token); } }; const parseOffsetMinutes = (offset) => { if (offset === 'Z') { return 0; } const compact = offset.match(/^([+-])(\d{2})(\d{2})$/); if (compact?.[1] && compact[2] && compact[3]) { const sign = compact[1] === '-' ? -1 : 1; const hours = Number.parseInt(compact[2], 10); const minutes = Number.parseInt(compact[3], 10); return sign * (hours * 60 + minutes); } const colon = offset.match(/^([+-])(\d{2}):(\d{2})$/); if (colon?.[1] && colon[2] && colon[3]) { const sign = colon[1] === '-' ? -1 : 1; const hours = Number.parseInt(colon[2], 10); const minutes = Number.parseInt(colon[3], 10); return sign * (hours * 60 + minutes); } throw new TypeError('Parse(): invalid offset'); }; const resolveTwoDigitYear = (year) => (year >= 69 ? 1900 + year : 2000 + year); function Parse(layout, value) { // discuss at: https://locutus.io/golang/time/Parse // parity verified: Go 1.23 // original by: Kevin van Zonneveld (https://kvz.io) // note 1: Supports a focused subset of Go layout tokens and parses into a JavaScript Date. // note 1: Layout markers outside the supported subset are treated as literals. // note 2: Returned Date is absolute time in UTC; timezone offsets in input are respected. // example 1: Parse('2006-01-02', '2026-03-03') // returns 1: new Date('2026-03-03T00:00:00.000Z') // example 2: Parse('2006-01-02 15:04:05', '2026-03-03 13:14:15') // returns 2: new Date('2026-03-03T13:14:15.000Z') // example 3: Parse('01/02/06 03:04 PM', '03/03/26 01:14 PM') // returns 3: new Date('2026-03-03T13:14:00.000Z') // example 4: Parse('2006-01-02T15:04:05-07:00', '2026-03-03T13:14:15+02:30') // returns 4: new Date('2026-03-03T10:44:15.000Z') // example 5: Parse('2006-01-02T15:04:05Z07:00', '2026-03-03T13:14:15Z') // returns 5: new Date('2026-03-03T13:14:15.000Z') const resolvedLayout = String(layout); const resolvedValue = String(value); const tokenOrder = []; let regexSource = '^'; for (let i = 0; i < resolvedLayout.length;) { const token = isTokenAt(resolvedLayout, i); if (token) { regexSource += tokenRegex(token); tokenOrder.push(token); i += token.length; continue; } const char = resolvedLayout[i]; if (char === undefined) { break; } regexSource += escapeRegex(char); i += 1; } regexSource += '$'; const match = new RegExp(regexSource).exec(resolvedValue); if (!match) { throw new TypeError('Parse(): input does not match layout'); } let year = 1970; let month = 1; let day = 1; let hour24 = 0; let minute = 0; let second = 0; let hour12; let ampm; let offsetMinutes = 0; let hasOffset = false; let matchIndex = 1; for (const token of tokenOrder) { const raw = match[matchIndex]; matchIndex += 1; if (!raw) { continue; } switch (token) { case '2006': year = Number.parseInt(raw, 10); break; case '06': year = resolveTwoDigitYear(Number.parseInt(raw, 10)); break; case '01': case '1': month = Number.parseInt(raw, 10); break; case '02': case '2': day = Number.parseInt(raw, 10); break; case '15': hour24 = Number.parseInt(raw, 10); break; case '03': case '3': hour12 = Number.parseInt(raw, 10); break; case '04': minute = Number.parseInt(raw, 10); break; case '05': second = Number.parseInt(raw, 10); break; case 'PM': case 'pm': ampm = raw.toUpperCase() === 'PM' ? 'PM' : 'AM'; break; case '-0700': case '-07:00': case 'Z07:00': offsetMinutes = parseOffsetMinutes(raw); hasOffset = true; break; default: break; } } if (hour12 !== undefined) { if (ampm === 'PM' && hour12 < 12) { hour24 = hour12 + 12; } else if (ampm === 'AM' && hour12 === 12) { hour24 = 0; } else { hour24 = hour12; } } if (month < 1 || month > 12 || day < 1 || day > 31 || hour24 < 0 || hour24 > 23 || minute < 0 || minute > 59) { throw new TypeError('Parse(): invalid datetime components'); } const localMillis = Date.UTC(year, month - 1, day, hour24, minute, second); const localCheck = new Date(localMillis); if (localCheck.getUTCFullYear() !== year || localCheck.getUTCMonth() + 1 !== month || localCheck.getUTCDate() !== day || localCheck.getUTCHours() !== hour24 || localCheck.getUTCMinutes() !== minute || localCheck.getUTCSeconds() !== second) { throw new TypeError('Parse(): invalid datetime components'); } const utcMillis = hasOffset ? localMillis - offsetMinutes * 60_000 : localMillis; return new Date(utcMillis); }