2021-07-07 17:41:37 +01:00
/*
* Copyright ( c ) 2021 , Linus Groh < linusg @ serenityos . org >
2021-07-11 21:04:11 +03:00
* Copyright ( c ) 2021 , Idan Horowitz < idan . horowitz @ serenityos . org >
2021-07-07 17:41:37 +01:00
*
* SPDX - License - Identifier : BSD - 2 - Clause
*/
2021-08-31 00:15:36 +01:00
# include <AK/Variant.h>
2021-07-07 17:41:37 +01:00
# include <LibCrypto/BigInt/SignedBigInteger.h>
# include <LibJS/Runtime/AbstractOperations.h>
# include <LibJS/Runtime/GlobalObject.h>
2021-07-11 21:04:11 +03:00
# include <LibJS/Runtime/Temporal/AbstractOperations.h>
2021-08-31 00:15:36 +01:00
# include <LibJS/Runtime/Temporal/Calendar.h>
2021-07-07 17:41:37 +01:00
# include <LibJS/Runtime/Temporal/Instant.h>
# include <LibJS/Runtime/Temporal/InstantConstructor.h>
2021-07-11 21:04:11 +03:00
# include <LibJS/Runtime/Temporal/PlainDateTime.h>
# include <LibJS/Runtime/Temporal/TimeZone.h>
2021-08-01 17:58:08 +01:00
# include <LibJS/Runtime/Temporal/ZonedDateTime.h>
2021-07-07 17:41:37 +01:00
namespace JS : : Temporal {
// 8 Temporal.Instant Objects, https://tc39.es/proposal-temporal/#sec-temporal-instant-objects
2021-09-09 21:18:08 +01:00
Instant : : Instant ( BigInt const & nanoseconds , Object & prototype )
2021-07-07 17:41:37 +01:00
: Object ( prototype )
, m_nanoseconds ( nanoseconds )
{
}
void Instant : : visit_edges ( Cell : : Visitor & visitor )
{
Base : : visit_edges ( visitor ) ;
visitor . visit ( & m_nanoseconds ) ;
}
// 8.5.1 IsValidEpochNanoseconds ( epochNanoseconds ), https://tc39.es/proposal-temporal/#sec-temporal-isvalidepochnanoseconds
bool is_valid_epoch_nanoseconds ( BigInt const & epoch_nanoseconds )
{
// 1. Assert: Type(epochNanoseconds) is BigInt.
// 2. If epochNanoseconds < − 86400ℤ × 10^17ℤ or epochNanoseconds > 86400ℤ × 10^17ℤ , then
if ( epoch_nanoseconds . big_integer ( ) < INSTANT_NANOSECONDS_MIN | | epoch_nanoseconds . big_integer ( ) > INSTANT_NANOSECONDS_MAX ) {
// a. Return false.
return false ;
}
// 3. Return true.
return true ;
}
// 8.5.2 CreateTemporalInstant ( epochNanoseconds [ , newTarget ] ), https://tc39.es/proposal-temporal/#sec-temporal-createtemporalinstant
2021-09-09 21:18:08 +01:00
Instant * create_temporal_instant ( GlobalObject & global_object , BigInt const & epoch_nanoseconds , FunctionObject const * new_target )
2021-07-07 17:41:37 +01:00
{
// 1. Assert: Type(epochNanoseconds) is BigInt.
// 2. Assert: ! IsValidEpochNanoseconds(epochNanoseconds) is true.
VERIFY ( is_valid_epoch_nanoseconds ( epoch_nanoseconds ) ) ;
// 3. If newTarget is not present, set it to %Temporal.Instant%.
if ( ! new_target )
new_target = global_object . temporal_instant_constructor ( ) ;
// 4. Let object be ? OrdinaryCreateFromConstructor(newTarget, "%Temporal.Instant.prototype%", « [[InitializedTemporalInstant]], [[Nanoseconds]] »).
// 5. Set object.[[Nanoseconds]] to epochNanoseconds.
2021-09-16 00:57:38 +03:00
auto * object = TRY_OR_DISCARD ( ordinary_create_from_constructor < Instant > ( global_object , * new_target , & GlobalObject : : temporal_instant_prototype , epoch_nanoseconds ) ) ;
2021-07-07 17:41:37 +01:00
// 6. Return object.
return object ;
}
2021-07-11 21:04:11 +03:00
// 8.5.3 ToTemporalInstant ( item ), https://tc39.es/proposal-temporal/#sec-temporal-totemporalinstant
Instant * to_temporal_instant ( GlobalObject & global_object , Value item )
{
auto & vm = global_object . vm ( ) ;
// 1. If Type(item) is Object, then
if ( item . is_object ( ) ) {
// a. If item has an [[InitializedTemporalInstant]] internal slot, then
if ( is < Instant > ( item . as_object ( ) ) ) {
// i. Return item.
return & static_cast < Instant & > ( item . as_object ( ) ) ;
}
2021-08-01 17:58:08 +01:00
2021-07-11 21:04:11 +03:00
// b. If item has an [[InitializedTemporalZonedDateTime]] internal slot, then
2021-08-01 17:58:08 +01:00
if ( is < ZonedDateTime > ( item . as_object ( ) ) ) {
auto & zoned_date_time = static_cast < ZonedDateTime & > ( item . as_object ( ) ) ;
// i. Return ! CreateTemporalInstant(item.[[Nanoseconds]]).
return create_temporal_instant ( global_object , zoned_date_time . nanoseconds ( ) ) ;
}
2021-07-11 21:04:11 +03:00
}
// 2. Let string be ? ToString(item).
auto string = item . to_string ( global_object ) ;
if ( vm . exception ( ) )
return { } ;
// 3. Let epochNanoseconds be ? ParseTemporalInstant(string).
auto * epoch_nanoseconds = parse_temporal_instant ( global_object , string ) ;
if ( vm . exception ( ) )
return { } ;
2021-07-19 00:20:34 +01:00
// 4. Return ! CreateTemporalInstant(ℤ (epochNanoseconds)).
2021-07-11 21:04:11 +03:00
return create_temporal_instant ( global_object , * epoch_nanoseconds ) ;
}
// 8.5.4 ParseTemporalInstant ( isoString ), https://tc39.es/proposal-temporal/#sec-temporal-parsetemporalinstant
BigInt * parse_temporal_instant ( GlobalObject & global_object , String const & iso_string )
{
auto & vm = global_object . vm ( ) ;
// 1. Assert: Type(isoString) is String.
// 2. Let result be ? ParseTemporalInstantString(isoString).
auto result = parse_temporal_instant_string ( global_object , iso_string ) ;
if ( vm . exception ( ) )
return { } ;
// 3. Let offsetString be result.[[TimeZoneOffsetString]].
auto & offset_string = result - > time_zone_offset ;
// 4. Assert: offsetString is not undefined.
VERIFY ( offset_string . has_value ( ) ) ;
// 5. Let utc be ? GetEpochFromISOParts(result.[[Year]], result.[[Month]], result.[[Day]], result.[[Hour]], result.[[Minute]], result.[[Second]], result.[[Millisecond]], result.[[Microsecond]], result.[[Nanosecond]]).
auto * utc = get_epoch_from_iso_parts ( global_object , result - > year , result - > month , result - > day , result - > hour , result - > minute , result - > second , result - > millisecond , result - > microsecond , result - > nanosecond ) ;
if ( vm . exception ( ) )
return { } ;
// 6. If utc < − 8.64 × 10^21 or utc > 8.64 × 10^21, then
if ( utc - > big_integer ( ) < INSTANT_NANOSECONDS_MIN | | utc - > big_integer ( ) > INSTANT_NANOSECONDS_MAX ) {
// a. Throw a RangeError exception.
vm . throw_exception < RangeError > ( global_object , ErrorType : : TemporalInvalidEpochNanoseconds ) ;
return { } ;
}
// 7. Let offsetNanoseconds be ? ParseTimeZoneOffsetString(offsetString).
auto offset_nanoseconds = parse_time_zone_offset_string ( global_object , * offset_string ) ;
if ( vm . exception ( ) )
return { } ;
// 8. Return utc − offsetNanoseconds.
2021-08-03 00:14:48 +01:00
return js_bigint ( vm , utc - > big_integer ( ) . minus ( Crypto : : SignedBigInteger : : create_from ( offset_nanoseconds ) ) ) ;
2021-07-11 21:04:11 +03:00
}
2021-08-08 11:04:30 +01:00
// 8.5.5 CompareEpochNanoseconds ( epochNanosecondsOne, epochNanosecondsTwo ), https://tc39.es/proposal-temporal/#sec-temporal-compareepochnanoseconds
2021-07-11 21:18:48 +03:00
i32 compare_epoch_nanoseconds ( BigInt const & epoch_nanoseconds_one , BigInt const & epoch_nanoseconds_two )
{
// 1. If epochNanosecondsOne > epochNanosecondsTwo, return 1.
if ( epoch_nanoseconds_one . big_integer ( ) > epoch_nanoseconds_two . big_integer ( ) )
return 1 ;
// 2. If epochNanosecondsOne < epochNanosecondsTwo, return -1.
if ( epoch_nanoseconds_one . big_integer ( ) < epoch_nanoseconds_two . big_integer ( ) )
return - 1 ;
// 3. Return 0.
return 0 ;
}
2021-08-07 00:31:08 +01:00
// 8.5.6 AddInstant ( epochNanoseconds, hours, minutes, seconds, milliseconds, microseconds, nanoseconds ), https://tc39.es/proposal-temporal/#sec-temporal-addinstant
BigInt * add_instant ( GlobalObject & global_object , BigInt const & epoch_nanoseconds , double hours , double minutes , double seconds , double milliseconds , double microseconds , double nanoseconds )
{
auto & vm = global_object . vm ( ) ;
// 1. Assert: hours, minutes, seconds, milliseconds, microseconds, and nanoseconds are integer Number values.
VERIFY ( hours = = trunc ( hours ) & & minutes = = trunc ( minutes ) & & seconds = = trunc ( seconds ) & & milliseconds = = trunc ( milliseconds ) & & microseconds = = trunc ( microseconds ) & & nanoseconds = = trunc ( nanoseconds ) ) ;
// 2. Let result be epochNanoseconds + ℤ (nanoseconds) + ℤ (microseconds) × 1000ℤ + ℤ (milliseconds) × 10^6ℤ + ℤ (seconds) × 10^9ℤ + ℤ (minutes) × 60ℤ × 10^9ℤ + ℤ (hours) × 3600ℤ × 10^9ℤ .
// FIXME: Pretty sure i64's are not sufficient for the extreme cases.
auto * result = js_bigint ( vm ,
epoch_nanoseconds . big_integer ( )
. plus ( Crypto : : SignedBigInteger : : create_from ( ( i64 ) nanoseconds ) )
. plus ( Crypto : : SignedBigInteger : : create_from ( ( i64 ) microseconds ) . multiplied_by ( Crypto : : SignedBigInteger { 1'000 } ) )
. plus ( Crypto : : SignedBigInteger : : create_from ( ( i64 ) milliseconds ) . multiplied_by ( Crypto : : SignedBigInteger { 1'000'000 } ) )
. plus ( Crypto : : SignedBigInteger : : create_from ( ( i64 ) seconds ) . multiplied_by ( Crypto : : SignedBigInteger { 1'000'000'000 } ) )
. plus ( Crypto : : SignedBigInteger : : create_from ( ( i64 ) minutes ) . multiplied_by ( Crypto : : SignedBigInteger { 60 } ) . multiplied_by ( Crypto : : SignedBigInteger { 1'000'000'000 } ) )
. plus ( Crypto : : SignedBigInteger : : create_from ( ( i64 ) hours ) . multiplied_by ( Crypto : : SignedBigInteger { 3600 } ) . multiplied_by ( Crypto : : SignedBigInteger { 1'000'000'000 } ) ) ) ;
// If ! IsValidEpochNanoseconds(result) is false, throw a RangeError exception.
if ( ! is_valid_epoch_nanoseconds ( * result ) ) {
vm . throw_exception < RangeError > ( global_object , ErrorType : : TemporalInvalidEpochNanoseconds ) ;
return { } ;
}
// 4. Return result.
return result ;
}
2021-09-06 21:21:57 +03:00
// 8.5.7 DifferenceInstant ( ns1, ns2, roundingIncrement, smallestUnit, roundingMode ), https://tc39.es/proposal-temporal/#sec-temporal-differenceinstant
BigInt * difference_instant ( GlobalObject & global_object , BigInt const & nanoseconds1 , BigInt const & nanoseconds2 , u64 rounding_increment , StringView smallest_unit , StringView rounding_mode )
{
auto & vm = global_object . vm ( ) ;
// 1. Assert: Type(ns1) is BigInt.
// 2. Assert: Type(ns2) is BigInt.
// 3. Return ! RoundTemporalInstant(ns2 − ns1, roundingIncrement, smallestUnit, roundingMode).
return round_temporal_instant ( global_object , * js_bigint ( vm , nanoseconds2 . big_integer ( ) . minus ( nanoseconds1 . big_integer ( ) ) ) , rounding_increment , smallest_unit , rounding_mode ) ;
}
2021-07-11 23:49:05 +03:00
// 8.5.8 RoundTemporalInstant ( ns, increment, unit, roundingMode ), https://tc39.es/proposal-temporal/#sec-temporal-roundtemporalinstant
2021-09-06 22:46:55 +03:00
BigInt * round_temporal_instant ( GlobalObject & global_object , BigInt const & nanoseconds , u64 increment , StringView unit , StringView rounding_mode )
2021-07-11 23:49:05 +03:00
{
// 1. Assert: Type(ns) is BigInt.
u64 increment_nanoseconds ;
// 2. If unit is "hour", then
2021-09-06 22:46:55 +03:00
if ( unit = = " hour " sv ) {
2021-07-11 23:49:05 +03:00
// a. Let incrementNs be increment × 3.6 × 10^12.
increment_nanoseconds = increment * 3600000000000 ;
}
// 3. Else if unit is "minute", then
2021-09-06 22:46:55 +03:00
else if ( unit = = " minute " sv ) {
2021-07-11 23:49:05 +03:00
// a. Let incrementNs be increment × 6 × 10^10.
increment_nanoseconds = increment * 60000000000 ;
}
// 4. Else if unit is "second", then
2021-09-06 22:46:55 +03:00
else if ( unit = = " second " sv ) {
2021-07-11 23:49:05 +03:00
// a. Let incrementNs be increment × 10^9.
increment_nanoseconds = increment * 1000000000 ;
}
// 5. Else if unit is "millisecond", then
2021-09-06 22:46:55 +03:00
else if ( unit = = " millisecond " sv ) {
2021-07-11 23:49:05 +03:00
// a. Let incrementNs be increment × 10^6.
increment_nanoseconds = increment * 1000000 ;
}
// 6. Else if unit is "microsecond", then
2021-09-06 22:46:55 +03:00
else if ( unit = = " microsecond " sv ) {
2021-07-11 23:49:05 +03:00
// a. Let incrementNs be increment × 10^3.
increment_nanoseconds = increment * 1000 ;
}
// 7. Else,
else {
2021-07-19 00:20:34 +01:00
// a. Assert: unit is "nanosecond".
2021-09-06 22:46:55 +03:00
VERIFY ( unit = = " nanosecond " sv ) ;
2021-07-19 00:20:34 +01:00
// b. Let incrementNs be increment.
2021-07-11 23:49:05 +03:00
increment_nanoseconds = increment ;
}
// 8. Return ! RoundNumberToIncrement(ℝ (ns), incrementNs, roundingMode).
return round_number_to_increment ( global_object , nanoseconds , increment_nanoseconds , rounding_mode ) ;
}
2021-08-31 00:15:36 +01:00
// 8.5.9 TemporalInstantToString ( instant, timeZone, precision ), https://tc39.es/proposal-temporal/#sec-temporal-temporalinstanttostring
2021-09-08 21:29:20 +01:00
Optional < String > temporal_instant_to_string ( GlobalObject & global_object , Instant & instant , Value time_zone , Variant < StringView , u8 > const & precision )
2021-08-31 00:15:36 +01:00
{
auto & vm = global_object . vm ( ) ;
// 1. Assert: Type(instant) is Object.
// 2. Assert: instant has an [[InitializedTemporalInstant]] internal slot.
// 3. Let outputTimeZone be timeZone.
auto output_time_zone = time_zone ;
// 4. If outputTimeZone is undefined, then
if ( output_time_zone . is_undefined ( ) ) {
// a. Set outputTimeZone to ? CreateTemporalTimeZone("UTC").
output_time_zone = create_temporal_time_zone ( global_object , " UTC " sv ) ;
// TODO: Can this really throw...?
if ( vm . exception ( ) )
return { } ;
}
// 5. Let isoCalendar be ! GetISO8601Calendar().
auto * iso_calendar = get_iso8601_calendar ( global_object ) ;
// 6. Let dateTime be ? BuiltinTimeZoneGetPlainDateTimeFor(outputTimeZone, instant, isoCalendar).
auto * date_time = builtin_time_zone_get_plain_date_time_for ( global_object , output_time_zone , instant , * iso_calendar ) ;
if ( vm . exception ( ) )
return { } ;
// 7. Let dateTimeString be ? TemporalDateTimeToString(dateTime.[[ISOYear]], dateTime.[[ISOMonth]], dateTime.[[ISODay]], dateTime.[[ISOHour]], dateTime.[[ISOMinute]], dateTime.[[ISOSecond]], dateTime.[[ISOMillisecond]], dateTime.[[ISOMicrosecond]], dateTime.[[ISONanosecond]], undefined, precision, "never").
auto date_time_string = temporal_date_time_to_string ( global_object , date_time - > iso_year ( ) , date_time - > iso_month ( ) , date_time - > iso_day ( ) , date_time - > iso_hour ( ) , date_time - > iso_minute ( ) , date_time - > iso_second ( ) , date_time - > iso_millisecond ( ) , date_time - > iso_microsecond ( ) , date_time - > iso_nanosecond ( ) , js_undefined ( ) , precision , " never " sv ) ;
if ( vm . exception ( ) )
return { } ;
Optional < String > time_zone_string ;
// 8. If timeZone is undefined, then
if ( time_zone . is_undefined ( ) ) {
// a. Let timeZoneString be "Z".
time_zone_string = " Z " sv ;
}
// 9. Else,
else {
// a. Let timeZoneString be ? BuiltinTimeZoneGetOffsetStringFor(timeZone, instant).
time_zone_string = builtin_time_zone_get_offset_string_for ( global_object , time_zone , instant ) ;
if ( vm . exception ( ) )
return { } ;
}
// 10. Return the string-concatenation of dateTimeString and timeZoneString.
return String : : formatted ( " {}{} " , * date_time_string , * time_zone_string ) ;
}
2021-07-07 17:41:37 +01:00
}