@formatjs/ecma402-abstract
Version:
A collection of implementation for ECMAScript abstract operations
72 lines (71 loc) • 2.77 kB
JavaScript
// Cached regex patterns for performance
const OFFSET_TIMEZONE_PREFIX_REGEX = /^[+-]/;
const OFFSET_TIMEZONE_FORMAT_REGEX = /^([+-])(\d{2})(?::?(\d{2}))?(?::?(\d{2}))?(?:\.(\d{1,9}))?$/;
/**
* IsTimeZoneOffsetString ( offsetString )
* https://tc39.es/ecma262/#sec-istimezoneoffsetstring
*
* Validates whether a string represents a valid UTC offset timezone.
* Supports formats: ±HH, ±HHMM, ±HH:MM, ±HH:MM:SS, ±HH:MM:SS.sss
*
* @param offsetString - The string to validate as a timezone offset
* @returns true if offsetString is a valid UTC offset format
*/
function IsTimeZoneOffsetString(offsetString) {
// 1. If offsetString does not start with '+' or '-', return false
if (!OFFSET_TIMEZONE_PREFIX_REGEX.test(offsetString)) {
return false;
}
// 2. Let parseResult be ParseText(offsetString, UTCOffset)
const match = OFFSET_TIMEZONE_FORMAT_REGEX.exec(offsetString);
// 3. If parseResult is a List of errors, return false
if (!match) {
return false;
}
// 4. Validate component ranges per ECMA-262 grammar
// Hour must be 0-23, Minute must be 0-59, Second must be 0-59
const hours = parseInt(match[2], 10);
const minutes = match[3] ? parseInt(match[3], 10) : 0;
const seconds = match[4] ? parseInt(match[4], 10) : 0;
if (hours > 23 || minutes > 59 || seconds > 59) {
return false;
}
// 5. Return true
return true;
}
/**
* IsValidTimeZoneName ( timeZone )
* https://tc39.es/ecma402/#sec-isvalidtimezonename
*
* Extended to support UTC offset time zones per ECMA-402 PR #788 (ES2026).
* The abstract operation validates both:
* 1. UTC offset identifiers (e.g., "+01:00", "-05:30")
* 2. Available named time zone identifiers from IANA Time Zone Database
*
* @param tz - The timezone identifier to validate
* @param implDetails - Implementation details containing timezone data
* @returns true if timeZone is a valid identifier
*/
export function IsValidTimeZoneName(tz, { zoneNamesFromData, uppercaseLinks }) {
// 1. If IsTimeZoneOffsetString(timeZone) is true, return true
// Per ECMA-402 PR #788, UTC offset identifiers are valid
if (IsTimeZoneOffsetString(tz)) {
return true;
}
// 2. Let timeZones be AvailableNamedTimeZoneIdentifiers()
// 3. If timeZones contains an element equal to timeZone, return true
// NOTE: Implementation uses case-insensitive comparison per spec note
const uppercasedTz = tz.toUpperCase();
const zoneNames = new Set();
const linkNames = new Set();
zoneNamesFromData.map((z) => z.toUpperCase()).forEach((z) => zoneNames.add(z));
Object.keys(uppercaseLinks).forEach((linkName) => {
linkNames.add(linkName.toUpperCase());
zoneNames.add(uppercaseLinks[linkName].toUpperCase());
});
if (zoneNames.has(uppercasedTz) || linkNames.has(uppercasedTz)) {
return true;
}
// 4. Return false
return false;
}