diff --git a/js/src/builtin/temporal/TemporalParser.cpp b/js/src/builtin/temporal/TemporalParser.cpp index 8efe21dac62c..677ee8b295b2 100644 --- a/js/src/builtin/temporal/TemporalParser.cpp +++ b/js/src/builtin/temporal/TemporalParser.cpp @@ -884,6 +884,8 @@ class TemporalParser final { mozilla::Result timeZoneUTCOffsetName(); + mozilla::Result timeZoneIdentifier(); + mozilla::Result timeZoneAnnotation(); bool timeZoneIANANameComponent(); @@ -914,6 +916,8 @@ class TemporalParser final { mozilla::Result parseTemporalTimeZoneString(); + mozilla::Result parseTimeZoneIdentifier(); + mozilla::Result parseTimeZoneOffsetString(); mozilla::Result parseDateTimeUTCOffset(); @@ -1297,21 +1301,11 @@ TemporalParser::utcOffsetSubMinutePrecision() { template mozilla::Result -TemporalParser::timeZoneAnnotation() { - // TimeZoneAnnotation : - // [ AnnotationCriticalFlag? TimeZoneIdentifier ] - // +TemporalParser::timeZoneIdentifier() { // TimeZoneIdentifier : // TimeZoneIANAName // TimeZoneUTCOffsetName - if (!character('[')) { - return mozilla::Err(JSMSG_TEMPORAL_PARSER_BRACKET_BEFORE_TIMEZONE); - } - - // Skip over the optional critical flag. - annotationCriticalFlag(); - TimeZoneAnnotation result = {}; if (hasSign()) { auto offset = timeZoneUTCOffsetName(); @@ -1327,6 +1321,27 @@ TemporalParser::timeZoneAnnotation() { result.name = name.unwrap(); } + return result; +} + +template +mozilla::Result +TemporalParser::timeZoneAnnotation() { + // TimeZoneAnnotation : + // [ AnnotationCriticalFlag? TimeZoneIdentifier ] + + if (!character('[')) { + return mozilla::Err(JSMSG_TEMPORAL_PARSER_BRACKET_BEFORE_TIMEZONE); + } + + // Skip over the optional critical flag. + annotationCriticalFlag(); + + auto result = timeZoneIdentifier(); + if (result.isErr()) { + return result.propagateErr(); + } + if (!character(']')) { return mozilla::Err(JSMSG_TEMPORAL_PARSER_BRACKET_AFTER_TIMEZONE); } @@ -1681,6 +1696,63 @@ bool js::temporal::ParseTemporalTimeZoneString( return true; } +template +mozilla::Result +TemporalParser::parseTimeZoneIdentifier() { + auto result = timeZoneIdentifier(); + if (result.isErr()) { + return result.propagateErr(); + } + if (!reader_.atEnd()) { + return mozilla::Err(JSMSG_TEMPORAL_PARSER_GARBAGE_AFTER_INPUT); + } + return result; +} + +/** + * ParseTimeZoneIdentifier ( identifier ) + */ +template +static auto ParseTimeZoneIdentifier(mozilla::Span str) { + TemporalParser parser(str); + return parser.parseTimeZoneIdentifier(); +} + +/** + * ParseTimeZoneIdentifier ( identifier ) + */ +static auto ParseTimeZoneIdentifier(Handle str) { + JS::AutoCheckCannotGC nogc; + if (str->hasLatin1Chars()) { + return ParseTimeZoneIdentifier(str->latin1Range(nogc)); + } + return ParseTimeZoneIdentifier(str->twoByteRange(nogc)); +} + +/** + * ParseTimeZoneIdentifier ( identifier ) + */ +bool js::temporal::ParseTimeZoneIdentifier( + JSContext* cx, Handle str, + MutableHandle result) { + Rooted linear(cx, str->ensureLinear(cx)); + if (!linear) { + return false; + } + + // Steps 1-2. + auto parseResult = ::ParseTimeZoneIdentifier(linear); + if (parseResult.isErr()) { + JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, + parseResult.unwrapErr()); + return false; + } + auto timeZone = parseResult.unwrap(); + + // Steps 3-4. + return ParseTimeZoneAnnotation(cx, timeZone, linear, result); +} + template mozilla::Result TemporalParser::parseTimeZoneOffsetString() { diff --git a/js/src/builtin/temporal/TemporalParser.h b/js/src/builtin/temporal/TemporalParser.h index f5779aa87d8e..2397708d354a 100644 --- a/js/src/builtin/temporal/TemporalParser.h +++ b/js/src/builtin/temporal/TemporalParser.h @@ -58,6 +58,12 @@ bool ParseTemporalInstantString(JSContext* cx, JS::Handle str, bool ParseTemporalTimeZoneString(JSContext* cx, JS::Handle str, JS::MutableHandle result); +/** + * ParseTimeZoneIdentifier ( identifier ) + */ +bool ParseTimeZoneIdentifier(JSContext* cx, JS::Handle str, + JS::MutableHandle result); + /** * ParseTimeZoneOffsetString ( isoString ) */ diff --git a/js/src/builtin/temporal/TimeZone.cpp b/js/src/builtin/temporal/TimeZone.cpp index feaf07a896ad..f6b942e77927 100644 --- a/js/src/builtin/temporal/TimeZone.cpp +++ b/js/src/builtin/temporal/TimeZone.cpp @@ -1079,6 +1079,108 @@ JSString* js::temporal::GetOffsetStringFor( return NewStringCopyN(cx, result, n); } +/** + * TimeZoneEquals ( one, two ) + */ +bool js::temporal::TimeZoneEquals(JSContext* cx, Handle one, + Handle two, bool* equals) { + // Steps 1-3. (Not applicable) + + // Step 4. + if (!EqualStrings(cx, one, two, equals)) { + return false; + } + if (*equals) { + return true; + } + + // Step 5. + Rooted timeZoneOne(cx); + if (!ParseTimeZoneIdentifier(cx, one, &timeZoneOne)) { + return false; + } + + // Step 6. + Rooted timeZoneTwo(cx); + if (!ParseTimeZoneIdentifier(cx, two, &timeZoneTwo)) { + return false; + } + + // Step 7. + if (timeZoneOne.name() && timeZoneTwo.name()) { + // Step 7.a. + Rooted validTimeZoneOne(cx); + if (!IsValidTimeZoneName(cx, timeZoneOne.name(), &validTimeZoneOne)) { + return false; + } + if (!validTimeZoneOne) { + *equals = false; + return true; + } + + // Step 7.b. + Rooted validTimeZoneTwo(cx); + if (!IsValidTimeZoneName(cx, timeZoneTwo.name(), &validTimeZoneTwo)) { + return false; + } + if (!validTimeZoneTwo) { + *equals = false; + return true; + } + + // Step 7.c and 9. + Rooted canonicalOne( + cx, CanonicalizeTimeZoneName(cx, validTimeZoneOne)); + if (!canonicalOne) { + return false; + } + + JSString* canonicalTwo = CanonicalizeTimeZoneName(cx, validTimeZoneTwo); + if (!canonicalTwo) { + return false; + } + + return EqualStrings(cx, canonicalOne, canonicalTwo, equals); + } + + // Step 8.a. + if (!timeZoneOne.name() && !timeZoneTwo.name()) { + *equals = (timeZoneOne.offset() == timeZoneTwo.offset()); + return true; + } + + // Step 9. + *equals = false; + return true; +} + +/** + * TimeZoneEquals ( one, two ) + */ +bool js::temporal::TimeZoneEquals(JSContext* cx, Handle one, + Handle two, bool* equals) { + // Step 1. + if (one.isObject() && two.isObject() && one.toObject() == two.toObject()) { + *equals = true; + return true; + } + + // Step 2. + Rooted timeZoneOne(cx, ToTemporalTimeZoneIdentifier(cx, one)); + if (!timeZoneOne) { + return false; + } + + // Step 3. + Rooted timeZoneTwo(cx, ToTemporalTimeZoneIdentifier(cx, two)); + if (!timeZoneTwo) { + return false; + } + + // Steps 4-9. + return TimeZoneEquals(cx, timeZoneOne, timeZoneTwo, equals); +} + // ES2019 draft rev 0ceb728a1adbffe42b26972a6541fd7f398b1557 // 5.2.5 Mathematical Operations static inline double PositiveModulo(double dividend, double divisor) { @@ -1803,6 +1905,37 @@ static bool TimeZone_from(JSContext* cx, unsigned argc, Value* vp) { return true; } +/** + * Temporal.TimeZone.prototype.equals ( other ) + */ +static bool TimeZone_equals(JSContext* cx, const CallArgs& args) { + Rooted timeZone(cx, &args.thisv().toObject()); + + // FIXME: spec bug - argument needs to be converted to time zone. + Rooted other(cx); + if (!ToTemporalTimeZone(cx, args.get(0), &other)) { + return false; + } + + // Step 3. + bool equals; + if (!TimeZoneEquals(cx, timeZone, other, &equals)) { + return false; + } + + args.rval().setBoolean(equals); + return true; +} + +/** + * Temporal.TimeZone.prototype.equals ( other ) + */ +static bool TimeZone_equals(JSContext* cx, unsigned argc, Value* vp) { + // Steps 1-2. + CallArgs args = CallArgsFromVp(argc, vp); + return CallNonGenericMethod(cx, args); +} + /** * Temporal.TimeZone.prototype.getOffsetNanosecondsFor ( instant ) */ @@ -2224,6 +2357,7 @@ static const JSFunctionSpec TimeZone_methods[] = { }; static const JSFunctionSpec TimeZone_prototype_methods[] = { + JS_FN("equals", TimeZone_equals, 1, 0), JS_FN("getOffsetNanosecondsFor", TimeZone_getOffsetNanosecondsFor, 1, 0), JS_FN("getOffsetStringFor", TimeZone_getOffsetStringFor, 1, 0), JS_FN("getPlainDateTimeFor", TimeZone_getPlainDateTimeFor, 1, 0), diff --git a/js/src/builtin/temporal/TimeZone.h b/js/src/builtin/temporal/TimeZone.h index ca28facfc539..5c349510b782 100644 --- a/js/src/builtin/temporal/TimeZone.h +++ b/js/src/builtin/temporal/TimeZone.h @@ -286,6 +286,18 @@ JSObject* ToTemporalTimeZoneObject(JSContext* cx, JSString* ToTemporalTimeZoneIdentifier(JSContext* cx, JS::Handle timeZone); +/** + * TimeZoneEquals ( one, two ) + */ +bool TimeZoneEquals(JSContext* cx, JS::Handle one, + JS::Handle two, bool* equals); + +/** + * TimeZoneEquals ( one, two ) + */ +bool TimeZoneEquals(JSContext* cx, JS::Handle one, + JS::Handle two, bool* equals); + /** * GetPlainDateTimeFor ( timeZone, instant, calendar ) */ diff --git a/js/src/builtin/temporal/ZonedDateTime.cpp b/js/src/builtin/temporal/ZonedDateTime.cpp index 4498418ee18d..479ad8364236 100644 --- a/js/src/builtin/temporal/ZonedDateTime.cpp +++ b/js/src/builtin/temporal/ZonedDateTime.cpp @@ -1050,33 +1050,6 @@ bool js::temporal::DifferenceZonedDateTime(JSContext* cx, const Instant& ns1, largestUnit, nullptr, result); } -/** - * TimeZoneEquals ( one, two ) - */ -static bool TimeZoneEquals(JSContext* cx, Handle one, - Handle two, bool* equals) { - // Step 1. - if (one.isObject() && two.isObject() && one.toObject() == two.toObject()) { - *equals = true; - return true; - } - - // Step 2. - Rooted timeZoneOne(cx, ToTemporalTimeZoneIdentifier(cx, one)); - if (!timeZoneOne) { - return false; - } - - // Step 3. - JSString* timeZoneTwo = ToTemporalTimeZoneIdentifier(cx, two); - if (!timeZoneTwo) { - return false; - } - - // Steps 4-5. - return EqualStrings(cx, timeZoneOne, timeZoneTwo, equals); -} - /** * TimeZoneEquals ( one, two ) */ @@ -1094,14 +1067,14 @@ static bool TimeZoneEqualsOrThrow(JSContext* cx, Handle one, } // Step 3. - JSString* timeZoneTwo = ToTemporalTimeZoneIdentifier(cx, two); + Rooted timeZoneTwo(cx, ToTemporalTimeZoneIdentifier(cx, two)); if (!timeZoneTwo) { return false; } - // Steps 4-5. + // Steps 4-9. bool equals; - if (!EqualStrings(cx, timeZoneOne, timeZoneTwo, &equals)) { + if (!TimeZoneEquals(cx, timeZoneOne, timeZoneTwo, &equals)) { return false; } if (equals) {