ladybird/Libraries/LibJS/Runtime/Temporal/Instant.cpp
Andreas Kling 164ed80244 Meta: Enable exit-time destructor warnings for libraries
Enable -Wexit-time-destructors for all in-tree library targets and
update process-lifetime library statics so they no longer register
exit-time destructors. Long-lived caches, lookup tables, singleton
registries, and generated constants now use NeverDestroyed or leaked
references where the data is intended to live until process exit.

Update LibWeb, LibLine, and the binding generators so regenerated
sources follow the same rule instead of reintroducing destructed
statics.
2026-06-04 19:20:49 +02:00

291 lines
16 KiB
C++
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/*
* Copyright (c) 2021-2023, Linus Groh <linusg@serenityos.org>
* Copyright (c) 2021, Idan Horowitz <idan.horowitz@serenityos.org>
* Copyright (c) 2023, Shannon Booth <shannon@serenityos.org>
* Copyright (c) 2024-2026, Tim Flynn <trflynn89@ladybird.org>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#include <LibJS/Runtime/AbstractOperations.h>
#include <LibJS/Runtime/BigInt.h>
#include <LibJS/Runtime/Date.h>
#include <LibJS/Runtime/Realm.h>
#include <LibJS/Runtime/Temporal/Calendar.h>
#include <LibJS/Runtime/Temporal/Duration.h>
#include <LibJS/Runtime/Temporal/Instant.h>
#include <LibJS/Runtime/Temporal/InstantConstructor.h>
#include <LibJS/Runtime/Temporal/PlainDateTime.h>
#include <LibJS/Runtime/Temporal/PlainTime.h>
#include <LibJS/Runtime/Temporal/TimeZone.h>
#include <LibJS/Runtime/Temporal/ZonedDateTime.h>
#include <LibJS/Runtime/VM.h>
#include <LibJS/Runtime/ValueInlines.h>
namespace JS::Temporal {
GC_DEFINE_ALLOCATOR(Instant);
// 8 Temporal.Instant Objects, https://tc39.es/proposal-temporal/#sec-temporal-instant-objects
Instant::Instant(BigInt const& epoch_nanoseconds, Object& prototype)
: Object(ConstructWithPrototypeTag::Tag, prototype)
, m_epoch_nanoseconds(epoch_nanoseconds)
{
}
void Instant::visit_edges(Cell::Visitor& visitor)
{
Base::visit_edges(visitor);
visitor.visit(m_epoch_nanoseconds);
}
// nsMaxInstant = 10**8 × nsPerDay = 8.64 × 10**21
Crypto::SignedBigInteger const& NANOSECONDS_MAX_INSTANT = *new Crypto::SignedBigInteger("8640000000000000000000"_sbigint);
// nsMinInstant = -nsMaxInstant = -8.64 × 10**21
Crypto::SignedBigInteger const& NANOSECONDS_MIN_INSTANT = *new Crypto::SignedBigInteger("-8640000000000000000000"_sbigint);
// nsPerDay = 10**6 × (msPerDay) = 8.64 × 10**13
Crypto::UnsignedBigInteger const& NANOSECONDS_PER_DAY = *new Crypto::UnsignedBigInteger(86400000000000_bigint);
// Non-standard:
Crypto::UnsignedBigInteger const& NANOSECONDS_PER_HOUR = *new Crypto::UnsignedBigInteger(3600000000000_bigint);
Crypto::UnsignedBigInteger const& NANOSECONDS_PER_MINUTE = *new Crypto::UnsignedBigInteger(60000000000_bigint);
Crypto::UnsignedBigInteger const& NANOSECONDS_PER_SECOND = *new Crypto::UnsignedBigInteger(1000000000_bigint);
Crypto::UnsignedBigInteger const& NANOSECONDS_PER_MILLISECOND = *new Crypto::UnsignedBigInteger(1000000_bigint);
Crypto::UnsignedBigInteger const& NANOSECONDS_PER_MICROSECOND = *new Crypto::UnsignedBigInteger(1000_bigint);
Crypto::UnsignedBigInteger const& NANOSECONDS_PER_NANOSECOND = *new Crypto::UnsignedBigInteger(1_bigint);
Crypto::UnsignedBigInteger const& MICROSECONDS_PER_MILLISECOND = *new Crypto::UnsignedBigInteger(1000_bigint);
Crypto::UnsignedBigInteger const& MILLISECONDS_PER_SECOND = *new Crypto::UnsignedBigInteger(1000_bigint);
Crypto::UnsignedBigInteger const& SECONDS_PER_MINUTE = *new Crypto::UnsignedBigInteger(60_bigint);
Crypto::UnsignedBigInteger const& MINUTES_PER_HOUR = *new Crypto::UnsignedBigInteger(60_bigint);
Crypto::UnsignedBigInteger const& HOURS_PER_DAY = *new Crypto::UnsignedBigInteger(24_bigint);
// 8.5.1 IsValidEpochNanoseconds ( epochNanoseconds ), https://tc39.es/proposal-temporal/#sec-temporal-isvalidepochnanoseconds
bool is_valid_epoch_nanoseconds(Crypto::SignedBigInteger const& epoch_nanoseconds)
{
// 1. If (epochNanoseconds) < nsMinInstant or (epochNanoseconds) > nsMaxInstant, return false; else return true.
return epoch_nanoseconds >= NANOSECONDS_MIN_INSTANT && epoch_nanoseconds <= NANOSECONDS_MAX_INSTANT;
}
// 8.5.2 CreateTemporalInstant ( epochNanoseconds [ , newTarget ] ), https://tc39.es/proposal-temporal/#sec-temporal-isvalidepochnanoseconds
ThrowCompletionOr<GC::Ref<Instant>> create_temporal_instant(VM& vm, BigInt const& epoch_nanoseconds, GC::Ptr<FunctionObject> new_target)
{
auto& realm = *vm.current_realm();
// 1. Assert: IsValidEpochNanoseconds(epochNanoseconds) is true.
VERIFY(is_valid_epoch_nanoseconds(epoch_nanoseconds.big_integer()));
// 2. If newTarget is not present, set newTarget to %Temporal.Instant%.
if (!new_target)
new_target = realm.intrinsics().temporal_instant_constructor();
// 3. Let object be ? OrdinaryCreateFromConstructor(newTarget, "%Temporal.Instant.prototype%", « [[InitializedTemporalInstant]], [[EpochNanoseconds]] »).
// 4. Set object.[[EpochNanoseconds]] to epochNanoseconds.
auto object = TRY(ordinary_create_from_constructor<Instant>(vm, *new_target, &Intrinsics::temporal_instant_prototype, epoch_nanoseconds));
// 5. Return object.
return object;
}
// 8.5.3 ToTemporalInstant ( item ), https://tc39.es/proposal-temporal/#sec-temporal-totemporalinstant
ThrowCompletionOr<GC::Ref<Instant>> to_temporal_instant(VM& vm, Value item)
{
// 1. If item is an Object, then
if (auto object = item.as_if<Object>()) {
// a. If item has an [[InitializedTemporalInstant]] or [[InitializedTemporalZonedDateTime]] internal slot, then
// i. Return ! CreateTemporalInstant(item.[[EpochNanoseconds]]).
if (auto const* instant = as_if<Instant>(*object))
return MUST(create_temporal_instant(vm, instant->epoch_nanoseconds()));
if (auto const* zoned_date_time = as_if<ZonedDateTime>(*object))
return MUST(create_temporal_instant(vm, zoned_date_time->epoch_nanoseconds()));
// b. NOTE: This use of ToPrimitive allows Instant-like objects to be converted.
// c. Set item to ? ToPrimitive(item, STRING).
item = TRY(item.to_primitive(vm, Value::PreferredType::String));
}
// 2. If item is not a String, throw a TypeError exception.
if (!item.is_string())
return vm.throw_completion<TypeError>(ErrorType::TemporalInvalidInstantString, item);
// 3. Let parsed be ? ParseISODateTime(item, « TemporalInstantString »).
auto parsed = TRY(parse_iso_date_time(vm, item.as_string().utf8_string_view(), { { Production::TemporalInstantString } }));
// 4. Assert: Either parsed.[[TimeZone]].[[OffsetString]] is not empty or parsed.[[TimeZone]].[[Z]] is true, but not both.
auto const& offset_string = parsed.time_zone.offset_string;
auto z_designator = parsed.time_zone.z_designator;
VERIFY(offset_string.has_value() || z_designator);
VERIFY(!offset_string.has_value() || !z_designator);
// 5. If parsed.[[TimeZone]].[[Z]] is true, let offsetNanoseconds be 0; else, let offsetNanoseconds be
// ! ParseDateTimeUTCOffset(parsed.[[TimeZone]].[[OffsetString]]).
auto offset_nanoseconds = z_designator ? 0.0 : parse_date_time_utc_offset(*offset_string);
// 6. If parsed.[[Time]] is START-OF-DAY, let time be MidnightTimeRecord(); else let time be parsed.[[Time]].
auto time = parsed.time.has<ParsedISODateTime::StartOfDay>() ? midnight_time_record() : parsed.time.get<Time>();
// 7. Let balanced be BalanceISODateTime(parsed.[[Year]], parsed.[[Month]], parsed.[[Day]], time.[[Hour]], time.[[Minute]], time.[[Second]], time.[[Millisecond]], time.[[Microsecond]], time.[[Nanosecond]] - offsetNanoseconds).
auto balanced = balance_iso_date_time(*parsed.year, parsed.month, parsed.day, time.hour, time.minute, time.second, time.millisecond, time.microsecond, time.nanosecond - offset_nanoseconds);
// 8. Perform ? CheckISODaysRange(balanced.[[ISODate]]).
TRY(check_iso_days_range(vm, balanced.iso_date));
// 9. Let epochNanoseconds be GetUTCEpochNanoseconds(balanced).
auto epoch_nanoseconds = get_utc_epoch_nanoseconds(balanced);
// 10. If IsValidEpochNanoseconds(epochNanoseconds) is false, throw a RangeError exception.
if (!is_valid_epoch_nanoseconds(epoch_nanoseconds))
return vm.throw_completion<RangeError>(ErrorType::TemporalInvalidEpochNanoseconds);
// 11. Return ! CreateTemporalInstant(epochNanoseconds).
return MUST(create_temporal_instant(vm, BigInt::create(vm, move(epoch_nanoseconds))));
}
// 8.5.4 CompareEpochNanoseconds ( epochNanosecondsOne, epochNanosecondsTwo ), https://tc39.es/proposal-temporal/#sec-temporal-compareepochnanoseconds
i8 compare_epoch_nanoseconds(Crypto::SignedBigInteger const& epoch_nanoseconds_one, Crypto::SignedBigInteger const& epoch_nanoseconds_two)
{
// 1. If epochNanosecondsOne > epochNanosecondsTwo, return 1.
if (epoch_nanoseconds_one > epoch_nanoseconds_two)
return 1;
// 2. If epochNanosecondsOne < epochNanosecondsTwo, return -1.
if (epoch_nanoseconds_one < epoch_nanoseconds_two)
return -1;
// 3. Return 0.
return 0;
}
// 8.5.5 AddInstant ( epochNanoseconds, timeDuration ), https://tc39.es/proposal-temporal/#sec-temporal-addinstant
ThrowCompletionOr<Crypto::SignedBigInteger> add_instant(VM& vm, Crypto::SignedBigInteger const& epoch_nanoseconds, TimeDuration const& time_duration)
{
// 1. Let result be AddTimeDurationToEpochNanoseconds(timeDuration, epochNanoseconds).
auto result = add_time_duration_to_epoch_nanoseconds(time_duration, epoch_nanoseconds);
// 2. If IsValidEpochNanoseconds(result) is false, throw a RangeError exception.
if (!is_valid_epoch_nanoseconds(result))
return vm.throw_completion<RangeError>(ErrorType::TemporalInvalidEpochNanoseconds);
// 3. Return result.
return result;
}
// 8.5.6 DifferenceInstant ( ns1, ns2, roundingIncrement, smallestUnit, roundingMode ), https://tc39.es/proposal-temporal/#sec-temporal-differenceinstant
InternalDuration difference_instant(VM& vm, Crypto::SignedBigInteger const& nanoseconds1, Crypto::SignedBigInteger const& nanoseconds2, u64 rounding_increment, Unit smallest_unit, RoundingMode rounding_mode)
{
// 1. Let timeDuration be TimeDurationFromEpochNanosecondsDifference(ns2, ns1).
auto time_duration = time_duration_from_epoch_nanoseconds_difference(nanoseconds2, nanoseconds1);
// 2. Set timeDuration to ! RoundTimeDuration(timeDuration, roundingIncrement, smallestUnit, roundingMode).
time_duration = MUST(round_time_duration(vm, time_duration, Crypto::UnsignedBigInteger { rounding_increment }, smallest_unit, rounding_mode));
// 3. Return CombineDateAndTimeDuration(ZeroDateDuration(), timeDuration).
return combine_date_and_time_duration(zero_date_duration(vm), move(time_duration));
}
// 8.5.7 RoundTemporalInstant ( ns, increment, unit, roundingMode ), https://tc39.es/proposal-temporal/#sec-temporal-roundtemporalinstant
Crypto::SignedBigInteger round_temporal_instant(Crypto::SignedBigInteger const& nanoseconds, u64 increment, Unit unit, RoundingMode rounding_mode)
{
// 1. Let unitLength be the value in the "Length in Nanoseconds" column of the row of Table 21 whose "Value" column contains unit.
auto unit_length = temporal_unit_length_in_nanoseconds(unit);
// 2. Let incrementNs be increment × unitLength.
auto increment_nanoseconds = Crypto::UnsignedBigInteger { increment }.multiplied_by(unit_length);
// 3. Return (RoundNumberToIncrementAsIfPositive((ns), incrementNs, roundingMode)).
return round_number_to_increment_as_if_positive(nanoseconds, increment_nanoseconds, rounding_mode);
}
// 8.5.8 TemporalInstantToString ( instant, timeZone, precision ), https://tc39.es/proposal-temporal/#sec-temporal-temporalinstanttostring
String temporal_instant_to_string(Instant const& instant, Optional<String const&> time_zone, SecondsStringPrecision::Precision precision)
{
// 1. Let outputTimeZone be timeZone.
// 2. If outputTimeZone is undefined, set outputTimeZone to "UTC".
auto const& output_time_zone = time_zone.value_or(UTC_TIME_ZONE);
// 3. Let epochNs be instant.[[EpochNanoseconds]].
auto const& epoch_nanoseconds = instant.epoch_nanoseconds()->big_integer();
// 4. Let isoDateTime be GetISODateTimeFor(outputTimeZone, epochNs).
auto iso_date_time = get_iso_date_time_for(output_time_zone, epoch_nanoseconds);
// 5. Let dateTimeString be ISODateTimeToString(isoDateTime, "iso8601", precision, NEVER).
auto date_time_string = iso_date_time_to_string(iso_date_time, ISO8601_CALENDAR, precision, ShowCalendar::Never);
String time_zone_string;
// 6. If timeZone is undefined, then
if (!time_zone.has_value()) {
// a. Let timeZoneString be "Z".
time_zone_string = "Z"_string;
}
// 7. Else,
else {
// a. Let offsetNanoseconds be GetOffsetNanosecondsFor(outputTimeZone, epochNs).
auto offset_nanoseconds = get_offset_nanoseconds_for(output_time_zone, epoch_nanoseconds);
// b. Let timeZoneString be FormatDateTimeUTCOffsetRounded(offsetNanoseconds).
time_zone_string = format_date_time_utc_offset_rounded(offset_nanoseconds);
}
// 8. Return the string-concatenation of dateTimeString and timeZoneString.
return MUST(String::formatted("{}{}", date_time_string, time_zone_string));
}
// 8.5.9 DifferenceTemporalInstant ( operation, instant, other, options ), https://tc39.es/proposal-temporal/#sec-temporal-differencetemporalinstant
ThrowCompletionOr<GC::Ref<Duration>> difference_temporal_instant(VM& vm, DurationOperation operation, Instant const& instant, Value other_value, Value options)
{
// 1. Set other to ? ToTemporalInstant(other).
auto other = TRY(to_temporal_instant(vm, other_value));
// 2. Let resolvedOptions be ? GetOptionsObject(options).
auto resolved_options = TRY(get_options_object(vm, options));
// 3. Let settings be ? GetDifferenceSettings(operation, resolvedOptions, TIME, « », NANOSECOND, SECOND).
auto settings = TRY(get_difference_settings(vm, operation, resolved_options, UnitGroup::Time, {}, Unit::Nanosecond, Unit::Second));
// 4. Let internalDuration be DifferenceInstant(instant.[[EpochNanoseconds]], other.[[EpochNanoseconds]], settings.[[RoundingIncrement]], settings.[[SmallestUnit]], settings.[[RoundingMode]]).
auto internal_duration = difference_instant(vm, instant.epoch_nanoseconds()->big_integer(), other->epoch_nanoseconds()->big_integer(), settings.rounding_increment, settings.smallest_unit, settings.rounding_mode);
// 5. Let result be ! TemporalDurationFromInternal(internalDuration, settings.[[LargestUnit]]).
auto result = MUST(temporal_duration_from_internal(vm, internal_duration, settings.largest_unit));
// 6. If operation is SINCE, set result to CreateNegatedTemporalDuration(result).
if (operation == DurationOperation::Since)
result = create_negated_temporal_duration(vm, result);
// 7. Return result.
return result;
}
// 8.5.10 AddDurationToInstant ( operation, instant, temporalDurationLike ), https://tc39.es/proposal-temporal/#sec-temporal-adddurationtoinstant
ThrowCompletionOr<GC::Ref<Instant>> add_duration_to_instant(VM& vm, ArithmeticOperation operation, Instant const& instant, Value temporal_duration_like)
{
// 1. Let duration be ? ToTemporalDuration(temporalDurationLike).
auto duration = TRY(to_temporal_duration(vm, temporal_duration_like));
// 2. If operation is SUBTRACT, set duration to CreateNegatedTemporalDuration(duration).
if (operation == ArithmeticOperation::Subtract)
duration = create_negated_temporal_duration(vm, duration);
// 3. Let largestUnit be DefaultTemporalLargestUnit(duration).
auto largest_unit = default_temporal_largest_unit(duration);
// 4. If TemporalUnitCategory(largestUnit) is DATE, throw a RangeError exception.
if (temporal_unit_category(largest_unit) == UnitCategory::Date)
return vm.throw_completion<RangeError>(ErrorType::TemporalInvalidLargestUnit, temporal_unit_to_string(largest_unit));
// 5. Let internalDuration be ToInternalDurationRecordWith24HourDays(duration).
auto internal_duration = to_internal_duration_record_with_24_hour_days(vm, duration);
// 6. Let ns be ? AddInstant(instant.[[EpochNanoseconds]], internalDuration.[[Time]]).
auto nanoseconds = TRY(add_instant(vm, instant.epoch_nanoseconds()->big_integer(), internal_duration.time));
// 7. Return ! CreateTemporalInstant(ns).
return MUST(create_temporal_instant(vm, BigInt::create(vm, move(nanoseconds))));
}
}