From 212fffcd2df3d5ac6dbcc6abfb628d696c5de146 Mon Sep 17 00:00:00 2001
From: Justin Grant <justingrant@users.noreply.github.com>
Date: Fri, 21 Jul 2023 03:19:11 -0700
Subject: [PATCH] Polyfill: clean up ISO parser & align to spec

To make it easier to find actual Test262 gaps, this PR removes
unreachable and unused code in ISO parsing.

It also removes offset normalization code from ParseISODateTime,
to better mach the spec that only normalizes downstream from ISO
string parsing. Doing this exposed a test bug that's fixed in the second
commit of https://github.com/tc39/test262/pull/3877.
---
 polyfill/lib/ecmascript.mjs    | 25 +++++++------------------
 polyfill/lib/regex.mjs         |  4 +++-
 polyfill/test/validStrings.mjs | 16 ++++++++--------
 polyfill/test262               |  2 +-
 4 files changed, 19 insertions(+), 28 deletions(-)

diff --git a/polyfill/lib/ecmascript.mjs b/polyfill/lib/ecmascript.mjs
index e9f24c4463..b05c3b701c 100644
--- a/polyfill/lib/ecmascript.mjs
+++ b/polyfill/lib/ecmascript.mjs
@@ -437,23 +437,11 @@ export function ParseISODateTime(isoString) {
   if (match[13]) {
     offset = undefined;
     z = true;
-  } else if (match[14] && match[15]) {
-    const offsetSign = match[14] === '-' || match[14] === '\u2212' ? '-' : '+';
-    const offsetHours = match[15] || '00';
-    const offsetMinutes = match[16] || '00';
-    const offsetSeconds = match[17] || '00';
-    let offsetFraction = match[18] || '0';
-    offset = `${offsetSign}${offsetHours}:${offsetMinutes}`;
-    if (+offsetFraction) {
-      while (offsetFraction.endsWith('0')) offsetFraction = offsetFraction.slice(0, -1);
-      offset += `:${offsetSeconds}.${offsetFraction}`;
-    } else if (+offsetSeconds) {
-      offset += `:${offsetSeconds}`;
-    }
-    if (offset === '-00:00') offset = '+00:00';
+  } else if (match[14]) {
+    offset = match[14];
   }
-  const tzName = match[19];
-  const calendar = processAnnotations(match[20]);
+  const tzName = match[15];
+  const calendar = processAnnotations(match[16]);
   RejectDateTime(year, month, day, hour, minute, second, millisecond, microsecond, nanosecond);
   return {
     year,
@@ -505,7 +493,7 @@ export function ParseTemporalTimeString(isoString) {
     millisecond = ToIntegerOrInfinity(fraction.slice(0, 3));
     microsecond = ToIntegerOrInfinity(fraction.slice(3, 6));
     nanosecond = ToIntegerOrInfinity(fraction.slice(6, 9));
-    processAnnotations(match[14]); // ignore found calendar
+    processAnnotations(match[10]); // ignore found calendar
     if (match[8]) throw new RangeError('Z designator not supported for PlainTime');
   } else {
     let z, hasTime;
@@ -2624,7 +2612,7 @@ export function IsOffsetTimeZoneIdentifier(string) {
 }
 
 export function ParseDateTimeUTCOffset(string) {
-  const match = OFFSET.exec(string);
+  const match = OFFSET_WITH_PARTS.exec(string);
   if (!match) {
     throw new RangeError(`invalid time zone offset: ${string}`);
   }
@@ -5632,6 +5620,7 @@ export function ASCIILowercase(str) {
 }
 
 const OFFSET = new RegExp(`^${PARSE.offset.source}$`);
+const OFFSET_WITH_PARTS = new RegExp(`^${PARSE.offsetWithParts.source}$`);
 
 function bisect(getState, left, right, lstate = getState(left), rstate = getState(right)) {
   left = bigInt(left);
diff --git a/polyfill/lib/regex.mjs b/polyfill/lib/regex.mjs
index 8a71c63841..62658f7f6e 100644
--- a/polyfill/lib/regex.mjs
+++ b/polyfill/lib/regex.mjs
@@ -22,7 +22,9 @@ export const datesplit = new RegExp(
   `(${yearpart.source})(?:-(${monthpart.source})-(${daypart.source})|(${monthpart.source})(${daypart.source}))`
 );
 const timesplit = /(\d{2})(?::(\d{2})(?::(\d{2})(?:[.,](\d{1,9}))?)?|(\d{2})(?:(\d{2})(?:[.,](\d{1,9}))?)?)?/;
-export const offset = /([+\u2212-])([01][0-9]|2[0-3])(?::?([0-5][0-9])(?::?([0-5][0-9])(?:[.,](\d{1,9}))?)?)?/;
+export const offsetWithParts = /([+\u2212-])([01][0-9]|2[0-3])(?::?([0-5][0-9])(?::?([0-5][0-9])(?:[.,](\d{1,9}))?)?)?/;
+export const offset =
+  /((?:[+\u2212-])(?:[01][0-9]|2[0-3])(?::?(?:[0-5][0-9])(?::?(?:[0-5][0-9])(?:[.,](?:\d{1,9}))?)?)?)/;
 const offsetpart = new RegExp(`([zZ])|${offset.source}?`);
 export const offsetIdentifier = /([+\u2212-])([01][0-9]|2[0-3])(?::?([0-5][0-9])?)?/;
 export const annotation = /\[(!)?([a-z_][a-z0-9_-]*)=([A-Za-z0-9]+(?:-[A-Za-z0-9]+)*)\]/g;
diff --git a/polyfill/test/validStrings.mjs b/polyfill/test/validStrings.mjs
index 3ba6aa9b1f..cec937b68c 100644
--- a/polyfill/test/validStrings.mjs
+++ b/polyfill/test/validStrings.mjs
@@ -6,7 +6,6 @@
 
 import assert from 'assert';
 import * as ES from '../lib/ecmascript.mjs';
-import { Instant } from '../lib/instant.mjs';
 
 const timezoneNames = Intl.supportedValuesOf('timeZone');
 const calendarNames = Intl.supportedValuesOf('calendar');
@@ -249,12 +248,7 @@ const temporalSign = withCode(
 );
 const temporalDecimalFraction = fraction;
 function saveOffset(data, result) {
-  // To canonicalize an offset string that may include nanoseconds, we use GetOffsetStringFor
-  const instant = new Instant(0n);
-  const fakeTimeZone = {
-    getOffsetNanosecondsFor: () => ES.ParseDateTimeUTCOffset(result).offsetNanoseconds
-  };
-  data.offset = ES.GetOffsetStringFor(fakeTimeZone, instant);
+  data.offset = ES.ParseDateTimeUTCOffset(result);
 }
 const utcOffsetSubMinutePrecision = withCode(
   seq(
@@ -468,7 +462,13 @@ function fuzzMode(mode) {
       for (let prop of comparisonItems[mode]) {
         let expected = generatedData[prop];
         if (prop !== 'tzName' && prop !== 'offset' && prop !== 'calendar') expected = expected || 0;
-        assert.equal(parsed[prop], expected, prop);
+        if (prop === 'offset' && expected) {
+          const parsedResult = ES.ParseDateTimeUTCOffset(parsed[prop]);
+          assert.equal(parsedResult.offsetNanoseconds, expected.offsetNanoseconds);
+          assert.equal(parsedResult.hasSubMinutePrecision, expected.hasSubMinutePrecision);
+        } else {
+          assert.equal(parsed[prop], expected, prop);
+        }
       }
       console.log(`${fuzzed} => ok`);
     } catch (e) {
diff --git a/polyfill/test262 b/polyfill/test262
index 29dde1ce0e..66f3959c14 160000
--- a/polyfill/test262
+++ b/polyfill/test262
@@ -1 +1 @@
-Subproject commit 29dde1ce0e97a8bd6423c4397b9d3b51df0a1d8e
+Subproject commit 66f3959c14646a4caba79e91adfe976c28246bf9