diff --git a/Libraries/LibJS/Runtime/Temporal/AbstractOperations.cpp b/Libraries/LibJS/Runtime/Temporal/AbstractOperations.cpp index df86ba6ee83..878bcb1f046 100644 --- a/Libraries/LibJS/Runtime/Temporal/AbstractOperations.cpp +++ b/Libraries/LibJS/Runtime/Temporal/AbstractOperations.cpp @@ -630,6 +630,19 @@ ThrowCompletionOr get_temporal_relative_to_option(VM& vm, Object con // iv. Set matchBehaviour to MATCH-MINUTES. match_behavior = MatchBehavior::MatchMinutes; + + // v. If offsetString is not EMPTY, then + if (offset_string.has_value()) { + // 1. Let offsetParseResult be ParseText(StringToCodePoints(offsetString), UTCOffset[+SubMinutePrecision]). + auto offset_parse_result = parse_utc_offset(*offset_string, SubMinutePrecision::Yes); + + // 2. Assert: offsetParseResult is a Parse Node. + VERIFY(offset_parse_result.has_value()); + + // 3. If offsetParseResult contains more than one MinuteSecond Parse Node, set matchBehaviour to MATCH-EXACTLY. + if (offset_parse_result->seconds.has_value()) + match_behavior = MatchBehavior::MatchExactly; + } } // g. Let calendar be result.[[Calendar]]. diff --git a/Libraries/LibJS/Runtime/Temporal/ZonedDateTime.cpp b/Libraries/LibJS/Runtime/Temporal/ZonedDateTime.cpp index 42bd7c57868..4e4980db62b 100644 --- a/Libraries/LibJS/Runtime/Temporal/ZonedDateTime.cpp +++ b/Libraries/LibJS/Runtime/Temporal/ZonedDateTime.cpp @@ -262,22 +262,35 @@ ThrowCompletionOr> to_temporal_zoned_date_time(VM& vm, Va // l. Set matchBehaviour to MATCH-MINUTES. match_behavior = MatchBehavior::MatchMinutes; - // m. Let resolvedOptions be ? GetOptionsObject(options). + // m. If offsetString is not EMPTY, then + if (offset_string.has_value()) { + // i. Let offsetParseResult be ParseText(StringToCodePoints(offsetString), UTCOffset[+SubMinutePrecision]). + auto offset_parse_result = parse_utc_offset(*offset_string, SubMinutePrecision::Yes); + + // ii. Assert: offsetParseResult is a Parse Node. + VERIFY(offset_parse_result.has_value()); + + // iii. If offsetParseResult contains more than one MinuteSecond Parse Node, set matchBehaviour to MATCH-EXACTLY. + if (offset_parse_result->seconds.has_value()) + match_behavior = MatchBehavior::MatchExactly; + } + + // n. Let resolvedOptions be ? GetOptionsObject(options). auto resolved_options = TRY(get_options_object(vm, options)); - // n. Let disambiguation be ? GetTemporalDisambiguationOption(resolvedOptions). + // o. Let disambiguation be ? GetTemporalDisambiguationOption(resolvedOptions). disambiguation = TRY(get_temporal_disambiguation_option(vm, resolved_options)); - // o. Let offsetOption be ? GetTemporalOffsetOption(resolvedOptions, REJECT). + // p. Let offsetOption be ? GetTemporalOffsetOption(resolvedOptions, REJECT). offset_option = TRY(get_temporal_offset_option(vm, resolved_options, OffsetOption::Reject)); - // p. Perform ? GetTemporalOverflowOption(resolvedOptions). + // q. Perform ? GetTemporalOverflowOption(resolvedOptions). TRY(get_temporal_overflow_option(vm, resolved_options)); - // q. Let isoDate be CreateISODateRecord(result.[[Year]], result.[[Month]], result.[[Day]]). + // r. Let isoDate be CreateISODateRecord(result.[[Year]], result.[[Month]], result.[[Day]]). iso_date = create_iso_date_record(*result.year, result.month, result.day); - // r. Let time be result.[[Time]]. + // s. Let time be result.[[Time]]. time = result.time; } diff --git a/Libraries/LibJS/Tests/builtins/Temporal/Duration/Duration.compare.js b/Libraries/LibJS/Tests/builtins/Temporal/Duration/Duration.compare.js index ab0568d5f9a..062ad042f7b 100644 --- a/Libraries/LibJS/Tests/builtins/Temporal/Duration/Duration.compare.js +++ b/Libraries/LibJS/Tests/builtins/Temporal/Duration/Duration.compare.js @@ -67,6 +67,21 @@ describe("correct behavior", () => { }); expect(result).toBe(-1); }); + + test("sub-minute time zone offset", () => { + const duration1 = new Temporal.Duration(0, 0, 0, 31); + const duration2 = new Temporal.Duration(0, 1); + + let result = Temporal.Duration.compare(duration1, duration2, { + relativeTo: "1970-01-01T00:00:00-00:45[Africa/Monrovia]", + }); + expect(result).toBe(0); + + result = Temporal.Duration.compare(duration1, duration2, { + relativeTo: "1970-01-01T00:00:00-00:44:30[Africa/Monrovia]", + }); + expect(result).toBe(0); + }); }); describe("errors", () => { @@ -132,4 +147,27 @@ describe("errors", () => { "A starting point is required for comparing calendar units" ); }); + + test("sub-minute time zone offset mismatch", () => { + const duration1 = new Temporal.Duration(0, 0, 0, 31); + const duration2 = new Temporal.Duration(0, 1); + + expect(() => { + Temporal.Duration.compare(duration1, duration2, { + relativeTo: "1970-01-01T00:00:00-00:44:40[Africa/Monrovia]", + }); + }).toThrowWithMessage( + RangeError, + "Invalid offset for the provided date and time in the current time zone" + ); + + expect(() => { + Temporal.Duration.compare(duration1, duration2, { + relativeTo: "1970-01-01T00:00:00-00:45:00[Africa/Monrovia]", + }); + }).toThrowWithMessage( + RangeError, + "Invalid offset for the provided date and time in the current time zone" + ); + }); });