Skip to content

Commit 4ef2161

Browse files
trflynn89awesomekling
authored andcommitted
LibJS: Implement stringification Temporal.ZonedDateTime prototypes
1 parent c0150ac commit 4ef2161

File tree

9 files changed

+339
-1
lines changed

9 files changed

+339
-1
lines changed

Libraries/LibJS/Runtime/Temporal/AbstractOperations.cpp

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -211,6 +211,40 @@ ThrowCompletionOr<ShowCalendar> get_temporal_show_calendar_name_option(VM& vm, O
211211
return ShowCalendar::Auto;
212212
}
213213

214+
// 13.11 GetTemporalShowTimeZoneNameOption ( options ), https://tc39.es/proposal-temporal/#sec-temporal-gettemporalshowtimezonenameoption
215+
ThrowCompletionOr<ShowTimeZoneName> get_temporal_show_time_zone_name_option(VM& vm, Object const& options)
216+
{
217+
// 1. Let stringValue be ? GetOption(options, "timeZoneName", STRING, « "auto", "never", "critical" », "auto").
218+
auto string_value = TRY(get_option(vm, options, vm.names.timeZoneName, OptionType::String, { "auto"sv, "never"sv, "critical"sv }, "auto"sv));
219+
auto string_view = string_value.as_string().utf8_string_view();
220+
221+
// 2. If stringValue is "never", return NEVER.
222+
if (string_view == "never"sv)
223+
return ShowTimeZoneName::Never;
224+
225+
// 3. If stringValue is "critical", return CRITICAL.
226+
if (string_view == "critical"sv)
227+
return ShowTimeZoneName::Critical;
228+
229+
// 4. Return AUTO.
230+
return ShowTimeZoneName::Auto;
231+
}
232+
233+
// 13.12 GetTemporalShowOffsetOption ( options ), https://tc39.es/proposal-temporal/#sec-temporal-gettemporalshowoffsetoption
234+
ThrowCompletionOr<ShowOffset> get_temporal_show_offset_option(VM& vm, Object const& options)
235+
{
236+
// 1. Let stringValue be ? GetOption(options, "offset", STRING, « "auto", "never" », "auto").
237+
auto string_value = TRY(get_option(vm, options, vm.names.offset, OptionType::String, { "auto"sv, "never"sv }, "auto"sv));
238+
auto string_view = string_value.as_string().utf8_string_view();
239+
240+
// 2. If stringValue is "never", return never.
241+
if (string_view == "never"sv)
242+
return ShowOffset::Never;
243+
244+
// 3. Return auto.
245+
return ShowOffset::Auto;
246+
}
247+
214248
// 13.14 ValidateTemporalRoundingIncrement ( increment, dividend, inclusive ), https://tc39.es/proposal-temporal/#sec-validatetemporalroundingincrement
215249
ThrowCompletionOr<void> validate_temporal_rounding_increment(VM& vm, u64 increment, u64 dividend, bool inclusive)
216250
{

Libraries/LibJS/Runtime/Temporal/AbstractOperations.h

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,17 @@ enum class ShowCalendar {
6464
Critical,
6565
};
6666

67+
enum class ShowOffset {
68+
Auto,
69+
Never,
70+
};
71+
72+
enum class ShowTimeZoneName {
73+
Auto,
74+
Never,
75+
Critical,
76+
};
77+
6778
enum class TimeStyle {
6879
Separated,
6980
Unseparated,
@@ -161,6 +172,8 @@ ThrowCompletionOr<Overflow> get_temporal_overflow_option(VM&, Object const& opti
161172
ThrowCompletionOr<Disambiguation> get_temporal_disambiguation_option(VM&, Object const& options);
162173
RoundingMode negate_rounding_mode(RoundingMode);
163174
ThrowCompletionOr<OffsetOption> get_temporal_offset_option(VM&, Object const& options, OffsetOption fallback);
175+
ThrowCompletionOr<ShowTimeZoneName> get_temporal_show_time_zone_name_option(VM&, Object const& options);
176+
ThrowCompletionOr<ShowOffset> get_temporal_show_offset_option(VM&, Object const& options);
164177
ThrowCompletionOr<ShowCalendar> get_temporal_show_calendar_name_option(VM&, Object const& options);
165178
ThrowCompletionOr<void> validate_temporal_rounding_increment(VM&, u64 increment, u64 dividend, bool inclusive);
166179
ThrowCompletionOr<Precision> get_temporal_fractional_second_digits_option(VM&, Object const& options);
@@ -184,7 +197,7 @@ Crypto::SignedBigInteger round_number_to_increment_as_if_positive(Crypto::Signed
184197
ThrowCompletionOr<ParsedISODateTime> parse_iso_date_time(VM&, StringView iso_string, ReadonlySpan<Production> allowed_formats);
185198
ThrowCompletionOr<String> parse_temporal_calendar_string(VM&, String const&);
186199
ThrowCompletionOr<GC::Ref<Duration>> parse_temporal_duration_string(VM&, StringView iso_string);
187-
ThrowCompletionOr<TimeZone> parse_temporal_time_zone_string(VM& vm, StringView time_zone_string);
200+
ThrowCompletionOr<TimeZone> parse_temporal_time_zone_string(VM&, StringView time_zone_string);
188201
ThrowCompletionOr<String> to_month_code(VM&, Value argument);
189202
ThrowCompletionOr<String> to_offset_string(VM&, Value argument);
190203
CalendarFields iso_date_to_fields(StringView calendar, ISODate const&, DateType);

Libraries/LibJS/Runtime/Temporal/ZonedDateTime.cpp

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -319,6 +319,63 @@ ThrowCompletionOr<GC::Ref<ZonedDateTime>> create_temporal_zoned_date_time(VM& vm
319319
return object;
320320
}
321321

322+
// 6.5.4 TemporalZonedDateTimeToString ( zonedDateTime, precision, showCalendar, showTimeZone, showOffset [ , increment [ , unit [ , roundingMode ] ] ] ), https://tc39.es/proposal-temporal/#sec-temporal-temporalzoneddatetimetostring
323+
String temporal_zoned_date_time_to_string(ZonedDateTime const& zoned_date_time, SecondsStringPrecision::Precision precision, ShowCalendar show_calendar, ShowTimeZoneName show_time_zone, ShowOffset show_offset, u64 increment, Unit unit, RoundingMode rounding_mode)
324+
{
325+
// 1. If increment is not present, set increment to 1.
326+
// 2. If unit is not present, set unit to NANOSECOND.
327+
// 3. If roundingMode is not present, set roundingMode to TRUNC.
328+
329+
// 4. Let epochNs be zonedDateTime.[[EpochNanoseconds]].
330+
// 5. Set epochNs to RoundTemporalInstant(epochNs, increment, unit, roundingMode).
331+
auto epoch_nanoseconds = round_temporal_instant(zoned_date_time.epoch_nanoseconds()->big_integer(), increment, unit, rounding_mode);
332+
333+
// 6. Let timeZone be zonedDateTime.[[TimeZone]].
334+
auto const& time_zone = zoned_date_time.time_zone();
335+
336+
// 7. Let offsetNanoseconds be GetOffsetNanosecondsFor(timeZone, epochNs).
337+
auto offset_nanoseconds = get_offset_nanoseconds_for(time_zone, epoch_nanoseconds);
338+
339+
// 8. Let isoDateTime be GetISODateTimeFor(timeZone, epochNs).
340+
auto iso_date_time = get_iso_date_time_for(time_zone, epoch_nanoseconds);
341+
342+
// 9. Let dateTimeString be ISODateTimeToString(isoDateTime, "iso8601", precision, NEVER).
343+
auto date_time_string = iso_date_time_to_string(iso_date_time, "iso8601"sv, precision, ShowCalendar::Never);
344+
345+
String offset_string;
346+
String time_zone_string;
347+
348+
// 10. If showOffset is NEVER, then
349+
if (show_offset == ShowOffset::Never) {
350+
// a. Let offsetString be the empty String.
351+
}
352+
// 11. Else,
353+
else {
354+
// a. Let offsetString be FormatDateTimeUTCOffsetRounded(offsetNanoseconds).
355+
offset_string = format_date_time_utc_offset_rounded(offset_nanoseconds);
356+
}
357+
358+
// 12. If showTimeZone is NEVER, then
359+
if (show_time_zone == ShowTimeZoneName::Never) {
360+
// a. Let timeZoneString be the empty String.
361+
}
362+
// 13. Else,
363+
else {
364+
// a. If showTimeZone is critical, let flag be "!"; else let flag be the empty String.
365+
auto flag = show_time_zone == ShowTimeZoneName::Critical ? "!"sv : ""sv;
366+
367+
// b. Let timeZoneString be the string-concatenation of the code unit 0x005B (LEFT SQUARE BRACKET), flag,
368+
// timeZone, and the code unit 0x005D (RIGHT SQUARE BRACKET).
369+
time_zone_string = MUST(String::formatted("[{}{}]", flag, time_zone));
370+
}
371+
372+
// 14. Let calendarString be FormatCalendarAnnotation(zonedDateTime.[[Calendar]], showCalendar).
373+
auto calendar_string = format_calendar_annotation(zoned_date_time.calendar(), show_calendar);
374+
375+
// 15. Return the string-concatenation of dateTimeString, offsetString, timeZoneString, and calendarString.
376+
return MUST(String::formatted("{}{}{}{}", date_time_string, offset_string, time_zone_string, calendar_string));
377+
}
378+
322379
// 6.5.5 AddZonedDateTime ( epochNanoseconds, timeZone, calendar, duration, overflow ), https://tc39.es/proposal-temporal/#sec-temporal-addzoneddatetime
323380
ThrowCompletionOr<Crypto::SignedBigInteger> add_zoned_date_time(VM& vm, Crypto::SignedBigInteger const& epoch_nanoseconds, StringView time_zone, StringView calendar, InternalDuration const& duration, Overflow overflow)
324381
{

Libraries/LibJS/Runtime/Temporal/ZonedDateTime.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,7 @@ enum class MatchBehavior {
5151
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);
5252
ThrowCompletionOr<GC::Ref<ZonedDateTime>> to_temporal_zoned_date_time(VM&, Value item, Value options = js_undefined());
5353
ThrowCompletionOr<GC::Ref<ZonedDateTime>> create_temporal_zoned_date_time(VM&, BigInt const& epoch_nanoseconds, String time_zone, String calendar, GC::Ptr<FunctionObject> new_target = {});
54+
String temporal_zoned_date_time_to_string(ZonedDateTime const&, SecondsStringPrecision::Precision, ShowCalendar, ShowTimeZoneName, ShowOffset, u64 increment = 1, Unit = Unit::Nanosecond, RoundingMode = RoundingMode::Trunc);
5455
ThrowCompletionOr<Crypto::SignedBigInteger> add_zoned_date_time(VM&, Crypto::SignedBigInteger const& epoch_nanoseconds, StringView time_zone, StringView calendar, InternalDuration const&, Overflow);
5556
ThrowCompletionOr<InternalDuration> difference_zoned_date_time(VM&, Crypto::SignedBigInteger const& nanoseconds1, Crypto::SignedBigInteger const& nanoseconds2, StringView time_zone, StringView calendar, Unit largest_unit);
5657
ThrowCompletionOr<InternalDuration> difference_zoned_date_time_with_rounding(VM&, Crypto::SignedBigInteger const& nanoseconds1, Crypto::SignedBigInteger const& nanoseconds2, StringView time_zone, StringView calendar, Unit largest_unit, u64 rounding_increment, Unit smallest_unit, RoundingMode);

Libraries/LibJS/Runtime/Temporal/ZonedDateTimePrototype.cpp

Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,9 @@ void ZonedDateTimePrototype::initialize(Realm& realm)
6262
define_native_accessor(realm, vm.names.offset, offset_getter, {}, Attribute::Configurable);
6363

6464
u8 attr = Attribute::Writable | Attribute::Configurable;
65+
define_native_function(realm, vm.names.toString, to_string, 0, attr);
66+
define_native_function(realm, vm.names.toLocaleString, to_locale_string, 0, attr);
67+
define_native_function(realm, vm.names.toJSON, to_json, 0, attr);
6568
define_native_function(realm, vm.names.valueOf, value_of, 0, attr);
6669
}
6770

@@ -339,6 +342,72 @@ JS_DEFINE_NATIVE_FUNCTION(ZonedDateTimePrototype::offset_getter)
339342
return PrimitiveString::create(vm, format_utc_offset_nanoseconds(offset_nanoseconds));
340343
}
341344

345+
// 6.3.41 Temporal.ZonedDateTime.prototype.toString ( [ options ] ), https://tc39.es/proposal-temporal/#sec-temporal.zoneddatetime.prototype.tostring
346+
JS_DEFINE_NATIVE_FUNCTION(ZonedDateTimePrototype::to_string)
347+
{
348+
// 1. Let zonedDateTime be the this value.
349+
// 2. Perform ? RequireInternalSlot(zonedDateTime, [[InitializedTemporalZonedDateTime]]).
350+
auto zoned_date_time = TRY(typed_this_object(vm));
351+
352+
// 3. Let resolvedOptions be ? GetOptionsObject(options).
353+
auto resolved_options = TRY(get_options_object(vm, vm.argument(0)));
354+
355+
// 4. NOTE: The following steps read options and perform independent validation in alphabetical order
356+
// (GetTemporalShowCalendarNameOption reads "calendarName", GetTemporalFractionalSecondDigitsOption reads
357+
// "fractionalSecondDigits", GetTemporalShowOffsetOption reads "offset", and GetRoundingModeOption reads "roundingMode").
358+
359+
// 5. Let showCalendar be ? GetTemporalShowCalendarNameOption(resolvedOptions).
360+
auto show_calendar = TRY(get_temporal_show_calendar_name_option(vm, resolved_options));
361+
362+
// 6. Let digits be ? GetTemporalFractionalSecondDigitsOption(resolvedOptions).
363+
auto digits = TRY(get_temporal_fractional_second_digits_option(vm, resolved_options));
364+
365+
// 7. Let showOffset be ? GetTemporalShowOffsetOption(resolvedOptions).
366+
auto show_offset = TRY(get_temporal_show_offset_option(vm, resolved_options));
367+
368+
// 8. Let roundingMode be ? GetRoundingModeOption(resolvedOptions, TRUNC).
369+
auto rounding_mode = TRY(get_rounding_mode_option(vm, resolved_options, RoundingMode::Trunc));
370+
371+
// 9. Let smallestUnit be ? GetTemporalUnitValuedOption(resolvedOptions, "smallestUnit", TIME, UNSET).
372+
auto smallest_unit = TRY(get_temporal_unit_valued_option(vm, resolved_options, vm.names.smallestUnit, UnitGroup::Time, Unset {}));
373+
374+
// 10. If smallestUnit is hour, throw a RangeError exception.
375+
if (auto const* unit = smallest_unit.get_pointer<Unit>(); unit && *unit == Unit::Hour)
376+
return vm.throw_completion<RangeError>(ErrorType::OptionIsNotValidValue, temporal_unit_to_string(*unit), vm.names.smallestUnit);
377+
378+
// 11. Let showTimeZone be ? GetTemporalShowTimeZoneNameOption(resolvedOptions).
379+
auto show_time_zone = TRY(get_temporal_show_time_zone_name_option(vm, resolved_options));
380+
381+
// 12. Let precision be ToSecondsStringPrecisionRecord(smallestUnit, digits).
382+
auto precision = to_seconds_string_precision_record(smallest_unit, digits);
383+
384+
// 13. Return TemporalZonedDateTimeToString(zonedDateTime, precision.[[Precision]], showCalendar, showTimeZone, showOffset, precision.[[Increment]], precision.[[Unit]], roundingMode).
385+
return PrimitiveString::create(vm, temporal_zoned_date_time_to_string(zoned_date_time, precision.precision, show_calendar, show_time_zone, show_offset, precision.increment, precision.unit, rounding_mode));
386+
}
387+
388+
// 6.3.42 Temporal.ZonedDateTime.prototype.toLocaleString ( [ locales [ , options ] ] ), https://tc39.es/proposal-temporal/#sec-temporal.zoneddatetime.prototype.tolocalestring
389+
// NOTE: This is the minimum toLocaleString implementation for engines without ECMA-402.
390+
JS_DEFINE_NATIVE_FUNCTION(ZonedDateTimePrototype::to_locale_string)
391+
{
392+
// 1. Let zonedDateTime be the this value.
393+
// 2. Perform ? RequireInternalSlot(zonedDateTime, [[InitializedTemporalZonedDateTime]]).
394+
auto zoned_date_time = TRY(typed_this_object(vm));
395+
396+
// 3. Return TemporalZonedDateTimeToString(zonedDateTime, AUTO, AUTO, AUTO, AUTO).
397+
return PrimitiveString::create(vm, temporal_zoned_date_time_to_string(zoned_date_time, Auto {}, ShowCalendar::Auto, ShowTimeZoneName::Auto, ShowOffset::Auto));
398+
}
399+
400+
// 6.3.43 Temporal.ZonedDateTime.prototype.toJSON ( ), https://tc39.es/proposal-temporal/#sec-temporal.zoneddatetime.prototype.tojson
401+
JS_DEFINE_NATIVE_FUNCTION(ZonedDateTimePrototype::to_json)
402+
{
403+
// 1. Let zonedDateTime be the this value.
404+
// 2. Perform ? RequireInternalSlot(zonedDateTime, [[InitializedTemporalZonedDateTime]]).
405+
auto zoned_date_time = TRY(typed_this_object(vm));
406+
407+
// 3. Return TemporalZonedDateTimeToString(zonedDateTime, AUTO, AUTO, AUTO, AUTO).
408+
return PrimitiveString::create(vm, temporal_zoned_date_time_to_string(zoned_date_time, Auto {}, ShowCalendar::Auto, ShowTimeZoneName::Auto, ShowOffset::Auto));
409+
}
410+
342411
// 6.3.44 Temporal.ZonedDateTime.prototype.valueOf ( ), https://tc39.es/proposal-temporal/#sec-temporal.zoneddatetime.prototype.valueof
343412
JS_DEFINE_NATIVE_FUNCTION(ZonedDateTimePrototype::value_of)
344413
{

Libraries/LibJS/Runtime/Temporal/ZonedDateTimePrototype.h

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,9 @@ class ZonedDateTimePrototype final : public PrototypeObject<ZonedDateTimePrototy
5151
JS_DECLARE_NATIVE_FUNCTION(in_leap_year_getter);
5252
JS_DECLARE_NATIVE_FUNCTION(offset_nanoseconds_getter);
5353
JS_DECLARE_NATIVE_FUNCTION(offset_getter);
54+
JS_DECLARE_NATIVE_FUNCTION(to_string);
55+
JS_DECLARE_NATIVE_FUNCTION(to_locale_string);
56+
JS_DECLARE_NATIVE_FUNCTION(to_json);
5457
JS_DECLARE_NATIVE_FUNCTION(value_of);
5558
};
5659

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
describe("correct behavior", () => {
2+
test("length is 0", () => {
3+
expect(Temporal.ZonedDateTime.prototype.toJSON).toHaveLength(0);
4+
});
5+
6+
test("basic functionality", () => {
7+
const plainDateTime = new Temporal.PlainDateTime(2021, 11, 3, 1, 33, 5, 100, 200, 300);
8+
const zonedDateTime = plainDateTime.toZonedDateTime("UTC");
9+
expect(zonedDateTime.toJSON()).toBe("2021-11-03T01:33:05.1002003+00:00[UTC]");
10+
});
11+
});
12+
13+
describe("errors", () => {
14+
test("this value must be a Temporal.ZonedDateTime object", () => {
15+
expect(() => {
16+
Temporal.ZonedDateTime.prototype.toJSON.call("foo");
17+
}).toThrowWithMessage(TypeError, "Not an object of type Temporal.ZonedDateTime");
18+
});
19+
});
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
describe("correct behavior", () => {
2+
test("length is 0", () => {
3+
expect(Temporal.ZonedDateTime.prototype.toLocaleString).toHaveLength(0);
4+
});
5+
6+
test("basic functionality", () => {
7+
const plainDateTime = new Temporal.PlainDateTime(2021, 11, 3, 1, 33, 5, 100, 200, 300);
8+
const zonedDateTime = plainDateTime.toZonedDateTime("UTC");
9+
expect(zonedDateTime.toLocaleString()).toBe("2021-11-03T01:33:05.1002003+00:00[UTC]");
10+
});
11+
});
12+
13+
describe("errors", () => {
14+
test("this value must be a Temporal.ZonedDateTime object", () => {
15+
expect(() => {
16+
Temporal.ZonedDateTime.prototype.toLocaleString.call("foo");
17+
}).toThrowWithMessage(TypeError, "Not an object of type Temporal.ZonedDateTime");
18+
});
19+
});

0 commit comments

Comments
 (0)