import {HOUR, MINUTE} from './units.js'; import {TimeComponents, verifyTimeComponents} from './utilities.js'; export const date = /(?\d{4})(?(?:\-)?)(?\d{2})\k(?\d{2})/; export const time = /(?\d{2})(?:(?:?)(?\d{2})(?:\k(?\d{2})(?:[.,](?\d{1,9}))?)?)?/; export const timeZone = /\[(?[a-zA-Z\.\-\,\_\/]+)\]/; export const timeZoneDesignators = /(?Z)/; export const offset = /(?\+|\-)(?[0-9]{2})(?:(?::)?(?[0-9]{2}))?/; export const dateTime = new RegExp( `^(?:${date.source})?(?:T${time.source}(?:${offset.source}|${timeZoneDesignators.source})?)?(?:${timeZone.source})?$`, ); export let Zulu = Symbol('Zulu'); interface DateTimeGroups { year: string; month: string; day: string; hour?: string; minute?: string; second?: string; millisecond?: string; // offset offsetSign?: string; offsetHour?: string; offsetMinute?: string; timeZoneDesignator?: 'Z'; timeZoneName?: string; } export class ISO8601Result { constructor( public date?: {year: number; month: number; day: number}, public time?: TimeComponents, public offset?: number | symbol, public timeZone?: string, ) {} static parseOffsetGroups(sign: string, hour: string, minute?: string) { let multiplier = sign === '-' ? -1 : 1; let minuteValue = minute ? parseInt(minute, 10) * MINUTE : 0; return (parseInt(hour, 10) * HOUR + minuteValue) * multiplier; } static parseTimeGroups(hour: string, minute?: string, second?: string, millisecond?: string) { let hourValue = parseInt(hour, 10); let minuteValue = minute ? parseInt(minute, 10) : 0; let secondValue = second ? parseInt(second, 10) : 0; if (secondValue === 60) { secondValue = 59; } let millisecondValue = 0; if (millisecond) { millisecond = millisecond.slice(0, 3); let times = Math.pow(10, 3 - millisecond.length); millisecondValue = parseInt(millisecond, 10) * times; } return [hourValue, minuteValue, secondValue, millisecondValue]; } static parseDateTime(dateString: string) { let result = dateTime.exec(dateString); if (result) { let groups = result.groups as unknown as DateTimeGroups; let date: {year: number; month: number; day: number} | undefined; if (groups.year && groups.month && groups.day) { date = { year: parseInt(groups.year, 10), month: parseInt(groups.month, 10), day: parseInt(groups.day, 10), }; } let time: {hour: number; minute: number; second: number; millisecond: number} | undefined; if (groups.hour) { let [hourValue, minuteValue, secondValue, millisecondValue] = ISO8601Result.parseTimeGroups( groups.hour, groups.minute, groups.second, groups.millisecond, ); time = verifyTimeComponents(hourValue, minuteValue, secondValue, millisecondValue, 'reject'); } if (!time && !date) { return null; } let offset: number | symbol | undefined; if (groups.offsetSign && groups.offsetHour) { offset = ISO8601Result.parseOffsetGroups(groups.offsetSign, groups.offsetHour, groups.offsetMinute); } else if (groups.timeZoneDesignator) { offset = Zulu; } return new ISO8601Result(date, time, offset, groups.timeZoneName); } return null; } }