Skip to content

Commit 90ee179

Browse files
committed
Remove zoneRequired & align parsing to spec
The `zoneRequired` option in ParseTemporalTimeZoneString doesn't work, because the `zonesplit` regex in regex.mjs matches any string. (It does correctly match each component, it just doesn't fail if none match.) Furthermore, callers of this option don't really follow the spec that requires throwing as soon as we know that it's not the right regex for the type. This commit fixes #1973 by: * Removing the `zoneRequired` option * Removing the now-unused `datetime` regex, and renaming the `instant` regex to `zoneddatetime` to better reflect what it's doing. * Refactoring a few of the parsing methods to better match the spec, which expects that strings that don't satisfy the production will throw before doing non-parsing work. After this commit, every `Parse*` method will throw if the string doesn't match the production for that type.
1 parent ca4a9e0 commit 90ee179

File tree

2 files changed

+23
-23
lines changed

2 files changed

+23
-23
lines changed

polyfill/lib/ecmascript.mjs

Lines changed: 22 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -246,9 +246,9 @@ export const ES = ObjectAssign({}, ES2020, {
246246
if (showCalendar === 'auto' && id === 'iso8601') return '';
247247
return `[u-ca=${id}]`;
248248
},
249-
ParseISODateTime: (isoString, { zoneRequired }) => {
250-
const regex = zoneRequired ? PARSE.instant : PARSE.datetime;
251-
const match = regex.exec(isoString);
249+
ParseISODateTime: (isoString) => {
250+
// ZDT is the superset of fields for every other Temporal type
251+
const match = PARSE.zoneddatetime.exec(isoString);
252252
if (!match) throw new RangeError(`invalid ISO 8601 string: ${isoString}`);
253253
let yearString = match[1];
254254
if (yearString[0] === '\u2212') yearString = `-${yearString.slice(1)}`;
@@ -310,16 +310,20 @@ export const ES = ObjectAssign({}, ES2020, {
310310
};
311311
},
312312
ParseTemporalInstantString: (isoString) => {
313-
return ES.ParseISODateTime(isoString, { zoneRequired: true });
313+
const result = ES.ParseISODateTime(isoString);
314+
if (!result.z && !result.offset) throw new RangeError('Temporal.Instant requires a time zone offset');
315+
return result;
314316
},
315317
ParseTemporalZonedDateTimeString: (isoString) => {
316-
return ES.ParseISODateTime(isoString, { zoneRequired: true });
318+
const result = ES.ParseISODateTime(isoString);
319+
if (!result.ianaName) throw new RangeError('Temporal.ZonedDateTime requires a time zone ID in brackets');
320+
return result;
317321
},
318322
ParseTemporalDateTimeString: (isoString) => {
319-
return ES.ParseISODateTime(isoString, { zoneRequired: false });
323+
return ES.ParseISODateTime(isoString);
320324
},
321325
ParseTemporalDateString: (isoString) => {
322-
return ES.ParseISODateTime(isoString, { zoneRequired: false });
326+
return ES.ParseISODateTime(isoString);
323327
},
324328
ParseTemporalTimeString: (isoString) => {
325329
const match = PARSE.time.exec(isoString);
@@ -336,9 +340,7 @@ export const ES = ObjectAssign({}, ES2020, {
336340
calendar = match[15];
337341
} else {
338342
let z;
339-
({ hour, minute, second, millisecond, microsecond, nanosecond, calendar, z } = ES.ParseISODateTime(isoString, {
340-
zoneRequired: false
341-
}));
343+
({ hour, minute, second, millisecond, microsecond, nanosecond, calendar, z } = ES.ParseISODateTime(isoString));
342344
if (z) throw new RangeError('Z designator not supported for PlainTime');
343345
}
344346
return { hour, minute, second, millisecond, microsecond, nanosecond, calendar };
@@ -354,7 +356,7 @@ export const ES = ObjectAssign({}, ES2020, {
354356
calendar = match[3];
355357
} else {
356358
let z;
357-
({ year, month, calendar, day: referenceISODay, z } = ES.ParseISODateTime(isoString, { zoneRequired: false }));
359+
({ year, month, calendar, day: referenceISODay, z } = ES.ParseISODateTime(isoString));
358360
if (z) throw new RangeError('Z designator not supported for PlainYearMonth');
359361
}
360362
return { year, month, calendar, referenceISODay };
@@ -367,7 +369,7 @@ export const ES = ObjectAssign({}, ES2020, {
367369
day = ES.ToInteger(match[2]);
368370
} else {
369371
let z;
370-
({ month, day, calendar, year: referenceISOYear, z } = ES.ParseISODateTime(isoString, { zoneRequired: false }));
372+
({ month, day, calendar, year: referenceISOYear, z } = ES.ParseISODateTime(isoString));
371373
if (z) throw new RangeError('Z designator not supported for PlainMonthDay');
372374
}
373375
return { month, day, calendar, referenceISOYear };
@@ -385,10 +387,14 @@ export const ES = ObjectAssign({}, ES2020, {
385387
}
386388
try {
387389
// Try parsing ISO string instead
388-
return ES.ParseISODateTime(stringIdent, { zoneRequired: true });
390+
const result = ES.ParseISODateTime(stringIdent);
391+
if (result.z || result.offset || result.ianaName) {
392+
return result;
393+
}
389394
} catch {
390-
throw new RangeError(`Invalid time zone: ${stringIdent}`);
395+
// fall through
391396
}
397+
throw new RangeError(`Invalid time zone: ${stringIdent}`);
392398
},
393399
ParseTemporalDurationString: (isoString) => {
394400
const match = PARSE.duration.exec(isoString);
@@ -441,7 +447,6 @@ export const ES = ObjectAssign({}, ES2020, {
441447
nanosecond
442448
);
443449
if (epochNs === null) throw new RangeError('DateTime outside of supported range');
444-
if (!z && !offset) throw new RangeError('Temporal.Instant requires a time zone offset');
445450
const offsetNs = z ? 0 : ES.ParseOffsetString(offset);
446451
return epochNs.subtract(offsetNs);
447452
},
@@ -773,7 +778,7 @@ export const ES = ObjectAssign({}, ES2020, {
773778
} else {
774779
let ianaName, z;
775780
({ year, month, day, hour, minute, second, millisecond, microsecond, nanosecond, calendar, ianaName, offset, z } =
776-
ES.ParseISODateTime(ES.ToString(relativeTo), { zoneRequired: false }));
781+
ES.ParseISODateTime(ES.ToString(relativeTo)));
777782
if (ianaName) timeZone = ianaName;
778783
if (z) {
779784
offsetBehaviour = 'exact';
@@ -1356,7 +1361,6 @@ export const ES = ObjectAssign({}, ES2020, {
13561361
let ianaName, z;
13571362
({ year, month, day, hour, minute, second, millisecond, microsecond, nanosecond, ianaName, offset, z, calendar } =
13581363
ES.ParseTemporalZonedDateTimeString(ES.ToString(item)));
1359-
if (!ianaName) throw new RangeError('time zone ID required in brackets');
13601364
if (z) {
13611365
offsetBehaviour = 'exact';
13621366
} else if (!offset) {
@@ -1659,7 +1663,7 @@ export const ES = ObjectAssign({}, ES2020, {
16591663
if (IsBuiltinCalendar(identifier)) return new TemporalCalendar(identifier);
16601664
let calendar;
16611665
try {
1662-
({ calendar } = ES.ParseISODateTime(identifier, { zoneRequired: false }));
1666+
({ calendar } = ES.ParseISODateTime(identifier));
16631667
} catch {
16641668
throw new RangeError(`Invalid calendar: ${identifier}`);
16651669
}

polyfill/lib/regex.mjs

Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -18,14 +18,10 @@ export const offset = /([+\u2212-])([01][0-9]|2[0-3])(?::?([0-5][0-9])(?::?([0-5
1818
const zonesplit = new RegExp(`(?:([zZ])|(?:${offset.source})?)(?:\\[(${timeZoneID.source})\\])?`);
1919
const calendar = new RegExp(`\\[u-ca=(${calendarID.source})\\]`);
2020

21-
export const instant = new RegExp(
21+
export const zoneddatetime = new RegExp(
2222
`^${datesplit.source}(?:(?:T|\\s+)${timesplit.source})?${zonesplit.source}(?:${calendar.source})?$`,
2323
'i'
2424
);
25-
export const datetime = new RegExp(
26-
`^${datesplit.source}(?:(?:T|\\s+)${timesplit.source})?(?:${zonesplit.source})?(?:${calendar.source})?$`,
27-
'i'
28-
);
2925

3026
export const time = new RegExp(`^${timesplit.source}(?:${zonesplit.source})?(?:${calendar.source})?$`, 'i');
3127

0 commit comments

Comments
 (0)