LibJS: Implement the Temporal.ZonedDateTime constructor

And the simple Temporal.ZonedDateTime.prototype getters, so that the
constructed Temporal.ZonedDateTime may actually be validated.
This commit is contained in:
Timothy Flynn 2024-11-24 20:42:47 -05:00 committed by Andreas Kling
commit 8c73cae2b8
Notes: github-actions[bot] 2024-11-26 10:04:22 +00:00
48 changed files with 1757 additions and 23 deletions

View file

@ -232,6 +232,9 @@ set(SOURCES
Runtime/Temporal/PlainYearMonthPrototype.cpp Runtime/Temporal/PlainYearMonthPrototype.cpp
Runtime/Temporal/Temporal.cpp Runtime/Temporal/Temporal.cpp
Runtime/Temporal/TimeZone.cpp Runtime/Temporal/TimeZone.cpp
Runtime/Temporal/ZonedDateTime.cpp
Runtime/Temporal/ZonedDateTimeConstructor.cpp
Runtime/Temporal/ZonedDateTimePrototype.cpp
Runtime/TypedArray.cpp Runtime/TypedArray.cpp
Runtime/TypedArrayConstructor.cpp Runtime/TypedArrayConstructor.cpp
Runtime/TypedArrayPrototype.cpp Runtime/TypedArrayPrototype.cpp

View file

@ -87,14 +87,15 @@
__JS_ENUMERATE(RelativeTimeFormat, relative_time_format, RelativeTimeFormatPrototype, RelativeTimeFormatConstructor) \ __JS_ENUMERATE(RelativeTimeFormat, relative_time_format, RelativeTimeFormatPrototype, RelativeTimeFormatConstructor) \
__JS_ENUMERATE(Segmenter, segmenter, SegmenterPrototype, SegmenterConstructor) __JS_ENUMERATE(Segmenter, segmenter, SegmenterPrototype, SegmenterConstructor)
#define JS_ENUMERATE_TEMPORAL_OBJECTS \ #define JS_ENUMERATE_TEMPORAL_OBJECTS \
__JS_ENUMERATE(Duration, duration, DurationPrototype, DurationConstructor) \ __JS_ENUMERATE(Duration, duration, DurationPrototype, DurationConstructor) \
__JS_ENUMERATE(Instant, instant, InstantPrototype, InstantConstructor) \ __JS_ENUMERATE(Instant, instant, InstantPrototype, InstantConstructor) \
__JS_ENUMERATE(PlainDate, plain_date, PlainDatePrototype, PlainDateConstructor) \ __JS_ENUMERATE(PlainDate, plain_date, PlainDatePrototype, PlainDateConstructor) \
__JS_ENUMERATE(PlainDateTime, plain_date_time, PlainDateTimePrototype, PlainDateTimeConstructor) \ __JS_ENUMERATE(PlainDateTime, plain_date_time, PlainDateTimePrototype, PlainDateTimeConstructor) \
__JS_ENUMERATE(PlainMonthDay, plain_month_day, PlainMonthDayPrototype, PlainMonthDayConstructor) \ __JS_ENUMERATE(PlainMonthDay, plain_month_day, PlainMonthDayPrototype, PlainMonthDayConstructor) \
__JS_ENUMERATE(PlainTime, plain_time, PlainTimePrototype, PlainTimeConstructor) \ __JS_ENUMERATE(PlainTime, plain_time, PlainTimePrototype, PlainTimeConstructor) \
__JS_ENUMERATE(PlainYearMonth, plain_year_month, PlainYearMonthPrototype, PlainYearMonthConstructor) __JS_ENUMERATE(PlainYearMonth, plain_year_month, PlainYearMonthPrototype, PlainYearMonthConstructor) \
__JS_ENUMERATE(ZonedDateTime, zoned_date_time, ZonedDateTimePrototype, ZonedDateTimeConstructor)
#define JS_ENUMERATE_BUILTIN_NAMESPACE_OBJECTS \ #define JS_ENUMERATE_BUILTIN_NAMESPACE_OBJECTS \
__JS_ENUMERATE(AtomicsObject, atomics) \ __JS_ENUMERATE(AtomicsObject, atomics) \

View file

@ -54,6 +54,7 @@
#include <LibJS/Runtime/Temporal/PlainMonthDay.h> #include <LibJS/Runtime/Temporal/PlainMonthDay.h>
#include <LibJS/Runtime/Temporal/PlainTime.h> #include <LibJS/Runtime/Temporal/PlainTime.h>
#include <LibJS/Runtime/Temporal/PlainYearMonth.h> #include <LibJS/Runtime/Temporal/PlainYearMonth.h>
#include <LibJS/Runtime/Temporal/ZonedDateTime.h>
#include <LibJS/Runtime/TypedArray.h> #include <LibJS/Runtime/TypedArray.h>
#include <LibJS/Runtime/Value.h> #include <LibJS/Runtime/Value.h>
#include <LibJS/Runtime/WeakMap.h> #include <LibJS/Runtime/WeakMap.h>
@ -893,6 +894,18 @@ ErrorOr<void> print_temporal_plain_year_month(JS::PrintContext& print_context, J
return {}; return {};
} }
ErrorOr<void> print_temporal_zoned_date_time(JS::PrintContext& print_context, JS::Temporal::ZonedDateTime const& zoned_date_time, HashTable<JS::Object*>& seen_objects)
{
TRY(print_type(print_context, "Temporal.ZonedDateTime"sv));
TRY(js_out(print_context, "\n epochNanoseconds: "));
TRY(print_value(print_context, zoned_date_time.epoch_nanoseconds(), seen_objects));
TRY(js_out(print_context, "\n timeZone: "));
TRY(print_value(print_context, JS::PrimitiveString::create(zoned_date_time.vm(), zoned_date_time.time_zone()), seen_objects));
TRY(js_out(print_context, "\n calendar: "));
TRY(print_value(print_context, JS::PrimitiveString::create(zoned_date_time.vm(), zoned_date_time.calendar()), seen_objects));
return {};
}
ErrorOr<void> print_boolean_object(JS::PrintContext& print_context, JS::BooleanObject const& boolean_object, HashTable<JS::Object*>& seen_objects) ErrorOr<void> print_boolean_object(JS::PrintContext& print_context, JS::BooleanObject const& boolean_object, HashTable<JS::Object*>& seen_objects)
{ {
TRY(print_type(print_context, "Boolean"sv)); TRY(print_type(print_context, "Boolean"sv));
@ -1022,6 +1035,8 @@ ErrorOr<void> print_value(JS::PrintContext& print_context, JS::Value value, Hash
return print_temporal_plain_time(print_context, static_cast<JS::Temporal::PlainTime&>(object), seen_objects); return print_temporal_plain_time(print_context, static_cast<JS::Temporal::PlainTime&>(object), seen_objects);
if (is<JS::Temporal::PlainYearMonth>(object)) if (is<JS::Temporal::PlainYearMonth>(object))
return print_temporal_plain_year_month(print_context, static_cast<JS::Temporal::PlainYearMonth&>(object), seen_objects); return print_temporal_plain_year_month(print_context, static_cast<JS::Temporal::PlainYearMonth&>(object), seen_objects);
if (is<JS::Temporal::ZonedDateTime>(object))
return print_temporal_zoned_date_time(print_context, static_cast<JS::Temporal::ZonedDateTime&>(object), seen_objects);
return print_object(print_context, object, seen_objects); return print_object(print_context, object, seen_objects);
} }

View file

@ -114,6 +114,8 @@
#include <LibJS/Runtime/Temporal/PlainYearMonthConstructor.h> #include <LibJS/Runtime/Temporal/PlainYearMonthConstructor.h>
#include <LibJS/Runtime/Temporal/PlainYearMonthPrototype.h> #include <LibJS/Runtime/Temporal/PlainYearMonthPrototype.h>
#include <LibJS/Runtime/Temporal/Temporal.h> #include <LibJS/Runtime/Temporal/Temporal.h>
#include <LibJS/Runtime/Temporal/ZonedDateTimeConstructor.h>
#include <LibJS/Runtime/Temporal/ZonedDateTimePrototype.h>
#include <LibJS/Runtime/TypedArray.h> #include <LibJS/Runtime/TypedArray.h>
#include <LibJS/Runtime/TypedArrayConstructor.h> #include <LibJS/Runtime/TypedArrayConstructor.h>
#include <LibJS/Runtime/TypedArrayPrototype.h> #include <LibJS/Runtime/TypedArrayPrototype.h>

View file

@ -21,6 +21,7 @@
#include <LibJS/Runtime/Temporal/PlainTime.h> #include <LibJS/Runtime/Temporal/PlainTime.h>
#include <LibJS/Runtime/Temporal/PlainYearMonth.h> #include <LibJS/Runtime/Temporal/PlainYearMonth.h>
#include <LibJS/Runtime/Temporal/TimeZone.h> #include <LibJS/Runtime/Temporal/TimeZone.h>
#include <LibJS/Runtime/Temporal/ZonedDateTime.h>
namespace JS::Temporal { namespace JS::Temporal {
@ -100,6 +101,29 @@ ThrowCompletionOr<Overflow> get_temporal_overflow_option(VM& vm, Object const& o
return Overflow::Reject; return Overflow::Reject;
} }
// 13.7 GetTemporalDisambiguationOption ( options ), https://tc39.es/proposal-temporal/#sec-temporal-gettemporaldisambiguationoption
ThrowCompletionOr<Disambiguation> get_temporal_disambiguation_option(VM& vm, Object const& options)
{
// 1. Let stringValue be ? GetOption(options, "disambiguation", STRING, « "compatible", "earlier", "later", "reject" », "compatible").
auto string_value = TRY(get_option(vm, options, vm.names.disambiguation, OptionType::String, { "compatible"sv, "earlier"sv, "later"sv, "reject"sv }, "compatible"sv));
auto string_view = string_value.as_string().utf8_string_view();
// 2. If stringValue is "compatible", return COMPATIBLE.
if (string_view == "compatible"sv)
return Disambiguation::Compatible;
// 3. If stringValue is "earlier", return EARLIER.
if (string_view == "earlier"sv)
return Disambiguation::Earlier;
// 4. If stringValue is "later", return LATER.
if (string_view == "later"sv)
return Disambiguation::Later;
// 5. Return REJECT.
return Disambiguation::Reject;
}
// 13.8 NegateRoundingMode ( roundingMode ), https://tc39.es/proposal-temporal/#sec-temporal-negateroundingmode // 13.8 NegateRoundingMode ( roundingMode ), https://tc39.es/proposal-temporal/#sec-temporal-negateroundingmode
RoundingMode negate_rounding_mode(RoundingMode rounding_mode) RoundingMode negate_rounding_mode(RoundingMode rounding_mode)
{ {
@ -123,22 +147,64 @@ RoundingMode negate_rounding_mode(RoundingMode rounding_mode)
return rounding_mode; return rounding_mode;
} }
// 13.9 GetTemporalOffsetOption ( options, fallback ), https://tc39.es/proposal-temporal/#sec-temporal-gettemporaloffsetoption
ThrowCompletionOr<OffsetOption> get_temporal_offset_option(VM& vm, Object const& options, OffsetOption fallback)
{
auto string_fallback = [&]() {
switch (fallback) {
// 1. If fallback is PREFER, let stringFallback be "prefer".
case OffsetOption::Prefer:
return "prefer"sv;
// 2. Else if fallback is USE, let stringFallback be "use".
case OffsetOption::Use:
return "use"sv;
// 3. Else if fallback is IGNORE, let stringFallback be "ignore".
case OffsetOption::Ignore:
return "ignore"sv;
// 4. Else, let stringFallback be "reject".
case OffsetOption::Reject:
return "reject"sv;
}
VERIFY_NOT_REACHED();
}();
// 5. Let stringValue be ? GetOption(options, "offset", STRING, « "prefer", "use", "ignore", "reject" », stringFallback).
auto string_value = TRY(get_option(vm, options, vm.names.offset, OptionType::String, { "prefer"sv, "use"sv, "ignore"sv, "reject"sv }, string_fallback));
auto string_view = string_value.as_string().utf8_string_view();
// 6. If stringValue is "prefer", return PREFER.
if (string_view == "prefer"sv)
return OffsetOption::Prefer;
// 7. If stringValue is "use", return USE.
if (string_view == "use"sv)
return OffsetOption::Use;
// 8. If stringValue is "ignore", return IGNORE.
if (string_view == "ignore"sv)
return OffsetOption::Ignore;
// 9. Return REJECT.
return OffsetOption::Reject;
}
// 13.10 GetTemporalShowCalendarNameOption ( options ), https://tc39.es/proposal-temporal/#sec-temporal-gettemporalshowcalendarnameoption // 13.10 GetTemporalShowCalendarNameOption ( options ), https://tc39.es/proposal-temporal/#sec-temporal-gettemporalshowcalendarnameoption
ThrowCompletionOr<ShowCalendar> get_temporal_show_calendar_name_option(VM& vm, Object const& options) ThrowCompletionOr<ShowCalendar> get_temporal_show_calendar_name_option(VM& vm, Object const& options)
{ {
// 1. Let stringValue be ? GetOption(options, "calendarName", STRING, « "auto", "always", "never", "critical" », "auto"). // 1. Let stringValue be ? GetOption(options, "calendarName", STRING, « "auto", "always", "never", "critical" », "auto").
auto string_value = TRY(get_option(vm, options, vm.names.calendarName, OptionType::String, { "auto"sv, "always"sv, "never"sv, "critical"sv }, "auto"sv)); auto string_value = TRY(get_option(vm, options, vm.names.calendarName, OptionType::String, { "auto"sv, "always"sv, "never"sv, "critical"sv }, "auto"sv));
auto string_view = string_value.as_string().utf8_string_view();
// 2. If stringValue is "always", return ALWAYS. // 2. If stringValue is "always", return ALWAYS.
if (string_value.as_string().utf8_string_view() == "always"sv) if (string_view == "always"sv)
return ShowCalendar::Always; return ShowCalendar::Always;
// 3. If stringValue is "never", return NEVER. // 3. If stringValue is "never", return NEVER.
if (string_value.as_string().utf8_string_view() == "never"sv) if (string_view == "never"sv)
return ShowCalendar::Never; return ShowCalendar::Never;
// 4. If stringValue is "critical", return CRITICAL. // 4. If stringValue is "critical", return CRITICAL.
if (string_value.as_string().utf8_string_view() == "critical"sv) if (string_view == "critical"sv)
return ShowCalendar::Critical; return ShowCalendar::Critical;
// 5. Return AUTO. // 5. Return AUTO.
@ -630,7 +696,6 @@ ThrowCompletionOr<bool> is_partial_temporal_object(VM& vm, Value value)
// 2. If value has an [[InitializedTemporalDate]], [[InitializedTemporalDateTime]], [[InitializedTemporalMonthDay]], // 2. If value has an [[InitializedTemporalDate]], [[InitializedTemporalDateTime]], [[InitializedTemporalMonthDay]],
// [[InitializedTemporalTime]], [[InitializedTemporalYearMonth]], or [[InitializedTemporalZonedDateTime]] internal // [[InitializedTemporalTime]], [[InitializedTemporalYearMonth]], or [[InitializedTemporalZonedDateTime]] internal
// slot, return false. // slot, return false.
// FIXME: Add the other types as we define them.
if (is<PlainDate>(object)) if (is<PlainDate>(object))
return false; return false;
if (is<PlainDateTime>(object)) if (is<PlainDateTime>(object))
@ -641,6 +706,8 @@ ThrowCompletionOr<bool> is_partial_temporal_object(VM& vm, Value value)
return false; return false;
if (is<PlainYearMonth>(object)) if (is<PlainYearMonth>(object))
return false; return false;
if (is<ZonedDateTime>(object))
return false;
// 3. Let calendarProperty be ? Get(value, "calendar"). // 3. Let calendarProperty be ? Get(value, "calendar").
auto calendar_property = TRY(object.get(vm.names.calendar)); auto calendar_property = TRY(object.get(vm.names.calendar));

View file

@ -33,11 +33,25 @@ enum class DateType {
YearMonth, YearMonth,
}; };
enum class Disambiguation {
Compatible,
Earlier,
Later,
Reject,
};
enum class DurationOperation { enum class DurationOperation {
Since, Since,
Until, Until,
}; };
enum class OffsetOption {
Prefer,
Use,
Ignore,
Reject,
};
enum class Overflow { enum class Overflow {
Constrain, Constrain,
Reject, Reject,
@ -144,7 +158,9 @@ double iso_date_to_epoch_days(double year, double month, double date);
double epoch_days_to_epoch_ms(double day, double time); double epoch_days_to_epoch_ms(double day, double time);
ThrowCompletionOr<void> check_iso_days_range(VM&, ISODate const&); ThrowCompletionOr<void> check_iso_days_range(VM&, ISODate const&);
ThrowCompletionOr<Overflow> get_temporal_overflow_option(VM&, Object const& options); ThrowCompletionOr<Overflow> get_temporal_overflow_option(VM&, Object const& options);
ThrowCompletionOr<Disambiguation> get_temporal_disambiguation_option(VM&, Object const& options);
RoundingMode negate_rounding_mode(RoundingMode); RoundingMode negate_rounding_mode(RoundingMode);
ThrowCompletionOr<OffsetOption> get_temporal_offset_option(VM&, Object const& options, OffsetOption fallback);
ThrowCompletionOr<ShowCalendar> get_temporal_show_calendar_name_option(VM&, Object const& options); ThrowCompletionOr<ShowCalendar> get_temporal_show_calendar_name_option(VM&, Object const& options);
ThrowCompletionOr<void> validate_temporal_rounding_increment(VM&, u64 increment, u64 dividend, bool inclusive); ThrowCompletionOr<void> validate_temporal_rounding_increment(VM&, u64 increment, u64 dividend, bool inclusive);
ThrowCompletionOr<Precision> get_temporal_fractional_second_digits_option(VM&, Object const& options); ThrowCompletionOr<Precision> get_temporal_fractional_second_digits_option(VM&, Object const& options);

View file

@ -19,6 +19,7 @@
#include <LibJS/Runtime/Temporal/PlainMonthDay.h> #include <LibJS/Runtime/Temporal/PlainMonthDay.h>
#include <LibJS/Runtime/Temporal/PlainYearMonth.h> #include <LibJS/Runtime/Temporal/PlainYearMonth.h>
#include <LibJS/Runtime/Temporal/TimeZone.h> #include <LibJS/Runtime/Temporal/TimeZone.h>
#include <LibJS/Runtime/Temporal/ZonedDateTime.h>
#include <LibJS/Runtime/VM.h> #include <LibJS/Runtime/VM.h>
#include <LibUnicode/Locale.h> #include <LibUnicode/Locale.h>
#include <LibUnicode/UnicodeKeywords.h> #include <LibUnicode/UnicodeKeywords.h>
@ -452,7 +453,6 @@ ThrowCompletionOr<String> to_temporal_calendar_identifier(VM& vm, Value temporal
// [[InitializedTemporalMonthDay]], [[InitializedTemporalYearMonth]], or [[InitializedTemporalZonedDateTime]] // [[InitializedTemporalMonthDay]], [[InitializedTemporalYearMonth]], or [[InitializedTemporalZonedDateTime]]
// internal slot, then // internal slot, then
// i. Return temporalCalendarLike.[[Calendar]]. // i. Return temporalCalendarLike.[[Calendar]].
// FIXME: Add the other calendar-holding types as we define them.
if (is<PlainDate>(temporal_calendar_object)) if (is<PlainDate>(temporal_calendar_object))
return static_cast<PlainDate const&>(temporal_calendar_object).calendar(); return static_cast<PlainDate const&>(temporal_calendar_object).calendar();
if (is<PlainDateTime>(temporal_calendar_object)) if (is<PlainDateTime>(temporal_calendar_object))
@ -461,6 +461,8 @@ ThrowCompletionOr<String> to_temporal_calendar_identifier(VM& vm, Value temporal
return static_cast<PlainMonthDay const&>(temporal_calendar_object).calendar(); return static_cast<PlainMonthDay const&>(temporal_calendar_object).calendar();
if (is<PlainYearMonth>(temporal_calendar_object)) if (is<PlainYearMonth>(temporal_calendar_object))
return static_cast<PlainYearMonth const&>(temporal_calendar_object).calendar(); return static_cast<PlainYearMonth const&>(temporal_calendar_object).calendar();
if (is<ZonedDateTime>(temporal_calendar_object))
return static_cast<ZonedDateTime const&>(temporal_calendar_object).calendar();
} }
// 2. If temporalCalendarLike is not a String, throw a TypeError exception. // 2. If temporalCalendarLike is not a String, throw a TypeError exception.
@ -480,7 +482,6 @@ ThrowCompletionOr<String> get_temporal_calendar_identifier_with_iso_default(VM&
// 1. If item has an [[InitializedTemporalDate]], [[InitializedTemporalDateTime]], [[InitializedTemporalMonthDay]], // 1. If item has an [[InitializedTemporalDate]], [[InitializedTemporalDateTime]], [[InitializedTemporalMonthDay]],
// [[InitializedTemporalYearMonth]], or [[InitializedTemporalZonedDateTime]] internal slot, then // [[InitializedTemporalYearMonth]], or [[InitializedTemporalZonedDateTime]] internal slot, then
// a. Return item.[[Calendar]]. // a. Return item.[[Calendar]].
// FIXME: Add the other calendar-holding types as we define them.
if (is<PlainDate>(item)) if (is<PlainDate>(item))
return static_cast<PlainDate const&>(item).calendar(); return static_cast<PlainDate const&>(item).calendar();
if (is<PlainDateTime>(item)) if (is<PlainDateTime>(item))
@ -489,6 +490,8 @@ ThrowCompletionOr<String> get_temporal_calendar_identifier_with_iso_default(VM&
return static_cast<PlainMonthDay const&>(item).calendar(); return static_cast<PlainMonthDay const&>(item).calendar();
if (is<PlainYearMonth>(item)) if (is<PlainYearMonth>(item))
return static_cast<PlainYearMonth const&>(item).calendar(); return static_cast<PlainYearMonth const&>(item).calendar();
if (is<ZonedDateTime>(item))
return static_cast<PlainYearMonth const&>(item).calendar();
// 2. Let calendarLike be ? Get(item, "calendar"). // 2. Let calendarLike be ? Get(item, "calendar").
auto calendar_like = TRY(item.get(vm.names.calendar)); auto calendar_like = TRY(item.get(vm.names.calendar));

View file

@ -15,6 +15,7 @@
#include <LibJS/Runtime/Temporal/PlainTimeConstructor.h> #include <LibJS/Runtime/Temporal/PlainTimeConstructor.h>
#include <LibJS/Runtime/Temporal/PlainYearMonthConstructor.h> #include <LibJS/Runtime/Temporal/PlainYearMonthConstructor.h>
#include <LibJS/Runtime/Temporal/Temporal.h> #include <LibJS/Runtime/Temporal/Temporal.h>
#include <LibJS/Runtime/Temporal/ZonedDateTimeConstructor.h>
namespace JS::Temporal { namespace JS::Temporal {
@ -44,6 +45,7 @@ void Temporal::initialize(Realm& realm)
define_intrinsic_accessor(vm.names.PlainMonthDay, attr, [](auto& realm) -> Value { return realm.intrinsics().temporal_plain_month_day_constructor(); }); define_intrinsic_accessor(vm.names.PlainMonthDay, attr, [](auto& realm) -> Value { return realm.intrinsics().temporal_plain_month_day_constructor(); });
define_intrinsic_accessor(vm.names.PlainTime, attr, [](auto& realm) -> Value { return realm.intrinsics().temporal_plain_time_constructor(); }); define_intrinsic_accessor(vm.names.PlainTime, attr, [](auto& realm) -> Value { return realm.intrinsics().temporal_plain_time_constructor(); });
define_intrinsic_accessor(vm.names.PlainYearMonth, attr, [](auto& realm) -> Value { return realm.intrinsics().temporal_plain_year_month_constructor(); }); define_intrinsic_accessor(vm.names.PlainYearMonth, attr, [](auto& realm) -> Value { return realm.intrinsics().temporal_plain_year_month_constructor(); });
define_intrinsic_accessor(vm.names.ZonedDateTime, attr, [](auto& realm) -> Value { return realm.intrinsics().temporal_zoned_date_time_constructor(); });
} }
} }

View file

@ -17,6 +17,7 @@
#include <LibJS/Runtime/Temporal/PlainDateTime.h> #include <LibJS/Runtime/Temporal/PlainDateTime.h>
#include <LibJS/Runtime/Temporal/PlainTime.h> #include <LibJS/Runtime/Temporal/PlainTime.h>
#include <LibJS/Runtime/Temporal/TimeZone.h> #include <LibJS/Runtime/Temporal/TimeZone.h>
#include <LibJS/Runtime/Temporal/ZonedDateTime.h>
#include <LibJS/Runtime/VM.h> #include <LibJS/Runtime/VM.h>
namespace JS::Temporal { namespace JS::Temporal {
@ -96,6 +97,39 @@ String format_offset_time_zone_identifier(i64 offset_minutes, Optional<TimeStyle
return MUST(String::formatted("{}{}", sign, time_string)); return MUST(String::formatted("{}{}", sign, time_string));
} }
// 11.1.6 FormatUTCOffsetNanoseconds ( offsetNanoseconds ), https://tc39.es/proposal-temporal/#sec-temporal-formatutcoffsetnanoseconds
String format_utc_offset_nanoseconds(i64 offset_nanoseconds)
{
// 1. If offsetNanoseconds ≥ 0, let sign be the code unit 0x002B (PLUS SIGN); otherwise, let sign be the code unit 0x002D (HYPHEN-MINUS).
auto sign = offset_nanoseconds >= 0 ? '+' : '-';
// 2. Let absoluteNanoseconds be abs(offsetNanoseconds).
auto absolute_nanoseconds = static_cast<double>(abs(offset_nanoseconds));
// 3. Let hour be floor(absoluteNanoseconds / (3600 × 10**9)).
auto hour = floor(absolute_nanoseconds / 3'600'000'000'000.0);
// 4. Let minute be floor(absoluteNanoseconds / (60 × 10**9)) modulo 60.
auto minute = modulo(floor(absolute_nanoseconds / 60'000'000'000.0), 60.0);
// 5. Let second be floor(absoluteNanoseconds / 10**9) modulo 60.
auto second = modulo(floor(absolute_nanoseconds / 1'000'000'000.0), 60.0);
// 6. Let subSecondNanoseconds be absoluteNanoseconds modulo 10**9.
auto sub_second_nanoseconds = modulo(absolute_nanoseconds, 1'000'000'000.0);
// 7. If second = 0 and subSecondNanoseconds = 0, let precision be MINUTE; otherwise, let precision be AUTO.
SecondsStringPrecision::Precision precision { Auto {} };
if (second == 0 && sub_second_nanoseconds == 0)
precision = SecondsStringPrecision::Minute {};
// 8. Let timeString be FormatTimeString(hour, minute, second, subSecondNanoseconds, precision).
auto time_string = format_time_string(hour, minute, second, sub_second_nanoseconds, precision);
// 9. Return the string-concatenation of sign and timeString.
return MUST(String::formatted("{}{}", sign, time_string));
}
// 11.1.7 FormatDateTimeUTCOffsetRounded ( offsetNanoseconds ), https://tc39.es/proposal-temporal/#sec-temporal-formatdatetimeutcoffsetrounded // 11.1.7 FormatDateTimeUTCOffsetRounded ( offsetNanoseconds ), https://tc39.es/proposal-temporal/#sec-temporal-formatdatetimeutcoffsetrounded
String format_date_time_utc_offset_rounded(i64 offset_nanoseconds) String format_date_time_utc_offset_rounded(i64 offset_nanoseconds)
{ {
@ -117,8 +151,13 @@ ThrowCompletionOr<String> to_temporal_time_zone_identifier(VM& vm, Value tempora
{ {
// 1. If temporalTimeZoneLike is an Object, then // 1. If temporalTimeZoneLike is an Object, then
if (temporal_time_zone_like.is_object()) { if (temporal_time_zone_like.is_object()) {
// FIXME: a. If temporalTimeZoneLike has an [[InitializedTemporalZonedDateTime]] internal slot, then auto const& object = temporal_time_zone_like.as_object();
// FIXME: i. Return temporalTimeZoneLike.[[TimeZone]].
// a. If temporalTimeZoneLike has an [[InitializedTemporalZonedDateTime]] internal slot, then
if (is<ZonedDateTime>(object)) {
// i. Return temporalTimeZoneLike.[[TimeZone]].
return static_cast<ZonedDateTime const&>(object).time_zone();
}
} }
// 2. If temporalTimeZoneLike is not a String, throw a TypeError exception. // 2. If temporalTimeZoneLike is not a String, throw a TypeError exception.
@ -297,6 +336,23 @@ ThrowCompletionOr<Vector<Crypto::SignedBigInteger>> get_possible_epoch_nanosecon
return possible_epoch_nanoseconds; return possible_epoch_nanoseconds;
} }
// 11.1.14 GetStartOfDay ( timeZone, isoDate ), https://tc39.es/proposal-temporal/#sec-temporal-getstartofday
ThrowCompletionOr<Crypto::SignedBigInteger> get_start_of_day(VM& vm, StringView time_zone, ISODate iso_date)
{
// 1. Let isoDateTime be CombineISODateAndTimeRecord(isoDate, MidnightTimeRecord()).
auto iso_date_time = combine_iso_date_and_time_record(iso_date, midnight_time_record());
// 2. Let possibleEpochNs be ? GetPossibleEpochNanoseconds(timeZone, isoDateTime).
auto possible_epoch_nanoseconds = TRY(get_possible_epoch_nanoseconds(vm, time_zone, iso_date_time));
// 3. If possibleEpochNs is not empty, return possibleEpochNs[0].
if (!possible_epoch_nanoseconds.is_empty())
return move(possible_epoch_nanoseconds[0]);
// FIXME: GetNamedTimeZoneEpochNanoseconds currently does not produce zero instants.
TODO();
}
// 11.1.16 ParseTimeZoneIdentifier ( identifier ), https://tc39.es/proposal-temporal/#sec-parsetimezoneidentifier // 11.1.16 ParseTimeZoneIdentifier ( identifier ), https://tc39.es/proposal-temporal/#sec-parsetimezoneidentifier
ThrowCompletionOr<TimeZone> parse_time_zone_identifier(VM& vm, StringView identifier) ThrowCompletionOr<TimeZone> parse_time_zone_identifier(VM& vm, StringView identifier)
{ {

View file

@ -21,15 +21,9 @@ struct TimeZone {
Optional<i64> offset_minutes; Optional<i64> offset_minutes;
}; };
enum class Disambiguation {
Compatible,
Earlier,
Later,
Reject,
};
ISODateTime get_iso_parts_from_epoch(Crypto::SignedBigInteger const& epoch_nanoseconds); ISODateTime get_iso_parts_from_epoch(Crypto::SignedBigInteger const& epoch_nanoseconds);
String format_offset_time_zone_identifier(i64 offset_minutes, Optional<TimeStyle> = {}); String format_offset_time_zone_identifier(i64 offset_minutes, Optional<TimeStyle> = {});
String format_utc_offset_nanoseconds(i64 offset_nanoseconds);
String format_date_time_utc_offset_rounded(i64 offset_nanoseconds); String format_date_time_utc_offset_rounded(i64 offset_nanoseconds);
ThrowCompletionOr<String> to_temporal_time_zone_identifier(VM&, Value temporal_time_zone_like); ThrowCompletionOr<String> to_temporal_time_zone_identifier(VM&, Value temporal_time_zone_like);
i64 get_offset_nanoseconds_for(StringView time_zone, Crypto::SignedBigInteger const& epoch_nanoseconds); i64 get_offset_nanoseconds_for(StringView time_zone, Crypto::SignedBigInteger const& epoch_nanoseconds);
@ -38,6 +32,7 @@ ThrowCompletionOr<String> to_temporal_time_zone_identifier(VM&, StringView tempo
ThrowCompletionOr<Crypto::SignedBigInteger> get_epoch_nanoseconds_for(VM&, StringView time_zone, ISODateTime const&, Disambiguation); ThrowCompletionOr<Crypto::SignedBigInteger> get_epoch_nanoseconds_for(VM&, StringView time_zone, ISODateTime const&, Disambiguation);
ThrowCompletionOr<Crypto::SignedBigInteger> disambiguate_possible_epoch_nanoseconds(VM&, Vector<Crypto::SignedBigInteger> possible_epoch_ns, StringView time_zone, ISODateTime const&, Disambiguation); ThrowCompletionOr<Crypto::SignedBigInteger> disambiguate_possible_epoch_nanoseconds(VM&, Vector<Crypto::SignedBigInteger> possible_epoch_ns, StringView time_zone, ISODateTime const&, Disambiguation);
ThrowCompletionOr<Vector<Crypto::SignedBigInteger>> get_possible_epoch_nanoseconds(VM&, StringView time_zone, ISODateTime const&); ThrowCompletionOr<Vector<Crypto::SignedBigInteger>> get_possible_epoch_nanoseconds(VM&, StringView time_zone, ISODateTime const&);
ThrowCompletionOr<Crypto::SignedBigInteger> get_start_of_day(VM&, StringView time_zone, ISODate);
ThrowCompletionOr<TimeZone> parse_time_zone_identifier(VM&, StringView identifier); ThrowCompletionOr<TimeZone> parse_time_zone_identifier(VM&, StringView identifier);
TimeZone parse_time_zone_identifier(StringView identifier); TimeZone parse_time_zone_identifier(StringView identifier);
TimeZone parse_time_zone_identifier(ParseResult const&); TimeZone parse_time_zone_identifier(ParseResult const&);

View file

@ -0,0 +1,320 @@
/*
* Copyright (c) 2021-2023, Linus Groh <linusg@serenityos.org>
* Copyright (c) 2021-2023, Luke Wilde <lukew@serenityos.org>
* Copyright (c) 2024, Tim Flynn <trflynn89@ladybird.org>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#include <LibJS/Runtime/AbstractOperations.h>
#include <LibJS/Runtime/Date.h>
#include <LibJS/Runtime/Temporal/Calendar.h>
#include <LibJS/Runtime/Temporal/Instant.h>
#include <LibJS/Runtime/Temporal/PlainDate.h>
#include <LibJS/Runtime/Temporal/PlainDateTime.h>
#include <LibJS/Runtime/Temporal/TimeZone.h>
#include <LibJS/Runtime/Temporal/ZonedDateTime.h>
#include <LibJS/Runtime/Temporal/ZonedDateTimeConstructor.h>
namespace JS::Temporal {
GC_DEFINE_ALLOCATOR(ZonedDateTime);
// 6 Temporal.ZonedDateTime Objects, https://tc39.es/proposal-temporal/#sec-temporal-zoneddatetime-objects
ZonedDateTime::ZonedDateTime(BigInt const& epoch_nanoseconds, String time_zone, String calendar, Object& prototype)
: Object(ConstructWithPrototypeTag::Tag, prototype)
, m_epoch_nanoseconds(epoch_nanoseconds)
, m_time_zone(move(time_zone))
, m_calendar(move(calendar))
{
}
void ZonedDateTime::visit_edges(Cell::Visitor& visitor)
{
Base::visit_edges(visitor);
visitor.visit(m_epoch_nanoseconds);
}
// 6.5.1 InterpretISODateTimeOffset ( isoDate, time, offsetBehaviour, offsetNanoseconds, timeZone, disambiguation, offsetOption, matchBehaviour ), https://tc39.es/proposal-temporal/#sec-temporal-interpretisodatetimeoffset
ThrowCompletionOr<Crypto::SignedBigInteger> interpret_iso_date_time_offset(VM& vm, ISODate iso_date, Variant<ParsedISODateTime::StartOfDay, Time> const& time_or_start_of_day, OffsetBehavior offset_behavior, double offset_nanoseconds, StringView time_zone, Disambiguation disambiguation, OffsetOption offset_option, MatchBehavior match_behavior)
{
// 1. If time is START-OF-DAY, then
if (time_or_start_of_day.has<ParsedISODateTime::StartOfDay>()) {
// a. Assert: offsetBehaviour is WALL.
VERIFY(offset_behavior == OffsetBehavior::Wall);
// b. Assert: offsetNanoseconds is 0.
VERIFY(offset_nanoseconds == 0);
// c. Return ? GetStartOfDay(timeZone, isoDate).
return TRY(get_start_of_day(vm, time_zone, iso_date));
}
auto time = time_or_start_of_day.get<Time>();
// 2. Let isoDateTime be CombineISODateAndTimeRecord(isoDate, time).
auto iso_date_time = combine_iso_date_and_time_record(iso_date, time);
// 3. If offsetBehaviour is WALL, or offsetBehaviour is OPTION and offsetOption is IGNORE, then
if (offset_behavior == OffsetBehavior::Wall || (offset_behavior == OffsetBehavior::Option && offset_option == OffsetOption::Ignore)) {
// a. Return ? GetEpochNanosecondsFor(timeZone, isoDateTime, disambiguation).
return TRY(get_epoch_nanoseconds_for(vm, time_zone, iso_date_time, disambiguation));
}
// 4. If offsetBehaviour is EXACT, or offsetBehaviour is OPTION and offsetOption is USE, then
if (offset_behavior == OffsetBehavior::Exact || (offset_behavior == OffsetBehavior::Option && offset_option == OffsetOption::Use)) {
// a. Let balanced be BalanceISODateTime(isoDate.[[Year]], isoDate.[[Month]], isoDate.[[Day]], time.[[Hour]], time.[[Minute]], time.[[Second]], time.[[Millisecond]], time.[[Microsecond]], time.[[Nanosecond]] - offsetNanoseconds).
auto balanced = balance_iso_date_time(iso_date.year, iso_date.month, iso_date.day, time.hour, time.minute, time.second, time.millisecond, time.microsecond, static_cast<double>(time.nanosecond) - offset_nanoseconds);
// b. Perform ? CheckISODaysRange(balanced.[[ISODate]]).
TRY(check_iso_days_range(vm, balanced.iso_date));
// c. Let epochNanoseconds be GetUTCEpochNanoseconds(balanced).
auto epoch_nanoseconds = get_utc_epoch_nanoseconds(balanced);
// d. If IsValidEpochNanoseconds(epochNanoseconds) is false, throw a RangeError exception.
if (!is_valid_epoch_nanoseconds(epoch_nanoseconds))
return vm.throw_completion<RangeError>(ErrorType::TemporalInvalidEpochNanoseconds);
// e. Return epochNanoseconds.
return epoch_nanoseconds;
}
// 5. Assert: offsetBehaviour is OPTION.
VERIFY(offset_behavior == OffsetBehavior::Option);
// 6. Assert: offsetOption is PREFER or REJECT.
VERIFY(offset_option == OffsetOption::Prefer || offset_option == OffsetOption::Reject);
// 7. Perform ? CheckISODaysRange(isoDate).
TRY(check_iso_days_range(vm, iso_date));
// 8. Let utcEpochNanoseconds be GetUTCEpochNanoseconds(isoDateTime).
auto utc_epoch_nanoseconds = get_utc_epoch_nanoseconds(iso_date_time);
// 9. Let possibleEpochNs be ? GetPossibleEpochNanoseconds(timeZone, isoDateTime).
auto possible_epoch_nanoseconds = TRY(get_possible_epoch_nanoseconds(vm, time_zone, iso_date_time));
// 10. For each element candidate of possibleEpochNs, do
for (auto& candidate : possible_epoch_nanoseconds) {
// a. Let candidateOffset be utcEpochNanoseconds - candidate.
auto candidate_offset = utc_epoch_nanoseconds.minus(candidate);
// b. If candidateOffset = offsetNanoseconds, then
if (candidate_offset.compare_to_double(offset_nanoseconds) == Crypto::UnsignedBigInteger::CompareResult::DoubleEqualsBigInt) {
// i. Return candidate.
return move(candidate);
}
// c. If matchBehaviour is MATCH-MINUTES, then
if (match_behavior == MatchBehavior::MatchMinutes) {
// i. Let roundedCandidateNanoseconds be RoundNumberToIncrement(candidateOffset, 60 × 10**9, HALF-EXPAND).
auto rounded_candidate_nanoseconds = round_number_to_increment(candidate_offset, NANOSECONDS_PER_MINUTE, RoundingMode::HalfExpand);
// ii. If roundedCandidateNanoseconds = offsetNanoseconds, then
if (candidate_offset.compare_to_double(offset_nanoseconds) == Crypto::UnsignedBigInteger::CompareResult::DoubleEqualsBigInt) {
// 1. Return candidate.
return move(candidate);
}
}
}
// 11. If offsetOption is reject, throw a RangeError exception.
if (offset_option == OffsetOption::Reject)
return vm.throw_completion<RangeError>(ErrorType::TemporalInvalidZonedDateTimeOffset);
// 12. Return ? DisambiguatePossibleEpochNanoseconds(possibleEpochNs, timeZone, isoDateTime, disambiguation).
return TRY(disambiguate_possible_epoch_nanoseconds(vm, move(possible_epoch_nanoseconds), time_zone, iso_date_time, disambiguation));
}
// 6.5.2 ToTemporalZonedDateTime ( item [ , options ] ), https://tc39.es/proposal-temporal/#sec-temporal-totemporalzoneddatetime
ThrowCompletionOr<GC::Ref<ZonedDateTime>> to_temporal_zoned_date_time(VM& vm, Value item, Value options)
{
// 1. If options is not present, set options to undefined.
// 2. Let offsetBehaviour be OPTION.
auto offset_behavior = OffsetBehavior::Option;
// 3. Let matchBehaviour be MATCH-EXACTLY.
auto match_behavior = MatchBehavior::MatchExactly;
String calendar;
String time_zone;
Optional<String> offset_string;
Disambiguation disambiguation;
OffsetOption offset_option;
ISODate iso_date;
Variant<ParsedISODateTime::StartOfDay, Time> time { Time {} };
// 4. If item is an Object, then
if (item.is_object()) {
auto const& object = item.as_object();
// a. If item has an [[InitializedTemporalZonedDateTime]] internal slot, then
if (is<ZonedDateTime>(object)) {
auto const& zoned_date_time = static_cast<ZonedDateTime const&>(object);
// i. NOTE: The following steps, and similar ones below, read options and perform independent validation in
// alphabetical order (GetTemporalDisambiguationOption reads "disambiguation", GetTemporalOffsetOption
// reads "offset", and GetTemporalOverflowOption reads "overflow").
// ii. Let resolvedOptions be ? GetOptionsObject(options).
auto resolved_options = TRY(get_options_object(vm, options));
// iii. Perform ? GetTemporalDisambiguationOption(resolvedOptions).
TRY(get_temporal_disambiguation_option(vm, resolved_options));
// iv. Perform ? GetTemporalOffsetOption(resolvedOptions, REJECT).
TRY(get_temporal_offset_option(vm, resolved_options, OffsetOption::Reject));
// v. Perform ? GetTemporalOverflowOption(resolvedOptions).
TRY(get_temporal_overflow_option(vm, resolved_options));
// vi. Return ! CreateTemporalZonedDateTime(item.[[EpochNanoseconds]], item.[[TimeZone]], item.[[Calendar]]).
return MUST(create_temporal_zoned_date_time(vm, zoned_date_time.epoch_nanoseconds(), zoned_date_time.time_zone(), zoned_date_time.calendar()));
}
// b. Let calendar be ? GetTemporalCalendarIdentifierWithISODefault(item).
calendar = TRY(get_temporal_calendar_identifier_with_iso_default(vm, object));
// c. Let fields be ? PrepareCalendarFields(calendar, item, « YEAR, MONTH, MONTH-CODE, DAY », « HOUR, MINUTE, SECOND, MILLISECOND, MICROSECOND, NANOSECOND, OFFSET, TIME-ZONE », « TIME-ZONE »).
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, CalendarField::Offset, CalendarField::TimeZone });
static constexpr auto required_field_names = to_array({ CalendarField::TimeZone });
auto fields = TRY(prepare_calendar_fields(vm, calendar, object, calendar_field_names, non_calendar_field_names, required_field_names.span()));
// d. Let timeZone be fields.[[TimeZone]].
time_zone = fields.time_zone.release_value();
// e. Let offsetString be fields.[[OffsetString]].
offset_string = move(fields.offset);
// f. If offsetString is UNSET, then
if (!offset_string.has_value()) {
// i. Set offsetBehaviour to WALL.
offset_behavior = OffsetBehavior::Wall;
}
// g. Let resolvedOptions be ? GetOptionsObject(options).
auto resolved_options = TRY(get_options_object(vm, options));
// h. Let disambiguation be ? GetTemporalDisambiguationOption(resolvedOptions).
disambiguation = TRY(get_temporal_disambiguation_option(vm, resolved_options));
// i. Let offsetOption be ? GetTemporalOffsetOption(resolvedOptions, REJECT).
offset_option = TRY(get_temporal_offset_option(vm, resolved_options, OffsetOption::Reject));
// j. Let overflow be ? GetTemporalOverflowOption(resolvedOptions).
auto overflow = TRY(get_temporal_overflow_option(vm, resolved_options));
// k. Let result be ? InterpretTemporalDateTimeFields(calendar, fields, overflow).
auto result = TRY(interpret_temporal_date_time_fields(vm, calendar, fields, overflow));
// l. Let isoDate be result.[[ISODate]].
iso_date = result.iso_date;
// m. Let time be result.[[Time]].
time = result.time;
}
// 5. Else,
else {
// a. If item is not a String, throw a TypeError exception.
if (!item.is_string())
return vm.throw_completion<TypeError>(ErrorType::TemporalInvalidZonedDateTimeString, item);
// b. Let result be ? ParseISODateTime(item, « TemporalDateTimeString[+Zoned] »).
auto result = TRY(parse_iso_date_time(vm, item.as_string().utf8_string_view(), { { Production::TemporalZonedDateTimeString } }));
// c. Let annotation be result.[[TimeZone]].[[TimeZoneAnnotation]].
auto annotation = move(result.time_zone.time_zone_annotation);
// d. Assert: annotation is not empty.
VERIFY(annotation.has_value());
// e. Let timeZone be ? ToTemporalTimeZoneIdentifier(annotation).
time_zone = TRY(to_temporal_time_zone_identifier(vm, *annotation));
// f. Let offsetString be result.[[TimeZone]].[[OffsetString]].
offset_string = move(result.time_zone.offset_string);
// g. If result.[[TimeZone]].[[Z]] is true, then
if (result.time_zone.z_designator) {
// i. Set offsetBehaviour to EXACT.
offset_behavior = OffsetBehavior::Exact;
}
// h. Else if offsetString is EMPTY, then
else if (!offset_string.has_value()) {
// i. Set offsetBehaviour to WALL.
offset_behavior = OffsetBehavior::Wall;
}
// i. Let calendar be result.[[Calendar]].
// j. If calendar is empty, set calendar to "iso8601".
calendar = result.calendar.value_or("iso8601"_string);
// k. Set calendar to ? CanonicalizeCalendar(calendar).
calendar = TRY(canonicalize_calendar(vm, calendar));
// l. Set matchBehaviour to MATCH-MINUTES.
match_behavior = MatchBehavior::MatchMinutes;
// m. Let resolvedOptions be ? GetOptionsObject(options).
auto resolved_options = TRY(get_options_object(vm, options));
// n. Let disambiguation be ? GetTemporalDisambiguationOption(resolvedOptions).
disambiguation = TRY(get_temporal_disambiguation_option(vm, resolved_options));
// o. Let offsetOption be ? GetTemporalOffsetOption(resolvedOptions, REJECT).
offset_option = TRY(get_temporal_offset_option(vm, resolved_options, OffsetOption::Reject));
// p. Perform ? GetTemporalOverflowOption(resolvedOptions).
TRY(get_temporal_overflow_option(vm, resolved_options));
// q. 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]].
time = result.time;
}
// 6. Let offsetNanoseconds be 0.
double offset_nanoseconds = 0;
// 7. If offsetBehaviour is OPTION, then
if (offset_behavior == OffsetBehavior::Option) {
// a. Set offsetNanoseconds to ! ParseDateTimeUTCOffset(offsetString).
offset_nanoseconds = parse_date_time_utc_offset(*offset_string);
}
// 8. Let epochNanoseconds be ? InterpretISODateTimeOffset(isoDate, time, offsetBehaviour, offsetNanoseconds, timeZone, disambiguation, offsetOption, matchBehaviour).
auto epoch_nanoseconds = TRY(interpret_iso_date_time_offset(vm, iso_date, time, offset_behavior, offset_nanoseconds, time_zone, disambiguation, offset_option, match_behavior));
// 9. Return ! CreateTemporalZonedDateTime(epochNanoseconds, timeZone, calendar).
return MUST(create_temporal_zoned_date_time(vm, BigInt::create(vm, move(epoch_nanoseconds)), move(time_zone), move(calendar)));
}
// 6.5.3 CreateTemporalZonedDateTime ( epochNanoseconds, timeZone, calendar [ , newTarget ] ), https://tc39.es/proposal-temporal/#sec-temporal-createtemporalzoneddatetime
ThrowCompletionOr<GC::Ref<ZonedDateTime>> create_temporal_zoned_date_time(VM& vm, BigInt const& epoch_nanoseconds, String time_zone, String calendar, 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.ZonedDateTime%.
if (!new_target)
new_target = realm.intrinsics().temporal_zoned_date_time_constructor();
// 3. Let object be ? OrdinaryCreateFromConstructor(newTarget, "%Temporal.ZonedDateTime.prototype%", « [[InitializedTemporalZonedDateTime]], [[EpochNanoseconds]], [[TimeZone]], [[Calendar]] »).
// 4. Set object.[[EpochNanoseconds]] to epochNanoseconds.
// 5. Set object.[[TimeZone]] to timeZone.
// 6. Set object.[[Calendar]] to calendar.
auto object = TRY(ordinary_create_from_constructor<ZonedDateTime>(vm, *new_target, &Intrinsics::temporal_zoned_date_time_prototype, epoch_nanoseconds, move(time_zone), move(calendar)));
// 7. Return object.
return object;
}
}

View file

@ -0,0 +1,54 @@
/*
* Copyright (c) 2021-2023, Linus Groh <linusg@serenityos.org>
* Copyright (c) 2024, Tim Flynn <trflynn89@ladybird.org>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#pragma once
#include <LibJS/Runtime/BigInt.h>
#include <LibJS/Runtime/Completion.h>
#include <LibJS/Runtime/Object.h>
#include <LibJS/Runtime/Temporal/AbstractOperations.h>
#include <LibJS/Runtime/Temporal/ISORecords.h>
namespace JS::Temporal {
class ZonedDateTime final : public Object {
JS_OBJECT(ZonedDateTime, Object);
GC_DECLARE_ALLOCATOR(ZonedDateTime);
public:
virtual ~ZonedDateTime() override = default;
[[nodiscard]] GC::Ref<BigInt const> epoch_nanoseconds() const { return m_epoch_nanoseconds; }
[[nodiscard]] String const& time_zone() const { return m_time_zone; }
[[nodiscard]] String const& calendar() const { return m_calendar; }
private:
ZonedDateTime(BigInt const& nanoseconds, String time_zone, String calendar, Object& prototype);
virtual void visit_edges(Visitor&) override;
GC::Ref<BigInt const> m_epoch_nanoseconds; // [[EpochNanoseconds]]
String m_time_zone; // [[TimeZone]]
String m_calendar; // [[Calendar]]
};
enum class OffsetBehavior {
Option,
Exact,
Wall,
};
enum class MatchBehavior {
MatchExactly,
MatchMinutes,
};
ThrowCompletionOr<Crypto::SignedBigInteger> interpret_iso_date_time_offset(VM&, ISODate, Variant<ParsedISODateTime::StartOfDay, Time> const&, OffsetBehavior, double offset_nanoseconds, StringView time_zone, Disambiguation, OffsetOption, MatchBehavior);
ThrowCompletionOr<GC::Ref<ZonedDateTime>> to_temporal_zoned_date_time(VM&, Value item, Value options = js_undefined());
ThrowCompletionOr<GC::Ref<ZonedDateTime>> create_temporal_zoned_date_time(VM&, BigInt const& epoch_nanoseconds, String time_zone, String calendar, GC::Ptr<FunctionObject> new_target = {});
}

View file

@ -0,0 +1,130 @@
/*
* Copyright (c) 2021-2023, Linus Groh <linusg@serenityos.org>
* Copyright (c) 2021, Luke Wilde <lukew@serenityos.org>
* Copyright (c) 2024, Tim Flynn <trflynn89@ladybird.org>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#include <LibJS/Runtime/Temporal/Calendar.h>
#include <LibJS/Runtime/Temporal/Instant.h>
#include <LibJS/Runtime/Temporal/TimeZone.h>
#include <LibJS/Runtime/Temporal/ZonedDateTime.h>
#include <LibJS/Runtime/Temporal/ZonedDateTimeConstructor.h>
#include <Libraries/LibJS/Runtime/Intl/AbstractOperations.h>
namespace JS::Temporal {
GC_DEFINE_ALLOCATOR(ZonedDateTimeConstructor);
// 6.1 The Temporal.ZonedDateTime Constructor, https://tc39.es/proposal-temporal/#sec-temporal-zoneddatetime-constructor
ZonedDateTimeConstructor::ZonedDateTimeConstructor(Realm& realm)
: NativeFunction(realm.vm().names.ZonedDateTime.as_string(), realm.intrinsics().function_prototype())
{
}
void ZonedDateTimeConstructor::initialize(Realm& realm)
{
Base::initialize(realm);
auto& vm = this->vm();
// 6.2.1 Temporal.ZonedDateTime.prototype, https://tc39.es/proposal-temporal/#sec-temporal.zoneddatetime.prototype
define_direct_property(vm.names.prototype, realm.intrinsics().temporal_zoned_date_time_prototype(), 0);
u8 attr = Attribute::Writable | Attribute::Configurable;
define_native_function(realm, vm.names.from, from, 1, attr);
define_native_function(realm, vm.names.compare, compare, 2, attr);
define_direct_property(vm.names.length, Value(2), Attribute::Configurable);
}
// 6.1.1 Temporal.ZonedDateTime ( epochNanoseconds, timeZone [ , calendar ] ), https://tc39.es/proposal-temporal/#sec-temporal.zoneddatetime
ThrowCompletionOr<Value> ZonedDateTimeConstructor::call()
{
auto& vm = this->vm();
// 1. If NewTarget is undefined, then
// a. Throw a TypeError exception.
return vm.throw_completion<TypeError>(ErrorType::ConstructorWithoutNew, "Temporal.ZonedDateTime");
}
// 6.1.1 Temporal.ZonedDateTime ( epochNanoseconds, timeZone [ , calendar ] ), https://tc39.es/proposal-temporal/#sec-temporal.zoneddatetime
ThrowCompletionOr<GC::Ref<Object>> ZonedDateTimeConstructor::construct(FunctionObject& new_target)
{
auto& vm = this->vm();
auto epoch_nanoseconds_value = vm.argument(0);
auto time_zone_value = vm.argument(1);
auto calendar_value = vm.argument(2);
// 2. Set epochNanoseconds to ? ToBigInt(epochNanoseconds).
auto epoch_nanoseconds = TRY(epoch_nanoseconds_value.to_bigint(vm));
// 3. If IsValidEpochNanoseconds(epochNanoseconds) is false, throw a RangeError exception.
if (!is_valid_epoch_nanoseconds(epoch_nanoseconds->big_integer()))
return vm.throw_completion<RangeError>(ErrorType::TemporalInvalidEpochNanoseconds);
// 4. If timeZone is not a String, throw a TypeError exception.
if (!time_zone_value.is_string())
return vm.throw_completion<TypeError>(ErrorType::NotAString, time_zone_value);
// 5. Let timeZoneParse be ? ParseTimeZoneIdentifier(timeZone).
auto time_zone_parse = TRY(parse_time_zone_identifier(vm, time_zone_value.as_string().utf8_string_view()));
String time_zone;
// 6. If timeZoneParse.[[OffsetMinutes]] is EMPTY, then
if (!time_zone_parse.offset_minutes.has_value()) {
// a. Let identifierRecord be GetAvailableNamedTimeZoneIdentifier(timeZoneParse.[[Name]]).
auto identifier_record = Intl::get_available_named_time_zone_identifier(*time_zone_parse.name);
// b. If identifierRecord is EMPTY, throw a RangeError exception.
if (!identifier_record.has_value())
return vm.throw_completion<TypeError>(ErrorType::TemporalInvalidTimeZoneName, *time_zone_parse.name);
// c. Set timeZone to identifierRecord.[[Identifier]].
time_zone = identifier_record->identifier;
}
// 7. Else,
else {
// a. Set timeZone to FormatOffsetTimeZoneIdentifier(timeZoneParse.[[OffsetMinutes]]).
time_zone = format_offset_time_zone_identifier(*time_zone_parse.offset_minutes);
}
// 8. If calendar is undefined, set calendar to "iso8601".
if (calendar_value.is_undefined())
calendar_value = PrimitiveString::create(vm, "iso8601"_string);
// 9. If calendar is not a String, throw a TypeError exception.
if (!calendar_value.is_string())
return vm.throw_completion<TypeError>(ErrorType::NotAString, calendar_value);
// 10. Set calendar to ? CanonicalizeCalendar(calendar).
auto calendar = TRY(canonicalize_calendar(vm, calendar_value.as_string().utf8_string_view()));
// 11. Return ? CreateTemporalZonedDateTime(epochNanoseconds, timeZone, calendar, NewTarget).
return TRY(create_temporal_zoned_date_time(vm, epoch_nanoseconds, move(time_zone), move(calendar), new_target));
}
// 6.2.2 Temporal.ZonedDateTime.from ( item [ , options ] ), https://tc39.es/proposal-temporal/#sec-temporal.zoneddatetime.from
JS_DEFINE_NATIVE_FUNCTION(ZonedDateTimeConstructor::from)
{
// 1. Return ? ToTemporalZonedDateTime(item, options).
return TRY(to_temporal_zoned_date_time(vm, vm.argument(0), vm.argument(1)));
}
// 6.2.3 Temporal.ZonedDateTime.compare ( one, two ), https://tc39.es/proposal-temporal/#sec-temporal.zoneddatetime.compare
JS_DEFINE_NATIVE_FUNCTION(ZonedDateTimeConstructor::compare)
{
// 1. Set one to ? ToTemporalZonedDateTime(one).
auto one = TRY(to_temporal_zoned_date_time(vm, vm.argument(0)));
// 2. Set two to ? ToTemporalZonedDateTime(two).
auto two = TRY(to_temporal_zoned_date_time(vm, vm.argument(1)));
// 3. Return 𝔽(CompareEpochNanoseconds(one.[[EpochNanoseconds]], two.[[EpochNanoseconds]])).
return compare_epoch_nanoseconds(one->epoch_nanoseconds()->big_integer(), two->epoch_nanoseconds()->big_integer());
}
}

View file

@ -0,0 +1,34 @@
/*
* Copyright (c) 2021-2022, Linus Groh <linusg@serenityos.org>
* Copyright (c) 2024, Tim Flynn <trflynn89@ladybird.org>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#pragma once
#include <LibJS/Runtime/NativeFunction.h>
namespace JS::Temporal {
class ZonedDateTimeConstructor final : public NativeFunction {
JS_OBJECT(ZonedDateTimeConstructor, NativeFunction);
GC_DECLARE_ALLOCATOR(ZonedDateTimeConstructor);
public:
virtual void initialize(Realm&) override;
virtual ~ZonedDateTimeConstructor() override = default;
virtual ThrowCompletionOr<Value> call() override;
virtual ThrowCompletionOr<GC::Ref<Object>> construct(FunctionObject& new_target) override;
private:
explicit ZonedDateTimeConstructor(Realm&);
virtual bool has_constructor() const override { return true; }
JS_DECLARE_NATIVE_FUNCTION(from);
JS_DECLARE_NATIVE_FUNCTION(compare);
};
}

View file

@ -0,0 +1,349 @@
/*
* Copyright (c) 2021-2023, Linus Groh <linusg@serenityos.org>
* Copyright (c) 2021, Luke Wilde <lukew@serenityos.org>
* Copyright (c) 2024, Tim Flynn <trflynn89@ladybird.org>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#include <LibJS/Runtime/Temporal/Calendar.h>
#include <LibJS/Runtime/Temporal/Duration.h>
#include <LibJS/Runtime/Temporal/Instant.h>
#include <LibJS/Runtime/Temporal/PlainDate.h>
#include <LibJS/Runtime/Temporal/TimeZone.h>
#include <LibJS/Runtime/Temporal/ZonedDateTimePrototype.h>
namespace JS::Temporal {
GC_DEFINE_ALLOCATOR(ZonedDateTimePrototype);
// 6.3 Properties of the Temporal.ZonedDateTime Prototype Object, https://tc39.es/proposal-temporal/#sec-properties-of-the-temporal-zoneddatetime-prototype-object
ZonedDateTimePrototype::ZonedDateTimePrototype(Realm& realm)
: PrototypeObject(realm.intrinsics().object_prototype())
{
}
void ZonedDateTimePrototype::initialize(Realm& realm)
{
Base::initialize(realm);
auto& vm = this->vm();
// 6.3.2 Temporal.ZonedDateTime.prototype[ %Symbol.toStringTag% ], https://tc39.es/proposal-temporal/#sec-temporal.zoneddatetime.prototype-%symbol.tostringtag%
define_direct_property(vm.well_known_symbol_to_string_tag(), PrimitiveString::create(vm, "Temporal.ZonedDateTime"_string), Attribute::Configurable);
define_native_accessor(realm, vm.names.calendarId, calendar_id_getter, {}, Attribute::Configurable);
define_native_accessor(realm, vm.names.timeZoneId, time_zone_id_getter, {}, Attribute::Configurable);
define_native_accessor(realm, vm.names.era, era_getter, {}, Attribute::Configurable);
define_native_accessor(realm, vm.names.eraYear, era_year_getter, {}, Attribute::Configurable);
define_native_accessor(realm, vm.names.year, year_getter, {}, Attribute::Configurable);
define_native_accessor(realm, vm.names.month, month_getter, {}, Attribute::Configurable);
define_native_accessor(realm, vm.names.monthCode, month_code_getter, {}, Attribute::Configurable);
define_native_accessor(realm, vm.names.day, day_getter, {}, Attribute::Configurable);
define_native_accessor(realm, vm.names.hour, hour_getter, {}, Attribute::Configurable);
define_native_accessor(realm, vm.names.minute, minute_getter, {}, Attribute::Configurable);
define_native_accessor(realm, vm.names.second, second_getter, {}, Attribute::Configurable);
define_native_accessor(realm, vm.names.millisecond, millisecond_getter, {}, Attribute::Configurable);
define_native_accessor(realm, vm.names.microsecond, microsecond_getter, {}, Attribute::Configurable);
define_native_accessor(realm, vm.names.nanosecond, nanosecond_getter, {}, Attribute::Configurable);
define_native_accessor(realm, vm.names.epochMilliseconds, epoch_milliseconds_getter, {}, Attribute::Configurable);
define_native_accessor(realm, vm.names.epochNanoseconds, epoch_nanoseconds_getter, {}, Attribute::Configurable);
define_native_accessor(realm, vm.names.dayOfWeek, day_of_week_getter, {}, Attribute::Configurable);
define_native_accessor(realm, vm.names.dayOfYear, day_of_year_getter, {}, Attribute::Configurable);
define_native_accessor(realm, vm.names.weekOfYear, week_of_year_getter, {}, Attribute::Configurable);
define_native_accessor(realm, vm.names.yearOfWeek, year_of_week_getter, {}, Attribute::Configurable);
define_native_accessor(realm, vm.names.hoursInDay, hours_in_day_getter, {}, Attribute::Configurable);
define_native_accessor(realm, vm.names.daysInWeek, days_in_week_getter, {}, Attribute::Configurable);
define_native_accessor(realm, vm.names.daysInMonth, days_in_month_getter, {}, Attribute::Configurable);
define_native_accessor(realm, vm.names.daysInYear, days_in_year_getter, {}, Attribute::Configurable);
define_native_accessor(realm, vm.names.monthsInYear, months_in_year_getter, {}, Attribute::Configurable);
define_native_accessor(realm, vm.names.inLeapYear, in_leap_year_getter, {}, Attribute::Configurable);
define_native_accessor(realm, vm.names.offsetNanoseconds, offset_nanoseconds_getter, {}, Attribute::Configurable);
define_native_accessor(realm, vm.names.offset, offset_getter, {}, Attribute::Configurable);
u8 attr = Attribute::Writable | Attribute::Configurable;
define_native_function(realm, vm.names.valueOf, value_of, 0, attr);
}
// 6.3.3 get Temporal.ZonedDateTime.prototype.calendarId, https://tc39.es/proposal-temporal/#sec-get-temporal.zoneddatetime.prototype.calendarid
JS_DEFINE_NATIVE_FUNCTION(ZonedDateTimePrototype::calendar_id_getter)
{
// 1. Let zonedDateTime be the this value.
// 2. Perform ? RequireInternalSlot(zonedDateTime, [[InitializedTemporalZonedDateTime]]).
auto zoned_date_time = TRY(typed_this_object(vm));
// 3. Return zonedDateTime.[[Calendar]].
return PrimitiveString::create(vm, zoned_date_time->calendar());
}
// 6.3.4 get Temporal.ZonedDateTime.prototype.timeZoneId, https://tc39.es/proposal-temporal/#sec-get-temporal.zoneddatetime.prototype.timezoneid
JS_DEFINE_NATIVE_FUNCTION(ZonedDateTimePrototype::time_zone_id_getter)
{
// 1. Let zonedDateTime be the this value.
// 2. Perform ? RequireInternalSlot(zonedDateTime, [[InitializedTemporalZonedDateTime]]).
auto zoned_date_time = TRY(typed_this_object(vm));
// 3. Return zonedDateTime.[[TimeZone]].
return PrimitiveString::create(vm, zoned_date_time->time_zone());
}
// 6.3.5 get Temporal.ZonedDateTime.prototype.era, https://tc39.es/proposal-temporal/#sec-get-temporal.zoneddatetime.prototype.era
JS_DEFINE_NATIVE_FUNCTION(ZonedDateTimePrototype::era_getter)
{
// 1. Let zonedDateTime be the this value.
// 2. Perform ? RequireInternalSlot(zonedDateTime, [[InitializedTemporalZonedDateTime]]).
auto zoned_date_time = TRY(typed_this_object(vm));
// 3. Let isoDateTime be GetISODateTimeFor(zonedDateTime.[[TimeZone]], zonedDateTime.[[EpochNanoseconds]]).
auto iso_date_time = get_iso_date_time_for(zoned_date_time->time_zone(), zoned_date_time->epoch_nanoseconds()->big_integer());
// 4. Return CalendarISOToDate(zonedDateTime.[[Calendar]], isoDateTime.[[ISODate]]).[[Era]].
auto result = calendar_iso_to_date(zoned_date_time->calendar(), iso_date_time.iso_date).era;
// 5. If result is undefined, return undefined.
if (!result.has_value())
return js_undefined();
// 6. Return 𝔽(result).
return PrimitiveString::create(vm, result.release_value());
}
// 6.3.6 get Temporal.ZonedDateTime.prototype.eraYear, https://tc39.es/proposal-temporal/#sec-get-temporal.zoneddatetime.prototype.erayear
JS_DEFINE_NATIVE_FUNCTION(ZonedDateTimePrototype::era_year_getter)
{
// 1. Let zonedDateTime be the this value.
// 2. Perform ? RequireInternalSlot(zonedDateTime, [[InitializedTemporalZonedDateTime]]).
auto zoned_date_time = TRY(typed_this_object(vm));
// 3. Let isoDateTime be GetISODateTimeFor(zonedDateTime.[[TimeZone]], zonedDateTime.[[EpochNanoseconds]]).
auto iso_date_time = get_iso_date_time_for(zoned_date_time->time_zone(), zoned_date_time->epoch_nanoseconds()->big_integer());
// 4. Let result be CalendarISOToDate(zonedDateTime.[[Calendar]], isoDateTime.[[ISODate]]).[[EraYear]].
auto result = calendar_iso_to_date(zoned_date_time->calendar(), iso_date_time.iso_date).era_year;
// 5. If result is undefined, return undefined.
if (!result.has_value())
return js_undefined();
// 6. Return 𝔽(result).
return *result;
}
// 6.3.7 get Temporal.ZonedDateTime.prototype.year, https://tc39.es/proposal-temporal/#sec-get-temporal.zoneddatetime.prototype.year
// 6.3.8 get Temporal.ZonedDateTime.prototype.month, https://tc39.es/proposal-temporal/#sec-get-temporal.zoneddatetime.prototype.month
// 6.3.10 get Temporal.ZonedDateTime.prototype.day, https://tc39.es/proposal-temporal/#sec-get-temporal.zoneddatetime.prototype.monthcode
// 6.3.19 get Temporal.ZonedDateTime.prototype.dayOfWeek, https://tc39.es/proposal-temporal/#sec-get-temporal.zoneddatetime.prototype.dayofweek
// 6.3.20 get Temporal.ZonedDateTime.prototype.dayOfYear, https://tc39.es/proposal-temporal/#sec-get-temporal.zoneddatetime.prototype.dayofyear
// 6.3.24 get Temporal.ZonedDateTime.prototype.daysInWeek, https://tc39.es/proposal-temporal/#sec-get-temporal.zoneddatetime.prototype.daysinweek
// 6.3.25 get Temporal.ZonedDateTime.prototype.daysInMonth, https://tc39.es/proposal-temporal/#sec-get-temporal.zoneddatetime.prototype.daysinmonth
// 6.3.26 get Temporal.ZonedDateTime.prototype.daysInYear, https://tc39.es/proposal-temporal/#sec-get-temporal.zoneddatetime.prototype.daysinyear
// 6.3.27 get Temporal.ZonedDateTime.prototype.monthsInYear, https://tc39.es/proposal-temporal/#sec-get-temporal.zoneddatetime.prototype.monthsinyear
// 6.3.28 get Temporal.ZonedDateTime.prototype.inLeapYear, https://tc39.es/proposal-temporal/#sec-get-temporal.zoneddatetime.prototype.inleapyear
#define JS_ENUMERATE_ZONED_DATE_TIME_SIMPLE_DATE_FIELDS \
__JS_ENUMERATE(year) \
__JS_ENUMERATE(month) \
__JS_ENUMERATE(day) \
__JS_ENUMERATE(day_of_week) \
__JS_ENUMERATE(day_of_year) \
__JS_ENUMERATE(days_in_week) \
__JS_ENUMERATE(days_in_month) \
__JS_ENUMERATE(days_in_year) \
__JS_ENUMERATE(months_in_year) \
__JS_ENUMERATE(in_leap_year)
#define __JS_ENUMERATE(field) \
JS_DEFINE_NATIVE_FUNCTION(ZonedDateTimePrototype::field##_getter) \
{ \
/* 1. Let zonedDateTime be the this value. */ \
/* 2. Perform ? RequireInternalSlot(zonedDateTime, [[InitializedTemporalZonedDateTime]]). */ \
auto zoned_date_time = TRY(typed_this_object(vm)); \
\
/* Let isoDateTime be GetISODateTimeFor(zonedDateTime.[[TimeZone]], zonedDateTime.[[EpochNanoseconds]]). */ \
auto iso_date_time = get_iso_date_time_for(zoned_date_time->time_zone(), zoned_date_time->epoch_nanoseconds()->big_integer()); \
\
/* 3. Return 𝔽(CalendarISOToDate(zonedDateTime.[[Calendar]], isoDateTime.[[ISODate]]).[[<field>]]). */ \
return calendar_iso_to_date(zoned_date_time->calendar(), iso_date_time.iso_date).field; \
}
JS_ENUMERATE_ZONED_DATE_TIME_SIMPLE_DATE_FIELDS
#undef __JS_ENUMERATE
// 6.3.9 get Temporal.ZonedDateTime.prototype.monthCode, https://tc39.es/proposal-temporal/#sec-get-temporal.zoneddatetime.prototype.monthcode
JS_DEFINE_NATIVE_FUNCTION(ZonedDateTimePrototype::month_code_getter)
{
// 1. Let zonedDateTime be the this value.
// 2. Perform ? RequireInternalSlot(zonedDateTime, [[InitializedTemporalZonedDateTime]]).
auto zoned_date_time = TRY(typed_this_object(vm));
// 3. Let isoDateTime be GetISODateTimeFor(zonedDateTime.[[TimeZone]], zonedDateTime.[[EpochNanoseconds]]).
auto iso_date_time = get_iso_date_time_for(zoned_date_time->time_zone(), zoned_date_time->epoch_nanoseconds()->big_integer());
// 4. Return CalendarISOToDate(zonedDateTime.[[Calendar]], isoDateTime.[[ISODate]]).[[MonthCode]].
auto month_code = calendar_iso_to_date(zoned_date_time->calendar(), iso_date_time.iso_date).month_code;
return PrimitiveString::create(vm, move(month_code));
}
// 6.3.11 get Temporal.ZonedDateTime.prototype.hour, https://tc39.es/proposal-temporal/#sec-get-temporal.zoneddatetime.prototype.hour
// 6.3.12 get Temporal.ZonedDateTime.prototype.minute, https://tc39.es/proposal-temporal/#sec-get-temporal.zoneddatetime.prototype.minute
// 6.3.13 get Temporal.ZonedDateTime.prototype.second, https://tc39.es/proposal-temporal/#sec-get-temporal.zoneddatetime.prototype.second
// 6.3.14 get Temporal.ZonedDateTime.prototype.millisecond, https://tc39.es/proposal-temporal/#sec-get-temporal.zoneddatetime.prototype.millisecond
// 6.3.15 get Temporal.ZonedDateTime.prototype.microsecond, https://tc39.es/proposal-temporal/#sec-get-temporal.zoneddatetime.prototype.microsecond
// 6.3.16 get Temporal.ZonedDateTime.prototype.nanosecond, https://tc39.es/proposal-temporal/#sec-get-temporal.zoneddatetime.prototype.nanosecond
#define JS_ENUMERATE_PLAIN_DATE_TIME_TIME_FIELDS \
__JS_ENUMERATE(hour) \
__JS_ENUMERATE(minute) \
__JS_ENUMERATE(second) \
__JS_ENUMERATE(millisecond) \
__JS_ENUMERATE(microsecond) \
__JS_ENUMERATE(nanosecond)
#define __JS_ENUMERATE(field) \
JS_DEFINE_NATIVE_FUNCTION(ZonedDateTimePrototype::field##_getter) \
{ \
/* 1. Let zonedDateTime be the this value. */ \
/* 2. Perform ? RequireInternalSlot(zonedDateTime, [[InitializedTemporalZonedDateTime]]). */ \
auto zoned_date_time = TRY(typed_this_object(vm)); \
\
/* Let isoDateTime be GetISODateTimeFor(zonedDateTime.[[TimeZone]], zonedDateTime.[[EpochNanoseconds]]). */ \
auto iso_date_time = get_iso_date_time_for(zoned_date_time->time_zone(), zoned_date_time->epoch_nanoseconds()->big_integer()); \
\
/* 3. Return 𝔽(isoDateTime.[[Time]].[[<field>]]). */ \
return iso_date_time.time.field; \
}
JS_ENUMERATE_PLAIN_DATE_TIME_TIME_FIELDS
#undef __JS_ENUMERATE
// 6.3.17 get Temporal.ZonedDateTime.prototype.epochMilliseconds, https://tc39.es/proposal-temporal/#sec-get-temporal.zoneddatetime.prototype.epochmilliseconds
JS_DEFINE_NATIVE_FUNCTION(ZonedDateTimePrototype::epoch_milliseconds_getter)
{
// 1. Let zonedDateTime be the this value.
// 2. Perform ? RequireInternalSlot(zonedDateTime, [[InitializedTemporalZonedDateTime]]).
auto zoned_date_time = TRY(typed_this_object(vm));
// 3. Let ns be zonedDateTime.[[EpochNanoseconds]].
auto const& nanoseconds = zoned_date_time->epoch_nanoseconds()->big_integer();
// 4. Let ms be floor((ns) / 10**6).
auto milliseconds = big_floor(nanoseconds, NANOSECONDS_PER_MILLISECOND);
// 5. Return 𝔽(ms).
return milliseconds.to_double();
}
// 6.3.18 get Temporal.ZonedDateTime.prototype.epochNanoseconds, https://tc39.es/proposal-temporal/#sec-get-temporal.zoneddatetime.prototype.epochnanoseconds
JS_DEFINE_NATIVE_FUNCTION(ZonedDateTimePrototype::epoch_nanoseconds_getter)
{
// 1. Let zonedDateTime be the this value.
// 2. Perform ? RequireInternalSlot(zonedDateTime, [[InitializedTemporalZonedDateTime]]).
auto zoned_date_time = TRY(typed_this_object(vm));
// 3. Return zonedDateTime.[[EpochNanoseconds]].
return zoned_date_time->epoch_nanoseconds();
}
// 6.3.21 get Temporal.ZonedDateTime.prototype.weekOfYear, https://tc39.es/proposal-temporal/#sec-get-temporal.zoneddatetime.prototype.weekofyear
JS_DEFINE_NATIVE_FUNCTION(ZonedDateTimePrototype::week_of_year_getter)
{
// 1. Let zonedDateTime be the this value.
// 2. Perform ? RequireInternalSlot(zonedDateTime, [[InitializedTemporalZonedDateTime]]).
auto zoned_date_time = TRY(typed_this_object(vm));
// 3. Let isoDateTime be GetISODateTimeFor(zonedDateTime.[[TimeZone]], zonedDateTime.[[EpochNanoseconds]]).
auto iso_date_time = get_iso_date_time_for(zoned_date_time->time_zone(), zoned_date_time->epoch_nanoseconds()->big_integer());
// 4. Let result be CalendarISOToDate(zonedDateTime.[[Calendar]], isoDateTime.[[ISODate]]).[[WeekOfYear]].[[Week]].
auto result = calendar_iso_to_date(zoned_date_time->calendar(), iso_date_time.iso_date).week_of_year.week;
// 5. If result is undefined, return undefined.
if (!result.has_value())
return js_undefined();
// 6. Return 𝔽(result).
return *result;
}
// 6.3.22 get Temporal.ZonedDateTime.prototype.yearOfWeek, https://tc39.es/proposal-temporal/#sec-get-temporal.zoneddatetime.prototype.yearofweek
JS_DEFINE_NATIVE_FUNCTION(ZonedDateTimePrototype::year_of_week_getter)
{
// 1. Let zonedDateTime be the this value.
// 2. Perform ? RequireInternalSlot(zonedDateTime, [[InitializedTemporalZonedDateTime]]).
auto zoned_date_time = TRY(typed_this_object(vm));
// 3. Let isoDateTime be GetISODateTimeFor(zonedDateTime.[[TimeZone]], zonedDateTime.[[EpochNanoseconds]]).
auto iso_date_time = get_iso_date_time_for(zoned_date_time->time_zone(), zoned_date_time->epoch_nanoseconds()->big_integer());
// 4. Let result be CalendarISOToDate(zonedDateTime.[[Calendar]], isoDateTime.[[ISODate]]).[[WeekOfYear]].[[Year]].
auto result = calendar_iso_to_date(zoned_date_time->calendar(), iso_date_time.iso_date).week_of_year.year;
// 5. If result is undefined, return undefined.
if (!result.has_value())
return js_undefined();
// 6. Return 𝔽(result).
return *result;
}
// 6.3.23 get Temporal.ZonedDateTime.prototype.hoursInDay, https://tc39.es/proposal-temporal/#sec-get-temporal.zoneddatetime.prototype.hoursinday
JS_DEFINE_NATIVE_FUNCTION(ZonedDateTimePrototype::hours_in_day_getter)
{
// 1. Let zonedDateTime be the this value.
// 2. Perform ? RequireInternalSlot(zonedDateTime, [[InitializedTemporalZonedDateTime]]).
auto zoned_date_time = TRY(typed_this_object(vm));
// 3. Let timeZone be zonedDateTime.[[TimeZone]].
auto const& time_zone = zoned_date_time->time_zone();
// 4. Let isoDateTime be GetISODateTimeFor(timeZone, zonedDateTime.[[EpochNanoseconds]]).
auto iso_date_time = get_iso_date_time_for(time_zone, zoned_date_time->epoch_nanoseconds()->big_integer());
// 5. Let today be isoDateTime.[[ISODate]].
auto today = iso_date_time.iso_date;
// 6. Let tomorrow be BalanceISODate(today.[[Year]], today.[[Month]], today.[[Day]] + 1).
auto tomorrow = balance_iso_date(today.year, today.month, today.day + 1);
// 7. Let todayNs be ? GetStartOfDay(timeZone, today).
auto today_nanoseconds = TRY(get_start_of_day(vm, time_zone, today));
// 8. Let tomorrowNs be ? GetStartOfDay(timeZone, tomorrow).
auto tomorrow_nanoseconds = TRY(get_start_of_day(vm, time_zone, tomorrow));
// 9. Let diff be TimeDurationFromEpochNanosecondsDifference(tomorrowNs, todayNs).
auto diff = time_duration_from_epoch_nanoseconds_difference(tomorrow_nanoseconds, today_nanoseconds);
// 10. Return 𝔽(TotalTimeDuration(diff, HOUR)).
return total_time_duration(diff, Unit::Hour).to_double();
}
// 6.3.29 get Temporal.ZonedDateTime.prototype.offsetNanoseconds, https://tc39.es/proposal-temporal/#sec-get-temporal.zoneddatetime.prototype.offsetnanoseconds
JS_DEFINE_NATIVE_FUNCTION(ZonedDateTimePrototype::offset_nanoseconds_getter)
{
// 1. Let zonedDateTime be the this value.
// 2. Perform ? RequireInternalSlot(zonedDateTime, [[InitializedTemporalZonedDateTime]]).
auto zoned_date_time = TRY(typed_this_object(vm));
// 3. Return 𝔽(GetOffsetNanosecondsFor(zonedDateTime.[[TimeZone]], zonedDateTime.[[EpochNanoseconds]])).
return static_cast<double>(get_offset_nanoseconds_for(zoned_date_time->time_zone(), zoned_date_time->epoch_nanoseconds()->big_integer()));
}
// 6.3.30 get Temporal.ZonedDateTime.prototype.offset, https://tc39.es/proposal-temporal/#sec-get-temporal.zoneddatetime.prototype.offset
JS_DEFINE_NATIVE_FUNCTION(ZonedDateTimePrototype::offset_getter)
{
// 1. Let zonedDateTime be the this value.
// 2. Perform ? RequireInternalSlot(zonedDateTime, [[InitializedTemporalZonedDateTime]]).
auto zoned_date_time = TRY(typed_this_object(vm));
// 3. Let offsetNanoseconds be GetOffsetNanosecondsFor(zonedDateTime.[[TimeZone]], zonedDateTime.[[EpochNanoseconds]]).
auto offset_nanoseconds = get_offset_nanoseconds_for(zoned_date_time->time_zone(), zoned_date_time->epoch_nanoseconds()->big_integer());
// 4. Return FormatUTCOffsetNanoseconds(offsetNanoseconds).
return PrimitiveString::create(vm, format_utc_offset_nanoseconds(offset_nanoseconds));
}
// 6.3.44 Temporal.ZonedDateTime.prototype.valueOf ( ), https://tc39.es/proposal-temporal/#sec-temporal.zoneddatetime.prototype.valueof
JS_DEFINE_NATIVE_FUNCTION(ZonedDateTimePrototype::value_of)
{
// 1. Throw a TypeError exception.
return vm.throw_completion<TypeError>(ErrorType::Convert, "Temporal.ZonedDateTime", "a primitive value");
}
}

View file

@ -0,0 +1,57 @@
/*
* Copyright (c) 2021, Linus Groh <linusg@serenityos.org>
* Copyright (c) 2024, Tim Flynn <trflynn89@ladybird.org>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#pragma once
#include <LibJS/Runtime/PrototypeObject.h>
#include <LibJS/Runtime/Temporal/ZonedDateTime.h>
namespace JS::Temporal {
class ZonedDateTimePrototype final : public PrototypeObject<ZonedDateTimePrototype, ZonedDateTime> {
JS_PROTOTYPE_OBJECT(ZonedDateTimePrototype, ZonedDateTime, Temporal.ZonedDateTime);
GC_DECLARE_ALLOCATOR(ZonedDateTimePrototype);
public:
virtual void initialize(Realm&) override;
virtual ~ZonedDateTimePrototype() override = default;
private:
explicit ZonedDateTimePrototype(Realm&);
JS_DECLARE_NATIVE_FUNCTION(calendar_id_getter);
JS_DECLARE_NATIVE_FUNCTION(time_zone_id_getter);
JS_DECLARE_NATIVE_FUNCTION(era_getter);
JS_DECLARE_NATIVE_FUNCTION(era_year_getter);
JS_DECLARE_NATIVE_FUNCTION(year_getter);
JS_DECLARE_NATIVE_FUNCTION(month_getter);
JS_DECLARE_NATIVE_FUNCTION(month_code_getter);
JS_DECLARE_NATIVE_FUNCTION(day_getter);
JS_DECLARE_NATIVE_FUNCTION(hour_getter);
JS_DECLARE_NATIVE_FUNCTION(minute_getter);
JS_DECLARE_NATIVE_FUNCTION(second_getter);
JS_DECLARE_NATIVE_FUNCTION(millisecond_getter);
JS_DECLARE_NATIVE_FUNCTION(microsecond_getter);
JS_DECLARE_NATIVE_FUNCTION(nanosecond_getter);
JS_DECLARE_NATIVE_FUNCTION(epoch_milliseconds_getter);
JS_DECLARE_NATIVE_FUNCTION(epoch_nanoseconds_getter);
JS_DECLARE_NATIVE_FUNCTION(day_of_week_getter);
JS_DECLARE_NATIVE_FUNCTION(day_of_year_getter);
JS_DECLARE_NATIVE_FUNCTION(week_of_year_getter);
JS_DECLARE_NATIVE_FUNCTION(year_of_week_getter);
JS_DECLARE_NATIVE_FUNCTION(hours_in_day_getter);
JS_DECLARE_NATIVE_FUNCTION(days_in_week_getter);
JS_DECLARE_NATIVE_FUNCTION(days_in_month_getter);
JS_DECLARE_NATIVE_FUNCTION(days_in_year_getter);
JS_DECLARE_NATIVE_FUNCTION(months_in_year_getter);
JS_DECLARE_NATIVE_FUNCTION(in_leap_year_getter);
JS_DECLARE_NATIVE_FUNCTION(offset_nanoseconds_getter);
JS_DECLARE_NATIVE_FUNCTION(offset_getter);
JS_DECLARE_NATIVE_FUNCTION(value_of);
};
}

View file

@ -0,0 +1,15 @@
describe("correct behavior", () => {
test("length is 2", () => {
expect(Temporal.ZonedDateTime.compare).toHaveLength(2);
});
test("basic functionality", () => {
const zonedDateTimeOne = new Temporal.ZonedDateTime(1n, "UTC");
const zonedDateTimeTwo = new Temporal.ZonedDateTime(2n, "UTC");
expect(Temporal.ZonedDateTime.compare(zonedDateTimeOne, zonedDateTimeOne)).toBe(0);
expect(Temporal.ZonedDateTime.compare(zonedDateTimeTwo, zonedDateTimeTwo)).toBe(0);
expect(Temporal.ZonedDateTime.compare(zonedDateTimeOne, zonedDateTimeTwo)).toBe(-1);
expect(Temporal.ZonedDateTime.compare(zonedDateTimeTwo, zonedDateTimeOne)).toBe(1);
});
});

View file

@ -0,0 +1,132 @@
describe("correct behavior", () => {
test("length is 1", () => {
expect(Temporal.ZonedDateTime.from).toHaveLength(1);
});
test("ZonedDateTime instance argument", () => {
const timeZone = "UTC";
const calendar = "gregory";
const zonedDateTime = new Temporal.ZonedDateTime(1627318123456789000n, timeZone, calendar);
const createdZoneDateTime = Temporal.ZonedDateTime.from(zonedDateTime);
expect(createdZoneDateTime).toBeInstanceOf(Temporal.ZonedDateTime);
expect(createdZoneDateTime).not.toBe(zonedDateTime);
expect(createdZoneDateTime.timeZoneId).toBe(timeZone);
expect(createdZoneDateTime.calendarId).toBe(calendar);
expect(createdZoneDateTime.epochNanoseconds).toBe(1627318123456789000n);
});
test("PlainDate instance argument", () => {
const timeZone = "UTC";
const calendar = "gregory";
const plainDate = new Temporal.PlainDate(2021, 11, 7, calendar);
plainDate.timeZone = timeZone;
const createdZoneDateTime = Temporal.ZonedDateTime.from(plainDate);
expect(createdZoneDateTime).toBeInstanceOf(Temporal.ZonedDateTime);
expect(createdZoneDateTime.timeZoneId).toBe(timeZone);
expect(createdZoneDateTime.calendarId).toBe(calendar);
expect(createdZoneDateTime.year).toBe(2021);
expect(createdZoneDateTime.month).toBe(11);
expect(createdZoneDateTime.day).toBe(7);
});
test("PlainDateTime instance argument", () => {
const timeZone = "UTC";
const calendar = "gregory";
const plainDateTime = new Temporal.PlainDateTime(
2021,
11,
7,
0,
20,
5,
100,
200,
300,
calendar
);
plainDateTime.timeZone = timeZone;
const createdZoneDateTime = Temporal.ZonedDateTime.from(plainDateTime);
expect(createdZoneDateTime).toBeInstanceOf(Temporal.ZonedDateTime);
expect(createdZoneDateTime.timeZoneId).toBe(timeZone);
expect(createdZoneDateTime.calendarId).toBe(calendar);
expect(createdZoneDateTime.year).toBe(2021);
expect(createdZoneDateTime.month).toBe(11);
expect(createdZoneDateTime.day).toBe(7);
expect(createdZoneDateTime.hour).toBe(0);
expect(createdZoneDateTime.minute).toBe(20);
expect(createdZoneDateTime.second).toBe(5);
expect(createdZoneDateTime.millisecond).toBe(100);
expect(createdZoneDateTime.microsecond).toBe(200);
expect(createdZoneDateTime.nanosecond).toBe(300);
});
test("ZonedDateTime-like argument", () => {
const timeZone = "UTC";
const calendar = "gregory";
const zdtLike = {
timeZone,
calendar,
year: 2021,
month: 11,
day: 7,
hour: 0,
minute: 20,
second: 5,
millisecond: 100,
microsecond: 200,
nanosecond: 300,
};
const createdZoneDateTime = Temporal.ZonedDateTime.from(zdtLike);
expect(createdZoneDateTime).toBeInstanceOf(Temporal.ZonedDateTime);
expect(createdZoneDateTime.timeZoneId).toBe(timeZone);
expect(createdZoneDateTime.calendarId).toBe(calendar);
expect(createdZoneDateTime.year).toBe(2021);
expect(createdZoneDateTime.month).toBe(11);
expect(createdZoneDateTime.day).toBe(7);
expect(createdZoneDateTime.hour).toBe(0);
expect(createdZoneDateTime.minute).toBe(20);
expect(createdZoneDateTime.second).toBe(5);
expect(createdZoneDateTime.millisecond).toBe(100);
expect(createdZoneDateTime.microsecond).toBe(200);
expect(createdZoneDateTime.nanosecond).toBe(300);
});
test("from string", () => {
const zonedDateTime = Temporal.ZonedDateTime.from(
"2021-11-07T00:20:05.100200300+00:00[UTC][u-ca=iso8601]"
);
expect(zonedDateTime).toBeInstanceOf(Temporal.ZonedDateTime);
expect(zonedDateTime.timeZoneId).toBe("UTC");
expect(zonedDateTime.calendarId).toBe("iso8601");
expect(zonedDateTime.year).toBe(2021);
expect(zonedDateTime.month).toBe(11);
expect(zonedDateTime.day).toBe(7);
expect(zonedDateTime.hour).toBe(0);
expect(zonedDateTime.minute).toBe(20);
expect(zonedDateTime.second).toBe(5);
expect(zonedDateTime.millisecond).toBe(100);
expect(zonedDateTime.microsecond).toBe(200);
expect(zonedDateTime.nanosecond).toBe(300);
expect(zonedDateTime.offset).toBe("+00:00");
expect(zonedDateTime.offsetNanoseconds).toBe(0);
});
});
describe("errors", () => {
test("requires timeZone property", () => {
expect(() => {
Temporal.ZonedDateTime.from({});
}).toThrowWithMessage(TypeError, "Required property timeZone is missing or undefined");
});
test("invalid zoned date time string", () => {
expect(() => {
Temporal.ZonedDateTime.from("foo");
}).toThrowWithMessage(RangeError, "Invalid ISO date time");
});
});

View file

@ -0,0 +1,39 @@
describe("errors", () => {
test("called without new", () => {
expect(() => {
Temporal.ZonedDateTime();
}).toThrowWithMessage(
TypeError,
"Temporal.ZonedDateTime constructor must be called with 'new'"
);
});
test("out-of-range epoch nanoseconds value", () => {
expect(() => {
new Temporal.ZonedDateTime(8_640_000_000_000_000_000_001n);
}).toThrowWithMessage(
RangeError,
"Invalid epoch nanoseconds value, must be in range -86400 * 10^17 to 86400 * 10^17"
);
expect(() => {
new Temporal.ZonedDateTime(-8_640_000_000_000_000_000_001n);
}).toThrowWithMessage(
RangeError,
"Invalid epoch nanoseconds value, must be in range -86400 * 10^17 to 86400 * 10^17"
);
});
});
describe("normal behavior", () => {
test("length is 2", () => {
expect(Temporal.ZonedDateTime).toHaveLength(2);
});
test("basic functionality", () => {
const timeZone = "UTC";
const zonedDateTime = new Temporal.ZonedDateTime(0n, timeZone);
expect(typeof zonedDateTime).toBe("object");
expect(zonedDateTime).toBeInstanceOf(Temporal.ZonedDateTime);
expect(Object.getPrototypeOf(zonedDateTime)).toBe(Temporal.ZonedDateTime.prototype);
});
});

View file

@ -0,0 +1,16 @@
describe("correct behavior", () => {
test("calendarId basic functionality", () => {
const timeZone = "UTC";
const calendar = "gregory";
const zonedDateTime = new Temporal.ZonedDateTime(0n, timeZone, calendar);
expect(zonedDateTime.calendarId).toBe("gregory");
});
});
describe("errors", () => {
test("this value must be a Temporal.ZonedDateTime object", () => {
expect(() => {
Reflect.get(Temporal.ZonedDateTime.prototype, "calendarId", "foo");
}).toThrowWithMessage(TypeError, "Not an object of type Temporal.ZonedDateTime");
});
});

View file

@ -0,0 +1,15 @@
describe("correct behavior", () => {
test("basic functionality", () => {
const timeZone = "UTC";
const zonedDateTime = new Temporal.ZonedDateTime(1625614921000000000n, timeZone);
expect(zonedDateTime.day).toBe(6);
});
});
describe("errors", () => {
test("this value must be a Temporal.ZonedDateTime object", () => {
expect(() => {
Reflect.get(Temporal.ZonedDateTime.prototype, "day", "foo");
}).toThrowWithMessage(TypeError, "Not an object of type Temporal.ZonedDateTime");
});
});

View file

@ -0,0 +1,15 @@
describe("correct behavior", () => {
test("basic functionality", () => {
const timeZone = "UTC";
const zonedDateTime = new Temporal.ZonedDateTime(1625614921000000000n, timeZone);
expect(zonedDateTime.dayOfWeek).toBe(2);
});
});
describe("errors", () => {
test("this value must be a Temporal.ZonedDateTime object", () => {
expect(() => {
Reflect.get(Temporal.ZonedDateTime.prototype, "dayOfWeek", "foo");
}).toThrowWithMessage(TypeError, "Not an object of type Temporal.ZonedDateTime");
});
});

View file

@ -0,0 +1,15 @@
describe("correct behavior", () => {
test("basic functionality", () => {
const timeZone = "UTC";
const zonedDateTime = new Temporal.ZonedDateTime(1625614921000000000n, timeZone);
expect(zonedDateTime.dayOfYear).toBe(187);
});
});
describe("errors", () => {
test("this value must be a Temporal.ZonedDateTime object", () => {
expect(() => {
Reflect.get(Temporal.ZonedDateTime.prototype, "dayOfYear", "foo");
}).toThrowWithMessage(TypeError, "Not an object of type Temporal.ZonedDateTime");
});
});

View file

@ -0,0 +1,15 @@
describe("correct behavior", () => {
test("basic functionality", () => {
const timeZone = "UTC";
const zonedDateTime = new Temporal.ZonedDateTime(1625614921000000000n, timeZone);
expect(zonedDateTime.daysInMonth).toBe(31);
});
});
describe("errors", () => {
test("this value must be a Temporal.ZonedDateTime object", () => {
expect(() => {
Reflect.get(Temporal.ZonedDateTime.prototype, "daysInMonth", "foo");
}).toThrowWithMessage(TypeError, "Not an object of type Temporal.ZonedDateTime");
});
});

View file

@ -0,0 +1,15 @@
describe("correct behavior", () => {
test("basic functionality", () => {
const timeZone = "UTC";
const zonedDateTime = new Temporal.ZonedDateTime(1625614921000000000n, timeZone);
expect(zonedDateTime.daysInWeek).toBe(7);
});
});
describe("errors", () => {
test("this value must be a Temporal.ZonedDateTime object", () => {
expect(() => {
Reflect.get(Temporal.ZonedDateTime.prototype, "daysInWeek", "foo");
}).toThrowWithMessage(TypeError, "Not an object of type Temporal.ZonedDateTime");
});
});

View file

@ -0,0 +1,15 @@
describe("correct behavior", () => {
test("basic functionality", () => {
const timeZone = "UTC";
const zonedDateTime = new Temporal.ZonedDateTime(1625614921000000000n, timeZone);
expect(zonedDateTime.daysInYear).toBe(365);
});
});
describe("errors", () => {
test("this value must be a Temporal.ZonedDateTime object", () => {
expect(() => {
Reflect.get(Temporal.ZonedDateTime.prototype, "daysInYear", "foo");
}).toThrowWithMessage(TypeError, "Not an object of type Temporal.ZonedDateTime");
});
});

View file

@ -0,0 +1,15 @@
describe("correct behavior", () => {
test("basic functionality", () => {
const timeZone = "UTC";
const zonedDateTime = new Temporal.ZonedDateTime(1625614921000000000n, timeZone);
expect(zonedDateTime.epochMilliseconds).toBe(1625614921000);
});
});
describe("errors", () => {
test("this value must be a Temporal.ZonedDateTime object", () => {
expect(() => {
Reflect.get(Temporal.ZonedDateTime.prototype, "epochMilliseconds", "foo");
}).toThrowWithMessage(TypeError, "Not an object of type Temporal.ZonedDateTime");
});
});

View file

@ -0,0 +1,15 @@
describe("correct behavior", () => {
test("basic functionality", () => {
const timeZone = "UTC";
const zonedDateTime = new Temporal.ZonedDateTime(1625614921000000000n, timeZone);
expect(zonedDateTime.epochNanoseconds).toBe(1625614921000000000n);
});
});
describe("errors", () => {
test("this value must be a Temporal.ZonedDateTime object", () => {
expect(() => {
Reflect.get(Temporal.ZonedDateTime.prototype, "epochNanoseconds", "foo");
}).toThrowWithMessage(TypeError, "Not an object of type Temporal.ZonedDateTime");
});
});

View file

@ -0,0 +1,15 @@
describe("correct behavior", () => {
test("basic functionality", () => {
const timeZone = "UTC";
const zonedDateTime = new Temporal.ZonedDateTime(1625614921000000000n, timeZone);
expect(zonedDateTime.era).toBeUndefined();
});
});
describe("errors", () => {
test("this value must be a Temporal.ZonedDateTime object", () => {
expect(() => {
Reflect.get(Temporal.ZonedDateTime.prototype, "era", "foo");
}).toThrowWithMessage(TypeError, "Not an object of type Temporal.ZonedDateTime");
});
});

View file

@ -0,0 +1,15 @@
describe("correct behavior", () => {
test("basic functionality", () => {
const timeZone = "UTC";
const zonedDateTime = new Temporal.ZonedDateTime(1625614921000000000n, timeZone);
expect(zonedDateTime.eraYear).toBeUndefined();
});
});
describe("errors", () => {
test("this value must be a Temporal.ZonedDateTime object", () => {
expect(() => {
Reflect.get(Temporal.ZonedDateTime.prototype, "eraYear", "foo");
}).toThrowWithMessage(TypeError, "Not an object of type Temporal.ZonedDateTime");
});
});

View file

@ -0,0 +1,15 @@
describe("correct behavior", () => {
test("basic functionality", () => {
const timeZone = "UTC";
const zonedDateTime = new Temporal.ZonedDateTime(1625614921000000000n, timeZone);
expect(zonedDateTime.hour).toBe(23);
});
});
describe("errors", () => {
test("this value must be a Temporal.ZonedDateTime object", () => {
expect(() => {
Reflect.get(Temporal.ZonedDateTime.prototype, "hour", "foo");
}).toThrowWithMessage(TypeError, "Not an object of type Temporal.ZonedDateTime");
});
});

View file

@ -0,0 +1,15 @@
describe("correct behavior", () => {
test("basic functionality", () => {
const timeZone = "UTC";
const zonedDateTime = new Temporal.ZonedDateTime(1625614921000000000n, timeZone);
expect(zonedDateTime.hoursInDay).toBe(24);
});
});
describe("errors", () => {
test("this value must be a Temporal.ZonedDateTime object", () => {
expect(() => {
Reflect.get(Temporal.ZonedDateTime.prototype, "hoursInDay", "foo");
}).toThrowWithMessage(TypeError, "Not an object of type Temporal.ZonedDateTime");
});
});

View file

@ -0,0 +1,15 @@
describe("correct behavior", () => {
test("basic functionality", () => {
const timeZone = "UTC";
const zonedDateTime = new Temporal.ZonedDateTime(1625614921000000000n, timeZone);
expect(zonedDateTime.inLeapYear).toBeFalse();
});
});
describe("errors", () => {
test("this value must be a Temporal.ZonedDateTime object", () => {
expect(() => {
Reflect.get(Temporal.ZonedDateTime.prototype, "inLeapYear", "foo");
}).toThrowWithMessage(TypeError, "Not an object of type Temporal.ZonedDateTime");
});
});

View file

@ -0,0 +1,15 @@
describe("correct behavior", () => {
test("basic functionality", () => {
const timeZone = "UTC";
const zonedDateTime = new Temporal.ZonedDateTime(123000n, timeZone);
expect(zonedDateTime.microsecond).toBe(123);
});
});
describe("errors", () => {
test("this value must be a Temporal.ZonedDateTime object", () => {
expect(() => {
Reflect.get(Temporal.ZonedDateTime.prototype, "microsecond", "foo");
}).toThrowWithMessage(TypeError, "Not an object of type Temporal.ZonedDateTime");
});
});

View file

@ -0,0 +1,15 @@
describe("correct behavior", () => {
test("basic functionality", () => {
const timeZone = "UTC";
const zonedDateTime = new Temporal.ZonedDateTime(123000000n, timeZone);
expect(zonedDateTime.millisecond).toBe(123);
});
});
describe("errors", () => {
test("this value must be a Temporal.ZonedDateTime object", () => {
expect(() => {
Reflect.get(Temporal.ZonedDateTime.prototype, "millisecond", "foo");
}).toThrowWithMessage(TypeError, "Not an object of type Temporal.ZonedDateTime");
});
});

View file

@ -0,0 +1,15 @@
describe("correct behavior", () => {
test("basic functionality", () => {
const timeZone = "UTC";
const zonedDateTime = new Temporal.ZonedDateTime(1625614921000000000n, timeZone);
expect(zonedDateTime.minute).toBe(42);
});
});
describe("errors", () => {
test("this value must be a Temporal.ZonedDateTime object", () => {
expect(() => {
Reflect.get(Temporal.ZonedDateTime.prototype, "minute", "foo");
}).toThrowWithMessage(TypeError, "Not an object of type Temporal.ZonedDateTime");
});
});

View file

@ -0,0 +1,15 @@
describe("correct behavior", () => {
test("basic functionality", () => {
const timeZone = "UTC";
const zonedDateTime = new Temporal.ZonedDateTime(1625614921000000000n, timeZone);
expect(zonedDateTime.month).toBe(7);
});
});
describe("errors", () => {
test("this value must be a Temporal.ZonedDateTime object", () => {
expect(() => {
Reflect.get(Temporal.ZonedDateTime.prototype, "month", "foo");
}).toThrowWithMessage(TypeError, "Not an object of type Temporal.ZonedDateTime");
});
});

View file

@ -0,0 +1,15 @@
describe("correct behavior", () => {
test("basic functionality", () => {
const timeZone = "UTC";
const zonedDateTime = new Temporal.ZonedDateTime(1625614921000000000n, timeZone);
expect(zonedDateTime.monthCode).toBe("M07");
});
});
describe("errors", () => {
test("this value must be a Temporal.ZonedDateTime object", () => {
expect(() => {
Reflect.get(Temporal.ZonedDateTime.prototype, "monthCode", "foo");
}).toThrowWithMessage(TypeError, "Not an object of type Temporal.ZonedDateTime");
});
});

View file

@ -0,0 +1,15 @@
describe("correct behavior", () => {
test("basic functionality", () => {
const timeZone = "UTC";
const zonedDateTime = new Temporal.ZonedDateTime(1625614921000000000n, timeZone);
expect(zonedDateTime.monthsInYear).toBe(12);
});
});
describe("errors", () => {
test("this value must be a Temporal.ZonedDateTime object", () => {
expect(() => {
Reflect.get(Temporal.ZonedDateTime.prototype, "monthsInYear", "foo");
}).toThrowWithMessage(TypeError, "Not an object of type Temporal.ZonedDateTime");
});
});

View file

@ -0,0 +1,15 @@
describe("correct behavior", () => {
test("basic functionality", () => {
const timeZone = "UTC";
const zonedDateTime = new Temporal.ZonedDateTime(123n, timeZone);
expect(zonedDateTime.nanosecond).toBe(123);
});
});
describe("errors", () => {
test("this value must be a Temporal.ZonedDateTime object", () => {
expect(() => {
Reflect.get(Temporal.ZonedDateTime.prototype, "nanosecond", "foo");
}).toThrowWithMessage(TypeError, "Not an object of type Temporal.ZonedDateTime");
});
});

View file

@ -0,0 +1,21 @@
describe("correct behavior", () => {
test("basic functionality", () => {
const timeZone = "UTC";
const zonedDateTime = new Temporal.ZonedDateTime(0n, timeZone);
expect(zonedDateTime.offset).toBe("+00:00");
});
test("custom offset", () => {
const timeZone = "+01:30";
const zonedDateTime = new Temporal.ZonedDateTime(0n, timeZone);
expect(zonedDateTime.offset).toBe(timeZone);
});
});
describe("errors", () => {
test("this value must be a Temporal.ZonedDateTime object", () => {
expect(() => {
Reflect.get(Temporal.ZonedDateTime.prototype, "offset", "foo");
}).toThrowWithMessage(TypeError, "Not an object of type Temporal.ZonedDateTime");
});
});

View file

@ -0,0 +1,21 @@
describe("correct behavior", () => {
test("basic functionality", () => {
const timeZone = "UTC";
const zonedDateTime = new Temporal.ZonedDateTime(0n, timeZone);
expect(zonedDateTime.offsetNanoseconds).toBe(0);
});
test("custom offset", () => {
const timeZone = "+01:30";
const zonedDateTime = new Temporal.ZonedDateTime(0n, timeZone);
expect(zonedDateTime.offsetNanoseconds).toBe(5400000000000);
});
});
describe("errors", () => {
test("this value must be a Temporal.ZonedDateTime object", () => {
expect(() => {
Reflect.get(Temporal.ZonedDateTime.prototype, "offsetNanoseconds", "foo");
}).toThrowWithMessage(TypeError, "Not an object of type Temporal.ZonedDateTime");
});
});

View file

@ -0,0 +1,15 @@
describe("correct behavior", () => {
test("basic functionality", () => {
const timeZone = "UTC";
const zonedDateTime = new Temporal.ZonedDateTime(1625614921000000000n, timeZone);
expect(zonedDateTime.second).toBe(1);
});
});
describe("errors", () => {
test("this value must be a Temporal.ZonedDateTime object", () => {
expect(() => {
Reflect.get(Temporal.ZonedDateTime.prototype, "second", "foo");
}).toThrowWithMessage(TypeError, "Not an object of type Temporal.ZonedDateTime");
});
});

View file

@ -0,0 +1,15 @@
describe("correct behavior", () => {
test("basic functionality", () => {
const timeZone = "UTC";
const zonedDateTime = new Temporal.ZonedDateTime(0n, timeZone);
expect(zonedDateTime.timeZoneId).toBe(timeZone);
});
});
describe("errors", () => {
test("this value must be a Temporal.ZonedDateTime object", () => {
expect(() => {
Reflect.get(Temporal.ZonedDateTime.prototype, "timeZoneId", "foo");
}).toThrowWithMessage(TypeError, "Not an object of type Temporal.ZonedDateTime");
});
});

View file

@ -0,0 +1,11 @@
describe("errors", () => {
test("throws TypeError", () => {
const timeZone = "UTC";
expect(() => {
new Temporal.ZonedDateTime(0n, timeZone).valueOf();
}).toThrowWithMessage(
TypeError,
"Cannot convert Temporal.ZonedDateTime to a primitive value"
);
});
});

View file

@ -0,0 +1,15 @@
describe("correct behavior", () => {
test("basic functionality", () => {
const timeZone = "UTC";
const zonedDateTime = new Temporal.ZonedDateTime(1625614921000000000n, timeZone);
expect(zonedDateTime.weekOfYear).toBe(27);
});
});
describe("errors", () => {
test("this value must be a Temporal.ZonedDateTime object", () => {
expect(() => {
Reflect.get(Temporal.ZonedDateTime.prototype, "weekOfYear", "foo");
}).toThrowWithMessage(TypeError, "Not an object of type Temporal.ZonedDateTime");
});
});

View file

@ -0,0 +1,15 @@
describe("correct behavior", () => {
test("basic functionality", () => {
const timeZone = "UTC";
const zonedDateTime = new Temporal.ZonedDateTime(1625614921000000000n, timeZone);
expect(zonedDateTime.year).toBe(2021);
});
});
describe("errors", () => {
test("this value must be a Temporal.ZonedDateTime object", () => {
expect(() => {
Reflect.get(Temporal.ZonedDateTime.prototype, "year", "foo");
}).toThrowWithMessage(TypeError, "Not an object of type Temporal.ZonedDateTime");
});
});

View file

@ -0,0 +1,15 @@
describe("correct behavior", () => {
test("basic functionality", () => {
const timeZone = "UTC";
const zonedDateTime = new Temporal.ZonedDateTime(1672531200000000000n, timeZone);
expect(zonedDateTime.yearOfWeek).toBe(2022);
});
});
describe("errors", () => {
test("this value must be a Temporal.ZonedDateTime object", () => {
expect(() => {
Reflect.get(Temporal.ZonedDateTime.prototype, "yearOfWeek", "foo");
}).toThrowWithMessage(TypeError, "Not an object of type Temporal.ZonedDateTime");
});
});