/* * Copyright (c) 2021, Idan Horowitz * Copyright (c) 2021-2023, Linus Groh * Copyright (c) 2024, Tim Flynn * * SPDX-License-Identifier: BSD-2-Clause */ #include #include #include #include #include #include #include #include namespace JS::Temporal { GC_DEFINE_ALLOCATOR(PlainDateTime); PlainDateTime::PlainDateTime(ISODateTime const& iso_date_time, String calendar, Object& prototype) : Object(ConstructWithPrototypeTag::Tag, prototype) , m_iso_date_time(iso_date_time) , m_calendar(move(calendar)) { } // 5.5.3 CombineISODateAndTimeRecord ( isoDate, time ), https://tc39.es/proposal-temporal/#sec-temporal-combineisodateandtimerecord ISODateTime combine_iso_date_and_time_record(ISODate iso_date, Time time) { // 1. NOTE: time.[[Days]] is ignored. // 2. Return ISO Date-Time Record { [[ISODate]]: isoDate, [[Time]]: time }. return { .iso_date = iso_date, .time = time }; } // nsMinInstant - nsPerDay static auto const DATETIME_NANOSECONDS_MIN = "-8640000086400000000000"_sbigint; // nsMaxInstant + nsPerDay static auto const DATETIME_NANOSECONDS_MAX = "8640000086400000000000"_sbigint; // 5.5.4 ISODateTimeWithinLimits ( isoDateTime ), https://tc39.es/proposal-temporal/#sec-temporal-isodatetimewithinlimits bool iso_date_time_within_limits(ISODateTime iso_date_time) { // 1. If abs(ISODateToEpochDays(isoDateTime.[[ISODate]].[[Year]], isoDateTime.[[ISODate]].[[Month]] - 1, isoDateTime.[[ISODate]].[[Day]])) > 10**8 + 1, return false. if (fabs(iso_date_to_epoch_days(iso_date_time.iso_date.year, iso_date_time.iso_date.month - 1, iso_date_time.iso_date.day)) > 100000001) return false; // 2. Let ns be ℝ(GetUTCEpochNanoseconds(isoDateTime)). auto nanoseconds = get_utc_epoch_nanoseconds(iso_date_time); // 3. If ns ≤ nsMinInstant - nsPerDay, then if (nanoseconds <= DATETIME_NANOSECONDS_MIN) { // a. Return false. return false; } // 4. If ns ≥ nsMaxInstant + nsPerDay, then if (nanoseconds >= DATETIME_NANOSECONDS_MAX) { // a. Return false. return false; } // 5. Return true. return true; } // 5.5.5 InterpretTemporalDateTimeFields ( calendar, fields, overflow ), https://tc39.es/proposal-temporal/#sec-temporal-interprettemporaldatetimefields ThrowCompletionOr interpret_temporal_date_time_fields(VM& vm, StringView calendar, CalendarFields& fields, Overflow overflow) { // 1. Let isoDate be ? CalendarDateFromFields(calendar, fields, overflow). auto iso_date = TRY(calendar_date_from_fields(vm, calendar, fields, overflow)); // 2. Let time be ? RegulateTime(fields.[[Hour]], fields.[[Minute]], fields.[[Second]], fields.[[Millisecond]], fields.[[Microsecond]], fields.[[Nanosecond]], overflow). auto time = TRY(regulate_time(vm, *fields.hour, *fields.minute, *fields.second, *fields.millisecond, *fields.microsecond, *fields.nanosecond, overflow)); // 3. Return CombineISODateAndTimeRecord(isoDate, time). return combine_iso_date_and_time_record(iso_date, time); } // 5.5.6 ToTemporalDateTime ( item [ , options ] ), https://tc39.es/proposal-temporal/#sec-temporal-totemporaldatetime ThrowCompletionOr> to_temporal_date_time(VM& vm, Value item, Value options) { // 1. If options is not present, set options to undefined. // 2. If item is an Object, then if (item.is_object()) { auto const& object = item.as_object(); // a. If item has an [[InitializedTemporalDateTime]] internal slot, then if (is(object)) { auto const& plain_date_time = static_cast(object); // i. Let resolvedOptions be ? GetOptionsObject(options). auto resolved_options = TRY(get_options_object(vm, options)); // ii. Perform ? GetTemporalOverflowOption(resolvedOptions). TRY(get_temporal_overflow_option(vm, resolved_options)); // iii. Return ! CreateTemporalDateTime(item.[[ISODateTime]], item.[[Calendar]]). return MUST(create_temporal_date_time(vm, plain_date_time.iso_date_time(), plain_date_time.calendar())); } // FIXME: b. If item has an [[InitializedTemporalZonedDateTime]] internal slot, then // FIXME: i. Let isoDateTime be GetISODateTimeFor(item.[[TimeZone]], item.[[EpochNanoseconds]]). // FIXME: ii. Let resolvedOptions be ? GetOptionsObject(options). // FIXME: iii. Perform ? GetTemporalOverflowOption(resolvedOptions). // FIXME: iv. Return ! CreateTemporalDateTime(isoDateTime, item.[[Calendar]]). // c. If item has an [[InitializedTemporalDate]] internal slot, then if (is(object)) { auto const& plain_date = static_cast(object); // i. Let resolvedOptions be ? GetOptionsObject(options). auto resolved_options = TRY(get_options_object(vm, options)); // ii. Perform ? GetTemporalOverflowOption(resolvedOptions). TRY(get_temporal_overflow_option(vm, resolved_options)); // iii. Let isoDateTime be CombineISODateAndTimeRecord(item.[[ISODate]], MidnightTimeRecord()). auto iso_date_time = combine_iso_date_and_time_record(plain_date.iso_date(), midnight_time_record()); // iv. Return ? CreateTemporalDateTime(isoDateTime, item.[[Calendar]]). return TRY(create_temporal_date_time(vm, iso_date_time, plain_date.calendar())); } // d. Let calendar be ? GetTemporalCalendarIdentifierWithISODefault(item). auto calendar = TRY(get_temporal_calendar_identifier_with_iso_default(vm, object)); // e. Let fields be ? PrepareCalendarFields(calendar, item, « YEAR, MONTH, MONTH-CODE, DAY », « HOUR, MINUTE, SECOND, MILLISECOND, MICROSECOND, NANOSECOND », «»). static constexpr auto calendar_field_names = to_array({ CalendarField::Year, CalendarField::Month, CalendarField::MonthCode, CalendarField::Day }); static constexpr auto non_calendar_field_names = to_array({ CalendarField::Hour, CalendarField::Minute, CalendarField::Second, CalendarField::Millisecond, CalendarField::Microsecond, CalendarField::Nanosecond }); auto fields = TRY(prepare_calendar_fields(vm, calendar, object, calendar_field_names, non_calendar_field_names, CalendarFieldList {})); // f. Let resolvedOptions be ? GetOptionsObject(options). auto resolved_options = TRY(get_options_object(vm, options)); // g. Let overflow be ? GetTemporalOverflowOption(resolvedOptions). auto overflow = TRY(get_temporal_overflow_option(vm, resolved_options)); // h. Let result be ? InterpretTemporalDateTimeFields(calendar, fields, overflow). auto result = TRY(interpret_temporal_date_time_fields(vm, calendar, fields, overflow)); // i. Return ? CreateTemporalDateTime(result, calendar). return TRY(create_temporal_date_time(vm, result, move(calendar))); } // 3. If item is not a String, throw a TypeError exception. if (!item.is_string()) return vm.throw_completion(ErrorType::TemporalInvalidPlainDateTime); // 4. Let result be ? ParseISODateTime(item, « TemporalDateTimeString[~Zoned] »). auto result = TRY(parse_iso_date_time(vm, item.as_string().utf8_string_view(), { { Production::TemporalDateTimeString } })); // 5. If result.[[Time]] is START-OF-DAY, let time be MidnightTimeRecord(); else let time be result.[[Time]]. auto time = result.time.has() ? midnight_time_record() : result.time.get