Compare commits

...

47 commits

Author SHA1 Message Date
Lucien Fiorini
5fe9b1de65
Merge 0ce6cd67df into d6bcd3fb0b 2024-11-20 20:01:47 -05:00
Shannon Booth
d6bcd3fb0b LibWeb: Make CallbackType take a realm instead of settings object
Some checks are pending
CI / Lagom (false, FUZZ, ubuntu-24.04, Linux, Clang) (push) Waiting to run
CI / Lagom (false, NO_FUZZ, macos-15, macOS, Clang) (push) Waiting to run
CI / Lagom (false, NO_FUZZ, ubuntu-24.04, Linux, GNU) (push) Waiting to run
CI / Lagom (true, NO_FUZZ, ubuntu-24.04, Linux, Clang) (push) Waiting to run
Package the js repl as a binary artifact / build-and-package (macos-14, macOS, macOS-universal2) (push) Waiting to run
Package the js repl as a binary artifact / build-and-package (ubuntu-24.04, Linux, Linux-x86_64) (push) Waiting to run
Run test262 and test-wasm / run_and_update_results (push) Waiting to run
Lint Code / lint (push) Waiting to run
Push notes / build (push) Waiting to run
In line with the ShadowRealm proposal changes in the WebIDL spec:
webidl#1437 and supporting changes in HTML spec.

This is required for ShadowRealms as they have no relevant settings
object on the shadow realm, so fixes a crash in the QueueingStrategy
test in this commit.
2024-11-20 18:01:21 -07:00
Shannon Booth
d527c5df5d LibWeb: Allow using queuing strategies on globals other than Window
These interfaces are exposed on *, meaning it should work for workers
and our newly added shadow realm global object by being stored on the
universal global scope mixin.
2024-11-20 18:01:21 -07:00
rmg-x
13f349aea2 LibWeb/Fetch: Implement blob range section of scheme fetch specification 2024-11-21 00:26:58 +00:00
rmg-x
84f673515b LibWeb/XHR: Use normalized header value when validating contents
This was not matching specification and caused valid requests to be
rejected.
2024-11-21 00:26:58 +00:00
rmg-x
4e48298414 LibWeb/Fetch: Implement build_content_range(start, end, full_length) 2024-11-21 00:26:58 +00:00
rmg-x
bf5cf720b5 LibWeb/Fetch: Bring parse_single_range_header_value() up to spec
The previous implementation wasn't using the latest specification steps.
2024-11-21 00:26:58 +00:00
Timothy Flynn
488034477a Revert "LibWeb: Set doctype node immediately while parsing XML document"
This reverts commit cd446e5e9c.

This broke about 20k WPT subtests, all related to XML parsing. See:
https://wpt.fyi/results/html/the-xhtml-syntax/parsing-xhtml-documents?diff=&filter=ADC&run_id=5154815472828416&run_id=5090731742199808
2024-11-20 19:11:56 -05:00
Timothy Flynn
f57ff63432 LibJS: Implement Temporal.Duration.prototype.valueOf 2024-11-20 19:04:30 -05:00
Timothy Flynn
c715711f88 LibJS: Implement Temporal.Duration.prototype.total
Until we have re-implemented Temporal.PlainDate/ZonedDateTime, some of
Temporal.Duration.prototype.total (and its invoked AOs) are left
unimplemented.
2024-11-20 19:04:30 -05:00
Timothy Flynn
5689621c2b LibJS: Implement Temporal.Duration.prototype.round
Until we have re-implemented Temporal.PlainDate/ZonedDateTime, some of
Temporal.Duration.prototype.round (and its invoked AOs) are left
unimplemented.
2024-11-20 19:04:30 -05:00
Timothy Flynn
4742775262 LibJS: Implement stringification Temporal.Duration prototypes 2024-11-20 19:04:30 -05:00
Timothy Flynn
a80523be18 LibJS: Implement mathematical Temporal.Duration prototypes
Includes:
Temporal.Duration.prototype.negated
Temporal.Duration.prototype.abs
Temporal.Duration.prototype.add
Temporal.Duration.prototype.subtract
2024-11-20 19:04:30 -05:00
Timothy Flynn
55c81482b0 LibJS: Implement Temporal.Duration.prototype.with 2024-11-20 19:04:30 -05:00
Timothy Flynn
dfaa3bf649 LibJS: Implement Temporal.Duration.prototype.sign/blank 2024-11-20 19:04:30 -05:00
Timothy Flynn
5fe0d3352d LibJS: Implement the Temporal.Duration constructor
This also includes a stubbed Temporal.Duration.prototype.

Until we have re-implemented Temporal.PlainDate/ZonedDateTime, some of
Temporal.Duration.compare (and its invoked AOs) are left unimplemented.
2024-11-20 19:04:30 -05:00
Timothy Flynn
eca378a7a3 LibJS: Restore some Temporal numeric constants
And add few ad-hoc constants for convenience.
2024-11-20 19:04:30 -05:00
Timothy Flynn
e4e05837e1 LibJS: Return a GC::Ref from Temporal::get_options_object
The Object returned here is always non-null.
2024-11-20 19:04:30 -05:00
Timothy Flynn
c8d2404230 LibJS: Update spec steps for the few remaining Temporal AOs 2024-11-20 19:04:30 -05:00
Timothy Flynn
d368fcadac LibJS: Update spec link for Temporal [ %Symbol.toStringTag% ] 2024-11-20 19:04:30 -05:00
Timothy Flynn
f7517c5b8d LibJS: Remove our existing Temporal implementation
Our Temporal implementation is woefully out of date. The spec has been
so vastly rewritten that it is unfortunately not practical to update our
implementation in-place. Even just removing Temporal objects that were
removed from the spec, or updating any of the simpler remaining objects,
has proven to be a mess in previous attempts.

So, this removes our Temporal implementation. AOs used by other specs
are left intact.
2024-11-20 19:04:30 -05:00
Timothy Flynn
b94307583b LibCrypto: Add user-defined literals to convert numbers to a BigInt
It is much more convenient to define constants with:

    1000_bigint

Than with:

    Crypto::UnsignedBigInteger { 1000 }
2024-11-20 19:04:30 -05:00
Timothy Flynn
e236f1d2ae LibCrypto: Define UnsignedBigInteger::operator<=
We have all comparison operators except less-than-or-equal already.
2024-11-20 19:04:30 -05:00
Ali Mohammad Pur
63a5717bc7 LibDNS: Immediately resolve IPv4/IPv6 "hostnames" if A/AAAA is queried
Some checks are pending
CI / Lagom (false, FUZZ, ubuntu-24.04, Linux, Clang) (push) Waiting to run
CI / Lagom (false, NO_FUZZ, macos-15, macOS, Clang) (push) Waiting to run
CI / Lagom (false, NO_FUZZ, ubuntu-24.04, Linux, GNU) (push) Waiting to run
CI / Lagom (true, NO_FUZZ, ubuntu-24.04, Linux, Clang) (push) Waiting to run
Package the js repl as a binary artifact / build-and-package (macos-14, macOS, macOS-universal2) (push) Waiting to run
Package the js repl as a binary artifact / build-and-package (ubuntu-24.04, Linux, Linux-x86_64) (push) Waiting to run
Run test262 and test-wasm / run_and_update_results (push) Waiting to run
Lint Code / lint (push) Waiting to run
Push notes / build (push) Waiting to run
This makes e.g. lookup(192.168.1.1, A) resolve to the IP instead of
querying DNS for the IP.
2024-11-20 21:37:58 +01:00
Ali Mohammad Pur
c5afe58540 LibDNS: Add a default entry for localhost
In the future, we may want to parse /etc/hosts (or equivalent) into the
cache; this commit only adds localhost to make the normal workflow work.
2024-11-20 21:37:58 +01:00
Ali Mohammad Pur
3bcd91b109 LibDNS: Hide some debug logs behind DNS_DEBUG 2024-11-20 21:37:58 +01:00
Ali Mohammad Pur
7d1291b9f0 RequestServer: Implement the ResolveOnly EnsureConnection level 2024-11-20 21:37:58 +01:00
Ali Mohammad Pur
6911c45bab LibDNS: Respect records' TTL in the resolver cache 2024-11-20 21:37:58 +01:00
Ali Mohammad Pur
879ae94183 LibRequests: Don't crash on requests without a read stream finishing
This can now happen due to the hostname not existing, as RS explicitly
performs DNS resolution before setting up the response pipe.
2024-11-20 21:37:58 +01:00
Ali Mohammad Pur
7e20f4726f LibDNS+LibWeb+Ladybird+RequestServer: Let there be DNS over TLS
This commit adds our own DNS resolver, with the aim of implementing DoT
(and eventually DoH, maybe even DNSSEC etc.)
2024-11-20 21:37:58 +01:00
Ali Mohammad Pur
7f72c28e78 LibHTTP: Make HeaderMap movable and copyable 2024-11-20 21:37:58 +01:00
Ali Mohammad Pur
d704b61066 LibCore+LibTLS: Add an API for connect()'ing 'with hostname
This just unifies the API for all three sockets (UDP, TCP and TLS)
2024-11-20 21:37:58 +01:00
Ali Mohammad Pur
b93d8ef875 AK: Disable implicit conversion from char* -> ipv4 -> ipv6
This is a footgun with some massive bullets.
2024-11-20 21:37:58 +01:00
Pavel Shliak
8a07131229 LibGfx: Clean up #include directives
We actually include what we use where we use it.
This change aims to improve the speed of incremental builds.
2024-11-20 21:13:23 +01:00
Andreas Kling
063cd68bf4 LibWeb: Mark image elements for layout before firing their load event
This removes a long-standing source of flakiness seen for example in
WPT's /referrer-policy/ tests.
2024-11-20 19:04:37 +01:00
Luke Wilde
f638f84185 LibWeb: Make default document readiness be "complete"
This is required by mini Cloudflare invisible challenges, as it will
only run if the readyState is not "loading". If it is "loading", then
it waits for readystatechange to check that it's not "loading" anymore.

Initial about:blank iframes do not go through the full navigation and
thus don't go through HTMLParser::the_end, which sets the ready state
to something other than "loading". Therefore, the challenge would never
run, as readyState would never change.

Seen on https://discord.com/login
2024-11-20 16:20:28 +01:00
Andreas Kling
4203b7823f LibWeb: Fix incorrect exception on replaceChild() with doctypes
We were checking for presence of the wrong child in the parent.
2024-11-20 16:10:57 +01:00
Andreas Kling
cd446e5e9c LibWeb: Set doctype node immediately while parsing XML document
Instead of deferring it to the end of parsing, where scripts that
were expecting to look at the doctype may have already run.
2024-11-20 16:10:57 +01:00
Andreas Kling
ab0dc83d28 LibWeb: Make Node.normalize() ignore CDATASection nodes
We hadn't modeled the "exclusive text node" concept correctly.
2024-11-20 16:10:57 +01:00
Nico Weber
6fc06f45c2 LibWeb: Plumbing for svg stroke-dashoffset 2024-11-20 15:57:37 +01:00
Nicolas Ramz
e98e9b8e81 Documentation: Fix typo in Testing markdown 2024-11-20 15:48:13 +01:00
Gingeh
4b1deb6fe1 LibWeb: Don't skip filtering when CSS contains null or surrogates 2024-11-20 15:47:19 +01:00
Jonne Ransijn
356507284e LibWeb: Compare font keys by reference
Some checks are pending
CI / Lagom (false, FUZZ, ubuntu-24.04, Linux, Clang) (push) Waiting to run
CI / Lagom (false, NO_FUZZ, macos-15, macOS, Clang) (push) Waiting to run
CI / Lagom (false, NO_FUZZ, ubuntu-24.04, Linux, GNU) (push) Waiting to run
CI / Lagom (true, NO_FUZZ, ubuntu-24.04, Linux, Clang) (push) Waiting to run
Package the js repl as a binary artifact / build-and-package (macos-14, macOS, macOS-universal2) (push) Waiting to run
Package the js repl as a binary artifact / build-and-package (ubuntu-24.04, Linux, Linux-x86_64) (push) Waiting to run
Run test262 and test-wasm / run_and_update_results (push) Waiting to run
Lint Code / lint (push) Waiting to run
Push notes / build (push) Waiting to run
`StyleComputer::font_matching_algorithm` was creating a copy of a
`FlyString` every time a `MatchingFontCandidate` was constructed or
copied, causing millions of unnecessairy reference updates when a
lot of fonts are loaded.

While a more permanent solution would be to not load so many unused
fonts, let's do the right thing and remove the unnecessairy copies of
`FlyString`.
2024-11-20 15:38:03 +01:00
Jonne Ransijn
ec5ea0d686 LibGfx: Return family names by reference to avoid unnecessairy cloning 2024-11-20 15:38:03 +01:00
Pavel Shliak
b3c253e50f LibFileSystem: Clean up #include directives
This change aims to improve the speed of incremental builds.
2024-11-20 15:17:53 +01:00
Pavel Shliak
d0c0db5bb3 LibCompress: Clean up #include directives
This change aims to improve the speed of incremental builds.
2024-11-20 15:17:31 +01:00
Timothy Flynn
b99a3ec2df LibWeb: Clone CDATASection nodes with the correct node type
We were cloning these as plain Text nodes, but the clone must also be a
CDATASection node.
2024-11-20 15:15:56 +01:00
509 changed files with 6636 additions and 31224 deletions

View file

@ -9,10 +9,8 @@
#include <AK/BitmapView.h> #include <AK/BitmapView.h>
#include <AK/Error.h> #include <AK/Error.h>
#include <AK/Noncopyable.h> #include <AK/Noncopyable.h>
#include <AK/Optional.h>
#include <AK/Platform.h> #include <AK/Platform.h>
#include <AK/StdLibExtras.h> #include <AK/StdLibExtras.h>
#include <AK/Try.h>
#include <AK/Types.h> #include <AK/Types.h>
#include <AK/kmalloc.h> #include <AK/kmalloc.h>

View file

@ -50,6 +50,10 @@
# cmakedefine01 CSS_TRANSITIONS_DEBUG # cmakedefine01 CSS_TRANSITIONS_DEBUG
#endif #endif
#ifndef DNS_DEBUG
# cmakedefine01 DNS_DEBUG
#endif
#ifndef EDITOR_DEBUG #ifndef EDITOR_DEBUG
# cmakedefine01 EDITOR_DEBUG # cmakedefine01 EDITOR_DEBUG
#endif #endif

View file

@ -29,6 +29,15 @@ public:
m_data[i] = data[i]; m_data[i] = data[i];
} }
constexpr IPv6Address(Array<u8, 16> const& data)
{
for (size_t i = 0; i < 16; i++)
m_data[i] = data[i];
}
template<SameAs<char const*> T>
constexpr IPv6Address(T const&) = delete; // Disable implicit conversion of char const* -> ipv4 -> ipv6
constexpr IPv6Address(IPv4Address const& ipv4_address) constexpr IPv6Address(IPv4Address const& ipv4_address)
{ {
// IPv4 mapped IPv6 address // IPv4 mapped IPv6 address

View file

@ -1,6 +1,6 @@
# Testing Ladybird # Testing Ladybird
Tests are locates in `Tests/`, with a directory for each library. Tests are located in `Tests/`, with a directory for each library.
Every feature or bug fix added to LibWeb should have a corresponding test in `Tests/LibWeb`. Every feature or bug fix added to LibWeb should have a corresponding test in `Tests/LibWeb`.
The test should be either a Text, Layout, Ref, or Screenshot test depending on the feature. The test should be either a Text, Layout, Ref, or Screenshot test depending on the feature.

View file

@ -9,8 +9,6 @@
#include <AK/Assertions.h> #include <AK/Assertions.h>
#include <AK/BinarySearch.h> #include <AK/BinarySearch.h>
#include <AK/MemoryStream.h> #include <AK/MemoryStream.h>
#include <string.h>
#include <LibCompress/Deflate.h> #include <LibCompress/Deflate.h>
#include <LibCompress/Huffman.h> #include <LibCompress/Huffman.h>

View file

@ -11,9 +11,6 @@
#include <AK/MemoryStream.h> #include <AK/MemoryStream.h>
#include <AK/String.h> #include <AK/String.h>
#include <LibCore/DateTime.h> #include <LibCore/DateTime.h>
#include <LibCore/File.h>
#include <LibCore/MappedFile.h>
#include <LibCore/System.h>
namespace Compress { namespace Compress {

View file

@ -159,6 +159,7 @@ class TCPSocket final : public Socket {
public: public:
static ErrorOr<NonnullOwnPtr<TCPSocket>> connect(ByteString const& host, u16 port); static ErrorOr<NonnullOwnPtr<TCPSocket>> connect(ByteString const& host, u16 port);
static ErrorOr<NonnullOwnPtr<TCPSocket>> connect(SocketAddress const& address); static ErrorOr<NonnullOwnPtr<TCPSocket>> connect(SocketAddress const& address);
static ErrorOr<NonnullOwnPtr<TCPSocket>> connect(SocketAddress const& address, ByteString const&) { return connect(address); }
static ErrorOr<NonnullOwnPtr<TCPSocket>> adopt_fd(int fd); static ErrorOr<NonnullOwnPtr<TCPSocket>> adopt_fd(int fd);
TCPSocket(TCPSocket&& other) TCPSocket(TCPSocket&& other)
@ -220,6 +221,7 @@ class UDPSocket final : public Socket {
public: public:
static ErrorOr<NonnullOwnPtr<UDPSocket>> connect(ByteString const& host, u16 port, Optional<AK::Duration> timeout = {}); static ErrorOr<NonnullOwnPtr<UDPSocket>> connect(ByteString const& host, u16 port, Optional<AK::Duration> timeout = {});
static ErrorOr<NonnullOwnPtr<UDPSocket>> connect(SocketAddress const& address, Optional<AK::Duration> timeout = {}); static ErrorOr<NonnullOwnPtr<UDPSocket>> connect(SocketAddress const& address, Optional<AK::Duration> timeout = {});
static ErrorOr<NonnullOwnPtr<UDPSocket>> connect(SocketAddress const& address, ByteString const&, Optional<AK::Duration> timeout = {}) { return connect(address, timeout); }
UDPSocket(UDPSocket&& other) UDPSocket(UDPSocket&& other)
: Socket(static_cast<Socket&&>(other)) : Socket(static_cast<Socket&&>(other))

View file

@ -601,6 +601,11 @@ bool UnsignedBigInteger::operator<(UnsignedBigInteger const& other) const
return false; return false;
} }
bool UnsignedBigInteger::operator<=(UnsignedBigInteger const& other) const
{
return *this < other || *this == other;
}
bool UnsignedBigInteger::operator>(UnsignedBigInteger const& other) const bool UnsignedBigInteger::operator>(UnsignedBigInteger const& other) const
{ {
return *this != other && !(*this < other); return *this != other && !(*this < other);

View file

@ -128,6 +128,7 @@ public:
[[nodiscard]] bool operator==(UnsignedBigInteger const& other) const; [[nodiscard]] bool operator==(UnsignedBigInteger const& other) const;
[[nodiscard]] bool operator!=(UnsignedBigInteger const& other) const; [[nodiscard]] bool operator!=(UnsignedBigInteger const& other) const;
[[nodiscard]] bool operator<(UnsignedBigInteger const& other) const; [[nodiscard]] bool operator<(UnsignedBigInteger const& other) const;
[[nodiscard]] bool operator<=(UnsignedBigInteger const& other) const;
[[nodiscard]] bool operator>(UnsignedBigInteger const& other) const; [[nodiscard]] bool operator>(UnsignedBigInteger const& other) const;
[[nodiscard]] bool operator>=(UnsignedBigInteger const& other) const; [[nodiscard]] bool operator>=(UnsignedBigInteger const& other) const;
@ -171,8 +172,26 @@ struct AK::Formatter<Crypto::UnsignedBigInteger> : Formatter<StringView> {
ErrorOr<void> format(FormatBuilder&, Crypto::UnsignedBigInteger const&); ErrorOr<void> format(FormatBuilder&, Crypto::UnsignedBigInteger const&);
}; };
inline Crypto::UnsignedBigInteger inline Crypto::UnsignedBigInteger operator""_bigint(char const* string, size_t length)
operator""_bigint(char const* string, size_t length)
{ {
return MUST(Crypto::UnsignedBigInteger::from_base(10, { string, length })); return MUST(Crypto::UnsignedBigInteger::from_base(10, { string, length }));
} }
inline Crypto::UnsignedBigInteger operator""_bigint(unsigned long long value)
{
auto result = Crypto::UnsignedBigInteger { static_cast<u64>(value) };
VERIFY(!result.is_invalid());
return result;
}
inline Crypto::UnsignedBigInteger operator""_bigint(long double value)
{
VERIFY(value >= 0);
VERIFY(value < static_cast<long double>(NumericLimits<double>::max()));
auto result = Crypto::UnsignedBigInteger { static_cast<double>(value) };
VERIFY(!result.is_invalid());
return result;
}

View file

@ -0,0 +1,6 @@
set(SOURCES
Message.cpp
)
serenity_lib(LibDNS dns)
target_link_libraries(LibDNS PRIVATE LibCore)

1177
Libraries/LibDNS/Message.cpp Normal file

File diff suppressed because it is too large Load diff

704
Libraries/LibDNS/Message.h Normal file
View file

@ -0,0 +1,704 @@
/*
* Copyright (c) 2024, Ali Mohammad Pur <mpfard@serenityos.org>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#pragma once
#include <AK/Base64.h>
#include <AK/MaybeOwned.h>
#include <AK/RedBlackTree.h>
#include <AK/Time.h>
#include <LibCore/Promise.h>
#include <LibCore/SocketAddress.h>
#include <LibURL/URL.h>
namespace DNS {
namespace Messages {
struct DomainName;
struct ParseContext {
CountingStream& stream;
NonnullOwnPtr<RedBlackTree<u16, DomainName>> pointers;
};
enum class OpCode : u8;
struct Options {
// 1 1 1 1 1 1
// 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5
// +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
// | ID |
// +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
// |QR| Opcode |AA|TC|RD|RA| Z |AD|CD| RCODE |
constexpr inline static u16 QRMask = 0b1000000000000000;
constexpr inline static u16 OpCodeMask = 0b0111100000000000;
constexpr inline static u16 AuthoritativeAnswerMask = 0b0000010000000000;
constexpr inline static u16 TruncatedMask = 0b0000001000000000;
constexpr inline static u16 RecursionDesiredMask = 0b0000000100000000;
constexpr inline static u16 RecursionAvailableMask = 0b0000000010000000;
constexpr inline static u16 AuthenticatedDataMask = 0b0000000000100000;
constexpr inline static u16 CheckingDisabledMask = 0b0000000000010000;
constexpr inline static u16 ResponseCodeMask = 0b0000000000001111;
enum class ResponseCode : u16 {
NoError = 0,
FormatError = 1,
ServerFailure = 2,
NameError = 3,
NotImplemented = 4,
Refused = 5,
};
void set_is_question(bool value) { raw = (raw & ~QRMask) | (value ? QRMask : 0); }
void set_is_authoritative_answer(bool value) { raw = (raw & ~AuthoritativeAnswerMask) | (value ? AuthoritativeAnswerMask : 0); }
void set_is_truncated(bool value) { raw = (raw & ~TruncatedMask) | (value ? TruncatedMask : 0); }
void set_recursion_desired(bool value) { raw = (raw & ~RecursionDesiredMask) | (value ? RecursionDesiredMask : 0); }
void set_recursion_available(bool value) { raw = (raw & ~RecursionAvailableMask) | (value ? RecursionAvailableMask : 0); }
void set_response_code(ResponseCode code) { raw = (raw & ~ResponseCodeMask) | static_cast<u16>(code); }
void set_checking_disabled(bool value) { raw = (raw & ~CheckingDisabledMask) | (value ? CheckingDisabledMask : 0); }
void set_authenticated_data(bool value) { raw = (raw & ~AuthenticatedDataMask) | (value ? AuthenticatedDataMask : 0); }
void set_op_code(OpCode code) { raw = (raw & ~OpCodeMask) | (static_cast<u16>(code) << 11); }
bool is_question() const { return (raw & QRMask) == 0; }
bool is_authoritative_answer() const { return (raw & AuthoritativeAnswerMask) != 0; }
bool is_truncated() const { return (raw & TruncatedMask) != 0; }
bool recursion_desired() const { return (raw & RecursionDesiredMask) != 0; }
bool recursion_available() const { return (raw & RecursionAvailableMask) != 0; }
bool checking_disabled() const { return (raw & CheckingDisabledMask) != 0; }
bool authenticated_data() const { return (raw & AuthenticatedDataMask) != 0; }
ResponseCode response_code() const { return static_cast<ResponseCode>(raw & ResponseCodeMask); }
OpCode op_code() const { return static_cast<OpCode>((raw & OpCodeMask) >> 11); }
String to_string() const;
NetworkOrdered<u16> raw { 0 };
};
StringView to_string(Options::ResponseCode);
struct Header {
NetworkOrdered<u16> id;
Options options;
NetworkOrdered<u16> question_count;
NetworkOrdered<u16> answer_count;
NetworkOrdered<u16> authority_count;
NetworkOrdered<u16> additional_count;
};
struct DomainName {
Vector<ByteString> labels;
static DomainName from_string(StringView);
static ErrorOr<DomainName> from_raw(ParseContext&);
ErrorOr<void> to_raw(ByteBuffer&) const;
String to_string() const;
};
// Listing from IANA https://www.iana.org/assignments/dns-parameters/dns-parameters.xhtml#dns-parameters-4.
enum class ResourceType : u16 {
Reserved = 0, // [RFC6895]
A = 1, // a host address [RFC1035]
NS = 2, // an authoritative name server [RFC1035]
MD = 3, // a mail destination (OBSOLETE - use MX) [RFC1035]
MF = 4, // a mail forwarder (OBSOLETE - use MX) [RFC1035]
CNAME = 5, // the canonical name for an alias [RFC1035]
SOA = 6, // marks the start of a zone of authority [RFC1035]
MB = 7, // a mailbox domain name (EXPERIMENTAL) [RFC1035]
MG = 8, // a mail group member (EXPERIMENTAL) [RFC1035]
MR = 9, // a mail rename domain name (EXPERIMENTAL) [RFC1035]
NULL_ = 10, // a null RR (EXPERIMENTAL) [RFC1035]
WKS = 11, // a well known service description [RFC1035]
PTR = 12, // a domain name pointer [RFC1035]
HINFO = 13, // host information [RFC1035]
MINFO = 14, // mailbox or mail list information [RFC1035]
MX = 15, // mail exchange [RFC1035]
TXT = 16, // text strings [RFC1035]
RP = 17, // for Responsible Person [RFC1183]
AFSDB = 18, // for AFS Data Base location [RFC1183][RFC5864]
X25 = 19, // for X.25 PSDN address [RFC1183]
ISDN = 20, // for ISDN address [RFC1183]
RT = 21, // for Route Through [RFC1183]
NSAP = 22, // for NSAP address, NSAP style A record (DEPRECATED) [RFC1706][Moving TPC.INT and NSAP.INT infrastructure domains to historic]
NSAP_PTR = 23, // for domain name pointer, NSAP style (DEPRECATED) [RFC1706][Moving TPC.INT and NSAP.INT infrastructure domains to historic]
SIG = 24, // for security signature [RFC2536][RFC2931][RFC3110][RFC4034]
KEY = 25, // for security key [RFC2536][RFC2539][RFC3110][RFC4034]
PX = 26, // X.400 mail mapping information [RFC2163]
GPOS = 27, // Geographical Position [RFC1712]
AAAA = 28, // IP6 Address [RFC3596]
LOC = 29, // Location Information [RFC1876]
NXT = 30, // Next Domain (OBSOLETE) [RFC2535][RFC3755]
EID = 31, // Endpoint Identifier [Michael_Patton][http://ana-3.lcs.mit.edu/~jnc/nimrod/dns.txt]
NIMLOC = 32, // Nimrod Locator [1][Michael_Patton][http://ana-3.lcs.mit.edu/~jnc/nimrod/dns.txt]
SRV = 33, // Server Selection [1][RFC2782]
ATMA = 34, // ATM Address "[ ATM Forum Technical Committee, "ATM Name System, V2.0", Doc ID: AF-DANS-0152.000, July 2000. Available from and held in escrow by IANA.]"
NAPTR = 35, // Naming Authority Pointer [RFC3403]
KX = 36, // Key Exchanger [RFC2230]
CERT = 37, // CERT [RFC4398]
A6 = 38, // A6 (OBSOLETE - use AAAA) [RFC2874][RFC3226][RFC6563]
DNAME = 39, // DNAME [RFC6672]
SINK = 40, // SINK [Donald_E_Eastlake][draft-eastlake-kitchen-sink]
OPT = 41, // OPT [RFC3225][RFC6891]
APL = 42, // APL [RFC3123]
DS = 43, // Delegation Signer [RFC4034]
SSHFP = 44, // SSH Key Fingerprint [RFC4255]
IPSECKEY = 45, // IPSECKEY [RFC4025]
RRSIG = 46, // RRSIG [RFC4034]
NSEC = 47, // NSEC [RFC4034][RFC9077]
DNSKEY = 48, // DNSKEY [RFC4034]
DHCID = 49, // DHCID [RFC4701]
NSEC3 = 50, // NSEC3 [RFC5155][RFC9077]
NSEC3PARAM = 51, // NSEC3PARAM [RFC5155]
TLSA = 52, // TLSA [RFC6698]
SMIMEA = 53, // S/MIME cert association [RFC8162]
HIP = 55, // Host Identity Protocol [RFC8005]
NINFO = 56, // NINFO [Jim_Reid]
RKEY = 57, // RKEY [Jim_Reid]
TALINK = 58, // Trust Anchor LINK [Wouter_Wijngaards]
CDS = 59, // Child DS [RFC7344]
CDNSKEY = 60, // DNSKEY(s) the Child wants reflected in DS [RFC7344]
OPENPGPKEY = 61, // OpenPGP Key [RFC7929]
CSYNC = 62, // Child-To-Parent Synchronization [RFC7477]
ZONEMD = 63, // Message Digest Over Zone Data [RFC8976]
SVCB = 64, // General-purpose service binding [RFC9460]
HTTPS = 65, // SVCB-compatible type for use with HTTP [RFC9460]
SPF = 99, // [RFC7208]
UINFO = 100, // [IANA-Reserved]
UID = 101, // [IANA-Reserved]
GID = 102, // [IANA-Reserved]
UNSPEC = 103, // [IANA-Reserved]
NID = 104, // [RFC6742]
L32 = 105, // [RFC6742]
L64 = 106, // [RFC6742]
LP = 107, // [RFC6742]
EUI48 = 108, // an EUI-48 address [RFC7043]
EUI64 = 109, // an EUI-64 address [RFC7043]
NXNAME = 128, // NXDOMAIN indicator for Compact Denial of Existence [draft-ietf-dnsop-compact-denial-of-existence-04]
TKEY = 249, // Transaction Key [RFC2930]
TSIG = 250, // Transaction Signature [RFC8945]
IXFR = 251, // incremental transfer [RFC1995]
AXFR = 252, // transfer of an entire zone [RFC1035][RFC5936]
MAILB = 253, // mailbox-related RRs (MB, MG or MR) [RFC1035]
MAILA = 254, // mail agent RRs (OBSOLETE - see MX) [RFC1035]
ANY = 255, // A request for some or all records the server has available [RFC1035][RFC6895][RFC8482]
URI = 256, // URI [RFC7553]
CAA = 257, // Certification Authority Restriction [RFC8659]
AVC = 258, // Application Visibility and Control [Wolfgang_Riedel]
DOA = 259, // Digital Object Architecture [draft-durand-doa-over-dns]
AMTRELAY = 260, // Automatic Multicast Tunneling Relay [RFC8777]
RESINFO = 261, // Resolver Information as Key/Value Pairs [RFC9606]
WALLET = 262, // Public wallet address [Paul_Hoffman]
CLA = 263, // BP Convergence Layer Adapter [draft-johnson-dns-ipn-cla-07]
IPN = 264, // BP Node Number [draft-johnson-dns-ipn-cla-07]
TA = 32768, // DNSSEC Trust Authorities "[Sam_Weiler][Deploying DNSSEC Without a Signed Root. Technical Report 1999-19, Information Networking Institute, Carnegie Mellon University, April 2004.]"
DLV = 32769, // DNSSEC Lookaside Validation (OBSOLETE) [RFC8749][RFC4431]
};
StringView to_string(ResourceType);
Optional<ResourceType> resource_type_from_string(StringView);
// Listing from IANA https://www.iana.org/assignments/dns-parameters/dns-parameters.xhtml#dns-parameters-2.
enum class Class : u16 {
IN = 1, // the Internet [RFC1035]
CH = 3, // the CHAOS class [Moon1981]
HS = 4, // Hesiod [Dyer1987]
};
StringView to_string(Class);
// Listing from IANA https://www.iana.org/assignments/dns-parameters/dns-parameters.xhtml#dns-parameters-3.
enum class OpCode : u8 {
Query = 0, // a standard query (QUERY)
IQuery = 1, // an inverse query (IQUERY)
Status = 2, // a server status request (STATUS)
Notify = 4, // NOTIFY
Update = 5, // dynamic update (RFC 2136)
DSO = 6, // DNS Stateful Operations (DSO) [RFC8490]
Reserved = 7, // [RFC6895]
ReservedMask = 15 // [RFC6895]
};
StringView to_string(OpCode);
namespace TLSA {
// Listings from IANA https://www.iana.org/assignments/dane-parameters/dane-parameters.xhtml.
enum class CertUsage : u8 {
CAConstraint = 0,
ServiceCertificateConstraint = 1,
TrustAnchorAssertion = 2,
DomainIssuedCertificate = 3,
Private = 255
};
enum class Selector : u8 {
FullCertificate = 0,
SubjectPublicKeyInfo = 1,
Private = 255
};
enum class MatchingType : u8 {
Full = 0,
SHA256 = 1,
SHA512 = 2,
Private = 255
};
}
namespace DNSSEC {
// Listing from IANA https://www.iana.org/assignments/dns-sec-alg-numbers/dns-sec-alg-numbers.xhtml.
enum class Algorithm : u8 {
RSAMD5 = 1, // RSA/MD5 [RFC4034][RFC3110]
DSA = 3, // DSA/SHA-1 [RFC3755][RFC2536]
RSASHA1 = 5, // RSA/SHA-1 [RFC3110]
RSASHA1NSEC3SHA1 = 7, // [RFC5155]
RSASHA256 = 8, // RSA/SHA-256 [RFC5702]
RSASHA512 = 10, // RSA/SHA-512 [RFC5702]
ECDSAP256SHA256 = 13, // ECDSA Curve P-256 with SHA-256 [RFC6605]
ECDSAP384SHA384 = 14, // ECDSA Curve P-384 with SHA-384 [RFC6605]
ED25519 = 15, // Ed25519 [RFC8080]
Unknown = 255 // Reserved for Private Use
};
static inline StringView to_string(Algorithm algorithm)
{
switch (algorithm) {
case Algorithm::RSAMD5:
return "RSAMD5"sv;
case Algorithm::DSA:
return "DSA"sv;
case Algorithm::RSASHA1:
return "RSASHA1"sv;
case Algorithm::RSASHA1NSEC3SHA1:
return "RSASHA1NSEC3SHA1"sv;
case Algorithm::RSASHA256:
return "RSASHA256"sv;
case Algorithm::RSASHA512:
return "RSASHA512"sv;
case Algorithm::ECDSAP256SHA256:
return "ECDSAP256SHA256"sv;
case Algorithm::ECDSAP384SHA384:
return "ECDSAP384SHA384"sv;
case Algorithm::ED25519:
return "ED25519"sv;
case Algorithm::Unknown:
return "Unknown"sv;
}
VERIFY_NOT_REACHED();
}
// Listing from IANA https://www.iana.org/assignments/dns-sec-alg-numbers/dns-sec-alg-numbers.xhtml.
enum class DigestType : u8 {
SHA1 = 1, // SHA-1 [RFC3658]
SHA256 = 2, // SHA-256 [RFC4509]
GOST3411 = 3, // GOST R 34.11-94 [RFC5933]
SHA384 = 4, // SHA-384 [RFC6605]
SHA512 = 5, // SHA-512 [RFC6605]
SHA224 = 6, // SHA-224 [RFC6605]
Unknown = 255 // Reserved for Private Use
};
static inline StringView to_string(DigestType digest_type)
{
switch (digest_type) {
case DigestType::SHA1:
return "SHA1"sv;
case DigestType::SHA256:
return "SHA256"sv;
case DigestType::GOST3411:
return "GOST3411"sv;
case DigestType::SHA384:
return "SHA384"sv;
case DigestType::SHA512:
return "SHA512"sv;
case DigestType::SHA224:
return "SHA224"sv;
case DigestType::Unknown:
return "Unknown"sv;
}
VERIFY_NOT_REACHED();
}
// Listing from IANA https://www.iana.org/assignments/dns-sec-alg-numbers/dns-sec-alg-numbers.xhtml.
enum class NSEC3HashAlgorithm : u8 {
SHA1 = 1, // [RFC5155]
SHA256 = 2, // [RFC6605]
GOST3411 = 3, // [RFC5933]
SHA384 = 4, // [RFC6605]
SHA512 = 5, // [RFC6605]
SHA224 = 6, // [RFC6605]
Unknown = 255 // Reserved for Private Use
};
static inline StringView to_string(NSEC3HashAlgorithm hash_algorithm)
{
switch (hash_algorithm) {
case NSEC3HashAlgorithm::SHA1:
return "SHA1"sv;
case NSEC3HashAlgorithm::SHA256:
return "SHA256"sv;
case NSEC3HashAlgorithm::GOST3411:
return "GOST3411"sv;
case NSEC3HashAlgorithm::SHA384:
return "SHA384"sv;
case NSEC3HashAlgorithm::SHA512:
return "SHA512"sv;
case NSEC3HashAlgorithm::SHA224:
return "SHA224"sv;
case NSEC3HashAlgorithm::Unknown:
return "Unknown"sv;
}
VERIFY_NOT_REACHED();
}
}
struct Question {
DomainName name;
ResourceType type;
Class class_;
static ErrorOr<Question> from_raw(ParseContext&);
ErrorOr<void> to_raw(ByteBuffer&) const;
};
namespace Records {
struct A {
IPv4Address address;
static constexpr ResourceType type = ResourceType::A;
static ErrorOr<A> from_raw(ParseContext&);
ErrorOr<void> to_raw(ByteBuffer&) const { return Error::from_string_literal("Not implemented"); }
ErrorOr<String> to_string() const { return address.to_string(); }
};
struct AAAA {
IPv6Address address;
static constexpr ResourceType type = ResourceType::AAAA;
static ErrorOr<AAAA> from_raw(ParseContext&);
ErrorOr<void> to_raw(ByteBuffer&) const { return Error::from_string_literal("Not implemented"); }
ErrorOr<String> to_string() const { return address.to_string(); }
};
struct TXT {
ByteString content;
static constexpr ResourceType type = ResourceType::TXT;
static ErrorOr<TXT> from_raw(ParseContext&);
ErrorOr<void> to_raw(ByteBuffer&) const { return Error::from_string_literal("Not implemented"); }
ErrorOr<String> to_string() const { return String::formatted("Text: '{}'", StringView { content }); }
};
struct CNAME {
DomainName names;
static constexpr ResourceType type = ResourceType::CNAME;
static ErrorOr<CNAME> from_raw(ParseContext&);
ErrorOr<void> to_raw(ByteBuffer&) const { return Error::from_string_literal("Not implemented"); }
ErrorOr<String> to_string() const { return names.to_string(); }
};
struct NS {
DomainName name;
static constexpr ResourceType type = ResourceType::NS;
static ErrorOr<NS> from_raw(ParseContext&);
ErrorOr<void> to_raw(ByteBuffer&) const { return Error::from_string_literal("Not implemented"); }
ErrorOr<String> to_string() const { return name.to_string(); }
};
struct SOA {
DomainName mname;
DomainName rname;
u32 serial;
u32 refresh;
u32 retry;
u32 expire;
u32 minimum;
static constexpr ResourceType type = ResourceType::SOA;
static ErrorOr<SOA> from_raw(ParseContext&);
ErrorOr<void> to_raw(ByteBuffer&) const { return Error::from_string_literal("Not implemented"); }
ErrorOr<String> to_string() const
{
return String::formatted("SOA MName: '{}', RName: '{}', Serial: {}, Refresh: {}, Retry: {}, Expire: {}, Minimum: {}", mname.to_string(), rname.to_string(), serial, refresh, retry, expire, minimum);
}
};
struct MX {
u16 preference;
DomainName exchange;
static constexpr ResourceType type = ResourceType::MX;
static ErrorOr<MX> from_raw(ParseContext&);
ErrorOr<void> to_raw(ByteBuffer&) const { return Error::from_string_literal("Not implemented"); }
ErrorOr<String> to_string() const { return String::formatted("MX Preference: {}, Exchange: '{}'", preference, exchange.to_string()); }
};
struct PTR {
DomainName name;
static constexpr ResourceType type = ResourceType::PTR;
static ErrorOr<PTR> from_raw(ParseContext&);
ErrorOr<void> to_raw(ByteBuffer&) const { return Error::from_string_literal("Not implemented"); }
ErrorOr<String> to_string() const { return name.to_string(); }
};
struct SRV {
u16 priority;
u16 weight;
u16 port;
DomainName target;
static constexpr ResourceType type = ResourceType::SRV;
static ErrorOr<SRV> from_raw(ParseContext&);
ErrorOr<void> to_raw(ByteBuffer&) const { return Error::from_string_literal("Not implemented"); }
ErrorOr<String> to_string() const { return String::formatted("SRV Priority: {}, Weight: {}, Port: {}, Target: '{}'", priority, weight, port, target.to_string()); }
};
struct DNSKEY {
u16 flags;
u8 protocol;
DNSSEC::Algorithm algorithm;
ByteBuffer public_key;
constexpr static inline u16 FlagSecureEntryPoint = 0b1000000000000000;
constexpr static inline u16 FlagZoneKey = 0b0100000000000000;
constexpr static inline u16 FlagRevoked = 0b0010000000000000;
constexpr bool is_secure_entry_point() const { return flags & FlagSecureEntryPoint; }
constexpr bool is_zone_key() const { return flags & FlagZoneKey; }
constexpr bool is_revoked() const { return flags & FlagRevoked; }
constexpr bool is_key_signing_key() const { return is_secure_entry_point() && is_zone_key() && !is_revoked(); }
static constexpr ResourceType type = ResourceType::DNSKEY;
static ErrorOr<DNSKEY> from_raw(ParseContext&);
ErrorOr<void> to_raw(ByteBuffer&) const { return Error::from_string_literal("Not implemented"); }
ErrorOr<String> to_string() const
{
return String::formatted("DNSKEY Flags: {}{}{}{}({}), Protocol: {}, Algorithm: {}, Public Key: {}",
is_secure_entry_point() ? "sep "sv : ""sv,
is_zone_key() ? "zone "sv : ""sv,
is_revoked() ? "revoked "sv : ""sv,
is_key_signing_key() ? "ksk "sv : ""sv,
flags,
protocol,
DNSSEC::to_string(algorithm),
TRY(encode_base64(public_key)));
}
};
struct CDNSKEY : public DNSKEY {
template<typename... Ts>
CDNSKEY(Ts&&... args)
: DNSKEY(forward<Ts>(args)...)
{
}
static constexpr ResourceType type = ResourceType::CDNSKEY;
static ErrorOr<CDNSKEY> from_raw(ParseContext& raw) { return DNSKEY::from_raw(raw); }
};
struct DS {
u16 key_tag;
DNSSEC::Algorithm algorithm;
DNSSEC::DigestType digest_type;
ByteBuffer digest;
static constexpr ResourceType type = ResourceType::DS;
static ErrorOr<DS> from_raw(ParseContext&);
ErrorOr<void> to_raw(ByteBuffer&) const { return Error::from_string_literal("Not implemented"); }
ErrorOr<String> to_string() const { return "DS"_string; }
};
struct CDS : public DS {
template<typename... Ts>
CDS(Ts&&... args)
: DS(forward<Ts>(args)...)
{
}
static constexpr ResourceType type = ResourceType::CDS;
static ErrorOr<CDS> from_raw(ParseContext& raw) { return DS::from_raw(raw); }
};
struct SIG {
ResourceType type_covered;
DNSSEC::Algorithm algorithm;
u8 label_count;
u32 original_ttl;
UnixDateTime expiration;
UnixDateTime inception;
u16 key_tag;
DomainName signers_name;
ByteBuffer signature;
static constexpr ResourceType type = ResourceType::SIG;
static ErrorOr<SIG> from_raw(ParseContext&);
ErrorOr<void> to_raw(ByteBuffer&) const { return Error::from_string_literal("Not implemented"); }
ErrorOr<String> to_string() const;
};
struct RRSIG : public SIG {
template<typename... Ts>
RRSIG(Ts&&... args)
: SIG(forward<Ts>(args)...)
{
}
static constexpr ResourceType type = ResourceType::RRSIG;
static ErrorOr<RRSIG> from_raw(ParseContext& raw) { return SIG::from_raw(raw); }
};
struct NSEC {
DomainName next_domain_name;
Vector<ResourceType> types;
static constexpr ResourceType type = ResourceType::NSEC;
static ErrorOr<NSEC> from_raw(ParseContext&);
ErrorOr<void> to_raw(ByteBuffer&) const { return Error::from_string_literal("Not implemented"); }
ErrorOr<String> to_string() const { return "NSEC"_string; }
};
struct NSEC3 {
DNSSEC::NSEC3HashAlgorithm hash_algorithm;
u8 flags;
u16 iterations;
ByteBuffer salt;
DomainName next_hashed_owner_name;
Vector<ResourceType> types;
static constexpr ResourceType type = ResourceType::NSEC3;
static ErrorOr<NSEC3> from_raw(ParseContext&);
ErrorOr<void> to_raw(ByteBuffer&) const { return Error::from_string_literal("Not implemented"); }
ErrorOr<String> to_string() const { return "NSEC3"_string; }
};
struct NSEC3PARAM {
DNSSEC::NSEC3HashAlgorithm hash_algorithm;
u8 flags;
u16 iterations;
ByteBuffer salt;
constexpr static inline u8 FlagOptOut = 0b10000000;
constexpr bool is_opt_out() const { return flags & FlagOptOut; }
static constexpr ResourceType type = ResourceType::NSEC3PARAM;
static ErrorOr<NSEC3PARAM> from_raw(ParseContext&);
ErrorOr<void> to_raw(ByteBuffer&) const { return Error::from_string_literal("Not implemented"); }
ErrorOr<String> to_string() const { return "NSEC3PARAM"_string; }
};
struct TLSA {
Messages::TLSA::CertUsage cert_usage;
Messages::TLSA::Selector selector;
Messages::TLSA::MatchingType matching_type;
ByteBuffer certificate_association_data;
static ErrorOr<TLSA> from_raw(ParseContext&);
ErrorOr<void> to_raw(ByteBuffer&) const { return Error::from_string_literal("Not implemented"); }
ErrorOr<String> to_string() const { return "TLSA"_string; }
};
struct HINFO {
ByteString cpu;
ByteString os;
static constexpr ResourceType type = ResourceType::HINFO;
static ErrorOr<HINFO> from_raw(ParseContext&);
ErrorOr<void> to_raw(ByteBuffer&) const { return Error::from_string_literal("Not implemented"); }
ErrorOr<String> to_string() const { return String::formatted("HINFO CPU: '{}', OS: '{}'", StringView { cpu }, StringView { os }); }
};
struct OPT {
struct Option {
u16 code;
ByteBuffer data;
};
// 1 1 1 1 1 1
// 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5
// +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
// | UDP Payload Size |
// +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
// | Extended RCode | VER | ZZ |
// +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
// |DO| Z |
// +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
// | OPT-LEN / OPT-DATA...
NetworkOrdered<u16> udp_payload_size { 0 };
NetworkOrdered<u32> extended_rcode_and_flags { 0 };
Vector<Option> options;
static constexpr u32 MaskExtendedRCode = 0b11111111000000000000000000000000;
static constexpr u32 MaskVersion = 0b00000000111100000000000000000000;
static constexpr u32 MaskDO = 0b00000000000000001000000000000000;
static constexpr ResourceType type = ResourceType::OPT;
constexpr u8 extended_rcode() const { return (extended_rcode_and_flags & MaskExtendedRCode) >> 24; }
constexpr u8 version() const { return (extended_rcode_and_flags & MaskVersion) >> 20; }
constexpr bool dnssec_ok() const { return extended_rcode_and_flags & MaskDO; }
void set_extended_rcode(u8 value) { extended_rcode_and_flags = (extended_rcode_and_flags & ~MaskExtendedRCode) | (value << 24); }
void set_version(u8 value) { extended_rcode_and_flags = (extended_rcode_and_flags & ~MaskVersion) | (value << 20); }
void set_dnssec_ok(bool value) { extended_rcode_and_flags = (extended_rcode_and_flags & ~MaskDO) | (value ? MaskDO : 0); }
static ErrorOr<OPT> from_raw(ParseContext&);
ErrorOr<void> to_raw(ByteBuffer&) const;
ErrorOr<String> to_string() const
{
StringBuilder builder;
builder.appendff("OPT UDP Payload Size: {}, Extended RCode: {}, Version: {}, DNSSEC OK: {}", udp_payload_size, extended_rcode(), version(), dnssec_ok());
for (auto& option : options)
builder.appendff(", opt[{} = '{:hex-dump}']", option.code, option.data.bytes());
return builder.to_string();
}
};
}
using Record = Variant<
Records::A,
Records::AAAA,
Records::TXT,
Records::CNAME,
Records::NS,
Records::SOA,
Records::MX,
Records::PTR,
Records::SRV,
Records::DNSKEY,
Records::CDNSKEY,
Records::DS,
Records::CDS,
Records::RRSIG,
Records::NSEC,
Records::NSEC3,
Records::NSEC3PARAM,
Records::TLSA,
Records::HINFO,
Records::OPT,
// TODO: Add more records.
ByteBuffer>; // Fallback for unknown records.
struct ResourceRecord {
DomainName name;
ResourceType type;
Class class_;
u32 ttl;
Record record;
Optional<ByteBuffer> raw;
static ErrorOr<ResourceRecord> from_raw(ParseContext&);
ErrorOr<void> to_raw(ByteBuffer&) const;
ErrorOr<String> to_string() const;
};
struct ZoneAuthority {
DomainName name;
ByteString admin_mailbox;
u32 serial;
u32 refresh;
u32 retry;
u32 expire;
u32 minimum_ttl;
};
struct Message {
Header header;
Vector<Question> questions;
Vector<ResourceRecord> answers;
Vector<ResourceRecord> authorities;
Vector<ResourceRecord> additional_records;
static ErrorOr<Message> from_raw(ParseContext&);
static ErrorOr<Message> from_raw(Stream&);
ErrorOr<size_t> to_raw(ByteBuffer&) const;
ErrorOr<String> format_for_log() const;
};
}
}

488
Libraries/LibDNS/Resolver.h Normal file
View file

@ -0,0 +1,488 @@
/*
* Copyright (c) 2024, Ali Mohammad Pur <mpfard@serenityos.org>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#pragma once
#include <AK/AtomicRefCounted.h>
#include <AK/HashTable.h>
#include <AK/MemoryStream.h>
#include <AK/Random.h>
#include <AK/StringView.h>
#include <AK/TemporaryChange.h>
#include <LibCore/DateTime.h>
#include <LibCore/Promise.h>
#include <LibCore/SocketAddress.h>
#include <LibDNS/Message.h>
#include <LibThreading/MutexProtected.h>
#include <LibThreading/RWLockProtected.h>
namespace DNS {
class Resolver;
class LookupResult : public AtomicRefCounted<LookupResult>
, public Weakable<LookupResult> {
public:
explicit LookupResult(Messages::DomainName name)
: m_name(move(name))
{
}
Vector<Variant<IPv4Address, IPv6Address>> cached_addresses() const
{
Vector<Variant<IPv4Address, IPv6Address>> result;
for (auto& re : m_cached_records) {
re.record.record.visit(
[&](Messages::Records::A const& a) { result.append(a.address); },
[&](Messages::Records::AAAA const& aaaa) { result.append(aaaa.address); },
[](auto&) {});
}
return result;
}
void check_expiration()
{
if (!m_valid)
return;
auto now = Core::DateTime::now();
for (size_t i = 0; i < m_cached_records.size();) {
auto& record = m_cached_records[i];
if (record.expiration.has_value() && record.expiration.value() < now) {
dbgln_if(DNS_DEBUG, "DNS: Removing expired record for {}", m_name.to_string());
m_cached_records.remove(i);
} else {
dbgln_if(DNS_DEBUG, "DNS: Keeping record for {} (expires in {})", m_name.to_string(), record.expiration.has_value() ? record.expiration.value().to_string() : "never"_string);
++i;
}
}
if (m_cached_records.is_empty())
m_valid = false;
}
void add_record(Messages::ResourceRecord record)
{
m_valid = true;
auto expiration = record.ttl > 0 ? Optional<Core::DateTime>(Core::DateTime::from_timestamp(Core::DateTime::now().timestamp() + record.ttl)) : OptionalNone();
m_cached_records.append({ move(record), move(expiration) });
}
Vector<Messages::ResourceRecord> records() const
{
Vector<Messages::ResourceRecord> result;
for (auto& re : m_cached_records)
result.append(re.record);
return result;
}
bool has_record_of_type(Messages::ResourceType type, bool later = false) const
{
if (later && m_desired_types.contains(type))
return true;
for (auto const& re : m_cached_records) {
if (re.record.type == type)
return true;
}
return false;
}
void will_add_record_of_type(Messages::ResourceType type) { m_desired_types.set(type); }
void set_id(u16 id) { m_id = id; }
u16 id() { return m_id; }
bool is_valid() const { return m_valid; }
Messages::DomainName const& name() const { return m_name; }
private:
bool m_valid { false };
Messages::DomainName m_name;
struct RecordWithExpiration {
Messages::ResourceRecord record;
Optional<Core::DateTime> expiration;
};
Vector<RecordWithExpiration> m_cached_records;
HashTable<Messages::ResourceType> m_desired_types;
u16 m_id { 0 };
};
class Resolver {
public:
enum class ConnectionMode {
TCP,
UDP,
};
struct SocketResult {
MaybeOwned<Core::Socket> socket;
ConnectionMode mode;
};
Resolver(Function<ErrorOr<SocketResult>()> create_socket)
: m_pending_lookups(make<RedBlackTree<u16, PendingLookup>>())
, m_create_socket(move(create_socket))
{
m_cache.with_write_locked([&](auto& cache) {
auto add_v4v6_entry = [&cache](StringView name_string, IPv4Address v4, IPv6Address v6) {
auto name = Messages::DomainName::from_string(name_string);
auto ptr = make_ref_counted<LookupResult>(name);
ptr->will_add_record_of_type(Messages::ResourceType::A);
ptr->will_add_record_of_type(Messages::ResourceType::AAAA);
cache.set(name_string, ptr);
ptr->add_record({ .name = {}, .type = Messages::ResourceType::A, .class_ = Messages::Class::IN, .ttl = 0, .record = Messages::Records::A { v4 }, .raw = {} });
ptr->add_record({ .name = {}, .type = Messages::ResourceType::AAAA, .class_ = Messages::Class::IN, .ttl = 0, .record = Messages::Records::AAAA { v6 }, .raw = {} });
};
add_v4v6_entry("localhost"sv, { 127, 0, 0, 1 }, IPv6Address::loopback());
});
}
NonnullRefPtr<Core::Promise<Empty>> when_socket_ready()
{
auto promise = Core::Promise<Empty>::construct();
m_socket_ready_promises.append(promise);
if (has_connection(false)) {
promise->resolve({});
return promise;
}
if (!has_connection())
promise->reject(Error::from_string_literal("Failed to create socket"));
return promise;
}
void reset_connection()
{
m_socket.with_write_locked([&](auto& socket) { socket = {}; });
}
NonnullRefPtr<LookupResult const> expect_cached(StringView name, Messages::Class class_ = Messages::Class::IN)
{
return expect_cached(name, class_, Array { Messages::ResourceType::A, Messages::ResourceType::AAAA });
}
NonnullRefPtr<LookupResult const> expect_cached(StringView name, Messages::Class class_, Span<Messages::ResourceType const> desired_types)
{
auto result = lookup_in_cache(name, class_, desired_types);
VERIFY(!result.is_null());
dbgln_if(DNS_DEBUG, "DNS::expect({}) -> OK", name);
return *result;
}
RefPtr<LookupResult const> lookup_in_cache(StringView name, Messages::Class class_ = Messages::Class::IN)
{
return lookup_in_cache(name, class_, Array { Messages::ResourceType::A, Messages::ResourceType::AAAA });
}
RefPtr<LookupResult const> lookup_in_cache(StringView name, Messages::Class, Span<Messages::ResourceType const> desired_types)
{
return m_cache.with_read_locked([&](auto& cache) -> RefPtr<LookupResult const> {
auto it = cache.find(name);
if (it == cache.end())
return {};
auto& result = *it->value;
for (auto const& type : desired_types) {
if (!result.has_record_of_type(type))
return {};
}
return result;
});
}
NonnullRefPtr<Core::Promise<NonnullRefPtr<LookupResult const>>> lookup(ByteString name, Messages::Class class_ = Messages::Class::IN)
{
return lookup(move(name), class_, Array { Messages::ResourceType::A, Messages::ResourceType::AAAA });
}
NonnullRefPtr<Core::Promise<NonnullRefPtr<LookupResult const>>> lookup(ByteString name, Messages::Class class_, Span<Messages::ResourceType const> desired_types)
{
flush_cache();
auto promise = Core::Promise<NonnullRefPtr<LookupResult const>>::construct();
if (auto maybe_ipv4 = IPv4Address::from_string(name); maybe_ipv4.has_value()) {
if (desired_types.contains_slow(Messages::ResourceType::A)) {
auto result = make_ref_counted<LookupResult>(Messages::DomainName {});
result->add_record({ .name = {}, .type = Messages::ResourceType::A, .class_ = Messages::Class::IN, .ttl = 0, .record = Messages::Records::A { maybe_ipv4.release_value() }, .raw = {} });
promise->resolve(move(result));
return promise;
}
}
if (auto maybe_ipv6 = IPv6Address::from_string(name); maybe_ipv6.has_value()) {
if (desired_types.contains_slow(Messages::ResourceType::AAAA)) {
auto result = make_ref_counted<LookupResult>(Messages::DomainName {});
result->add_record({ .name = {}, .type = Messages::ResourceType::AAAA, .class_ = Messages::Class::IN, .ttl = 0, .record = Messages::Records::AAAA { maybe_ipv6.release_value() }, .raw = {} });
promise->resolve(move(result));
return promise;
}
}
if (auto result = lookup_in_cache(name, class_, desired_types)) {
promise->resolve(result.release_nonnull());
return promise;
}
auto domain_name = Messages::DomainName::from_string(name);
if (!has_connection()) {
// Use system resolver
// FIXME: Use an underlying resolver instead.
dbgln_if(DNS_DEBUG, "Not ready to resolve, using system resolver and skipping cache for {}", name);
auto record_or_error = Core::Socket::resolve_host(name, Core::Socket::SocketType::Stream);
if (record_or_error.is_error()) {
promise->reject(record_or_error.release_error());
return promise;
}
auto result = make_ref_counted<LookupResult>(domain_name);
auto record = record_or_error.release_value();
record.visit(
[&](IPv4Address const& address) {
result->add_record({ .name = {}, .type = Messages::ResourceType::A, .class_ = Messages::Class::IN, .ttl = 0, .record = Messages::Records::A { address }, .raw = {} });
},
[&](IPv6Address const& address) {
result->add_record({ .name = {}, .type = Messages::ResourceType::AAAA, .class_ = Messages::Class::IN, .ttl = 0, .record = Messages::Records::AAAA { address }, .raw = {} });
});
promise->resolve(result);
return promise;
}
auto already_in_cache = false;
auto result = m_cache.with_write_locked([&](auto& cache) -> NonnullRefPtr<LookupResult> {
auto existing = [&] -> RefPtr<LookupResult> {
if (cache.contains(name)) {
auto ptr = *cache.get(name);
already_in_cache = true;
for (auto const& type : desired_types) {
if (!ptr->has_record_of_type(type, true)) {
already_in_cache = false;
break;
}
}
return ptr;
}
return nullptr;
}();
if (existing)
return *existing;
auto ptr = make_ref_counted<LookupResult>(domain_name);
for (auto const& type : desired_types)
ptr->will_add_record_of_type(type);
cache.set(name, ptr);
return ptr;
});
Optional<u16> cached_result_id;
if (already_in_cache) {
auto id = result->id();
cached_result_id = id;
auto existing_promise = m_pending_lookups.with_write_locked([&](auto& lookups) -> RefPtr<Core::Promise<NonnullRefPtr<LookupResult const>>> {
if (auto* lookup = lookups->find(id))
return lookup->promise;
return nullptr;
});
if (existing_promise)
return existing_promise.release_nonnull();
promise->resolve(*result);
return promise;
}
Messages::Message query;
m_pending_lookups.with_read_locked([&](auto& lookups) {
do
fill_with_random({ &query.header.id, sizeof(query.header.id) });
while (lookups->find(query.header.id) != nullptr);
});
query.header.question_count = max(1u, desired_types.size());
query.header.options.set_response_code(Messages::Options::ResponseCode::NoError);
query.header.options.set_recursion_desired(true);
query.header.options.set_op_code(Messages::OpCode::Query);
for (auto const& type : desired_types) {
query.questions.append(Messages::Question {
.name = domain_name,
.type = type,
.class_ = class_,
});
}
if (query.questions.is_empty()) {
query.questions.append(Messages::Question {
.name = Messages::DomainName::from_string(name),
.type = Messages::ResourceType::A,
.class_ = class_,
});
}
auto cached_entry = m_pending_lookups.with_write_locked([&](auto& pending_lookups) -> RefPtr<Core::Promise<NonnullRefPtr<LookupResult const>>> {
// One more try to make sure we're not overwriting an existing lookup
if (cached_result_id.has_value()) {
if (auto* lookup = pending_lookups->find(*cached_result_id))
return lookup->promise;
}
pending_lookups->insert(query.header.id, { query.header.id, name, result->make_weak_ptr(), promise });
return nullptr;
});
if (cached_entry) {
dbgln_if(DNS_DEBUG, "DNS::lookup({}) -> Already in cache", name);
return cached_entry.release_nonnull();
}
ByteBuffer query_bytes;
MUST(query.to_raw(query_bytes));
if (m_mode == ConnectionMode::TCP) {
auto original_query_bytes = query_bytes;
query_bytes = MUST(ByteBuffer::create_uninitialized(query_bytes.size() + sizeof(u16)));
NetworkOrdered<u16> size = original_query_bytes.size();
query_bytes.overwrite(0, &size, sizeof(size));
query_bytes.overwrite(sizeof(size), original_query_bytes.data(), original_query_bytes.size());
}
auto write_result = m_socket.with_write_locked([&](auto& socket) {
return (*socket)->write_until_depleted(query_bytes.bytes());
});
if (write_result.is_error()) {
promise->reject(write_result.release_error());
return promise;
}
return promise;
}
private:
struct PendingLookup {
u16 id { 0 };
ByteString name;
WeakPtr<LookupResult> result;
NonnullRefPtr<Core::Promise<NonnullRefPtr<LookupResult const>>> promise;
};
ErrorOr<Messages::Message> parse_one_message()
{
if (m_mode == ConnectionMode::UDP)
return m_socket.with_write_locked([&](auto& socket) { return Messages::Message::from_raw(**socket); });
return m_socket.with_write_locked([&](auto& socket) -> ErrorOr<Messages::Message> {
if (!TRY((*socket)->can_read_without_blocking()))
return Error::from_errno(EAGAIN);
auto size = TRY((*socket)->template read_value<NetworkOrdered<u16>>());
auto buffer = TRY(ByteBuffer::create_uninitialized(size));
TRY((*socket)->read_until_filled(buffer));
FixedMemoryStream stream { static_cast<ReadonlyBytes>(buffer) };
return Messages::Message::from_raw(stream);
});
}
void process_incoming_messages()
{
while (true) {
if (auto result = m_socket.with_read_locked([](auto& socket) { return (*socket)->can_read_without_blocking(); }); result.is_error() || !result.value())
break;
auto message_or_err = parse_one_message();
if (message_or_err.is_error()) {
if (!message_or_err.error().is_errno() || message_or_err.error().code() != EAGAIN)
dbgln("DNS: Failed to receive message: {}", message_or_err.error());
break;
}
auto message = message_or_err.release_value();
auto result = m_pending_lookups.with_write_locked([&](auto& lookups) -> ErrorOr<void> {
auto* lookup = lookups->find(message.header.id);
if (!lookup)
return Error::from_string_literal("No pending lookup found for this message");
if (lookup->result.is_null())
return {}; // Message is a response to a lookup that's been purged from the cache, ignore it
auto result = lookup->result.strong_ref();
for (auto& record : message.answers)
result->add_record(move(record));
lookup->promise->resolve(*result);
lookups->remove(message.header.id);
return {};
});
if (result.is_error()) {
dbgln_if(DNS_DEBUG, "DNS: Received a message with no pending lookup: {}", result.error());
continue;
}
}
}
bool has_connection(bool attempt_restart = true)
{
auto result = m_socket.with_read_locked(
[&](auto& socket) { return socket.has_value() && (*socket)->is_open(); });
if (attempt_restart && !result && !m_attempting_restart) {
TemporaryChange change(m_attempting_restart, true);
auto create_result = m_create_socket();
if (create_result.is_error()) {
dbgln_if(DNS_DEBUG, "DNS: Failed to create socket: {}", create_result.error());
return false;
}
auto [socket, mode] = MUST(move(create_result));
set_socket(move(socket), mode);
result = true;
}
return result;
}
void set_socket(MaybeOwned<Core::Socket> socket, ConnectionMode mode = ConnectionMode::UDP)
{
m_mode = mode;
m_socket.with_write_locked([&](auto& s) {
s = move(socket);
(*s)->on_ready_to_read = [this] {
process_incoming_messages();
};
(*s)->set_notifications_enabled(true);
});
for (auto& promise : m_socket_ready_promises)
promise->resolve({});
m_socket_ready_promises.clear();
}
void flush_cache()
{
m_cache.with_write_locked([&](auto& cache) {
HashTable<ByteString> to_remove;
for (auto& entry : cache) {
entry.value->check_expiration();
if (!entry.value->is_valid())
to_remove.set(entry.key);
}
for (auto const& key : to_remove)
cache.remove(key);
});
}
Threading::RWLockProtected<HashMap<ByteString, NonnullRefPtr<LookupResult>>> m_cache;
Threading::RWLockProtected<NonnullOwnPtr<RedBlackTree<u16, PendingLookup>>> m_pending_lookups;
Threading::RWLockProtected<Optional<MaybeOwned<Core::Socket>>> m_socket;
Function<ErrorOr<SocketResult>()> m_create_socket;
bool m_attempting_restart { false };
ConnectionMode m_mode { ConnectionMode::UDP };
Vector<NonnullRefPtr<Core::Promise<Empty>>> m_socket_ready_promises;
};
}

View file

@ -10,8 +10,6 @@
#include <LibCore/DirIterator.h> #include <LibCore/DirIterator.h>
#include <LibCore/System.h> #include <LibCore/System.h>
#include <LibFileSystem/FileSystem.h> #include <LibFileSystem/FileSystem.h>
#include <dirent.h>
#include <limits.h>
#if !defined(AK_OS_IOS) && defined(AK_OS_BSD_GENERIC) #if !defined(AK_OS_IOS) && defined(AK_OS_BSD_GENERIC)
# include <sys/disk.h> # include <sys/disk.h>

View file

@ -11,7 +11,6 @@
#include <AK/Error.h> #include <AK/Error.h>
#include <AK/StringView.h> #include <AK/StringView.h>
#include <LibCore/File.h> #include <LibCore/File.h>
#include <sys/stat.h>
namespace FileSystem { namespace FileSystem {

View file

@ -10,11 +10,7 @@
# pragma GCC optimize("O3") # pragma GCC optimize("O3")
#endif #endif
#include <AK/Function.h>
#include <AK/NumericLimits.h>
#include <LibGfx/AntiAliasingPainter.h> #include <LibGfx/AntiAliasingPainter.h>
#include <LibGfx/DeprecatedPainter.h>
#include <LibGfx/Line.h>
namespace Gfx { namespace Gfx {

View file

@ -9,9 +9,7 @@
#include <LibGfx/Color.h> #include <LibGfx/Color.h>
#include <LibGfx/DeprecatedPath.h> #include <LibGfx/DeprecatedPath.h>
#include <LibGfx/Forward.h> #include <LibGfx/Forward.h>
#include <LibGfx/LineStyle.h>
#include <LibGfx/PaintStyle.h> #include <LibGfx/PaintStyle.h>
#include <LibGfx/Quad.h>
#include <LibGfx/WindingRule.h> #include <LibGfx/WindingRule.h>
namespace Gfx { namespace Gfx {

View file

@ -5,7 +5,6 @@
*/ */
#include <AK/Checked.h> #include <AK/Checked.h>
#include <AK/Forward.h>
#include <LibCore/AnonymousBuffer.h> #include <LibCore/AnonymousBuffer.h>
#include <LibGfx/Bitmap.h> #include <LibGfx/Bitmap.h>
#include <LibGfx/BitmapSequence.h> #include <LibGfx/BitmapSequence.h>

View file

@ -13,7 +13,6 @@
#include <AK/Swift.h> #include <AK/Swift.h>
#include <AK/Vector.h> #include <AK/Vector.h>
#include <LibGfx/Color.h> #include <LibGfx/Color.h>
#include <LibGfx/SystemTheme.h>
#include <LibIPC/Decoder.h> #include <LibIPC/Decoder.h>
#include <LibIPC/Encoder.h> #include <LibIPC/Encoder.h>
#include <ctype.h> #include <ctype.h>

View file

@ -12,7 +12,6 @@
#include <AK/Format.h> #include <AK/Format.h>
#include <AK/Forward.h> #include <AK/Forward.h>
#include <AK/Math.h> #include <AK/Math.h>
#include <AK/SIMD.h>
#include <AK/StdLibExtras.h> #include <AK/StdLibExtras.h>
#include <LibIPC/Forward.h> #include <LibIPC/Forward.h>

View file

@ -4,7 +4,6 @@
* SPDX-License-Identifier: BSD-2-Clause * SPDX-License-Identifier: BSD-2-Clause
*/ */
#include <AK/Format.h>
#include <AK/Math.h> #include <AK/Math.h>
#include <LibGfx/DeltaE.h> #include <LibGfx/DeltaE.h>
#include <math.h> #include <math.h>

View file

@ -12,18 +12,13 @@
#include "DeprecatedPainter.h" #include "DeprecatedPainter.h"
#include "Bitmap.h" #include "Bitmap.h"
#include "Font/Font.h"
#include <AK/Assertions.h> #include <AK/Assertions.h>
#include <AK/Function.h> #include <AK/Function.h>
#include <AK/Math.h> #include <AK/Math.h>
#include <AK/Memory.h> #include <AK/Memory.h>
#include <AK/Stack.h>
#include <AK/StdLibExtras.h> #include <AK/StdLibExtras.h>
#include <AK/Utf8View.h>
#include <LibGfx/DeprecatedPath.h> #include <LibGfx/DeprecatedPath.h>
#include <LibGfx/Palette.h> #include <LibGfx/ScalingMode.h>
#include <LibGfx/Quad.h>
#include <LibGfx/TextLayout.h>
#include <stdio.h> #include <stdio.h>
#if defined(AK_COMPILER_GCC) #if defined(AK_COMPILER_GCC)

View file

@ -7,7 +7,6 @@
#pragma once #pragma once
#include <AK/Forward.h> #include <AK/Forward.h>
#include <AK/Memory.h>
#include <AK/NonnullRefPtr.h> #include <AK/NonnullRefPtr.h>
#include <AK/Vector.h> #include <AK/Vector.h>
#include <LibGfx/Color.h> #include <LibGfx/Color.h>
@ -16,8 +15,6 @@
#include <LibGfx/PaintStyle.h> #include <LibGfx/PaintStyle.h>
#include <LibGfx/Point.h> #include <LibGfx/Point.h>
#include <LibGfx/Rect.h> #include <LibGfx/Rect.h>
#include <LibGfx/ScalingMode.h>
#include <LibGfx/Size.h>
#include <LibGfx/WindingRule.h> #include <LibGfx/WindingRule.h>
namespace Gfx { namespace Gfx {

View file

@ -6,12 +6,9 @@
#include <AK/Math.h> #include <AK/Math.h>
#include <AK/StringBuilder.h> #include <AK/StringBuilder.h>
#include <AK/TypeCasts.h>
#include <LibGfx/BoundingBox.h> #include <LibGfx/BoundingBox.h>
#include <LibGfx/DeprecatedPainter.h> #include <LibGfx/DeprecatedPainter.h>
#include <LibGfx/DeprecatedPath.h> #include <LibGfx/DeprecatedPath.h>
#include <LibGfx/Font/ScaledFont.h>
#include <LibGfx/TextLayout.h>
namespace Gfx { namespace Gfx {

View file

@ -6,7 +6,6 @@
#pragma once #pragma once
#include <AK/ByteString.h>
#include <AK/Optional.h> #include <AK/Optional.h>
#include <AK/Vector.h> #include <AK/Vector.h>
#include <LibGfx/Forward.h> #include <LibGfx/Forward.h>

View file

@ -4,9 +4,7 @@
* SPDX-License-Identifier: BSD-2-Clause * SPDX-License-Identifier: BSD-2-Clause
*/ */
#include <AK/Array.h> #include <AK/Memory.h>
#include <AK/Debug.h>
#include <AK/IntegralMath.h>
#include <AK/Types.h> #include <AK/Types.h>
#include <LibGfx/AntiAliasingPainter.h> #include <LibGfx/AntiAliasingPainter.h>
#include <LibGfx/DeprecatedPainter.h> #include <LibGfx/DeprecatedPainter.h>

View file

@ -7,15 +7,10 @@
#pragma once #pragma once
#include <AK/Bitmap.h>
#include <AK/ByteReader.h>
#include <AK/RefCounted.h> #include <AK/RefCounted.h>
#include <AK/RefPtr.h> #include <AK/RefPtr.h>
#include <AK/String.h> #include <AK/String.h>
#include <AK/Types.h> #include <AK/Types.h>
#include <LibCore/MappedFile.h>
#include <LibGfx/Bitmap.h>
#include <LibGfx/Size.h>
struct hb_font_t; struct hb_font_t;
@ -85,7 +80,7 @@ public:
virtual float width(StringView) const = 0; virtual float width(StringView) const = 0;
virtual float width(Utf8View const&) const = 0; virtual float width(Utf8View const&) const = 0;
virtual FlyString family() const = 0; virtual FlyString const& family() const = 0;
virtual NonnullRefPtr<Font> with_size(float point_size) const = 0; virtual NonnullRefPtr<Font> with_size(float point_size) const = 0;

View file

@ -6,9 +6,6 @@
#pragma once #pragma once
#include <AK/HashMap.h>
#include <AK/Noncopyable.h>
#include <AK/RefCounted.h>
#include <LibCore/Resource.h> #include <LibCore/Resource.h>
#include <LibGfx/Forward.h> #include <LibGfx/Forward.h>

View file

@ -8,7 +8,6 @@
#include <AK/FlyString.h> #include <AK/FlyString.h>
#include <AK/Function.h> #include <AK/Function.h>
#include <AK/HashMap.h>
#include <AK/OwnPtr.h> #include <AK/OwnPtr.h>
#include <LibGfx/Font/Typeface.h> #include <LibGfx/Font/Typeface.h>
#include <LibGfx/Forward.h> #include <LibGfx/Forward.h>

View file

@ -9,7 +9,6 @@
#include <AK/FlyString.h> #include <AK/FlyString.h>
#include <AK/Function.h> #include <AK/Function.h>
#include <AK/HashMap.h> #include <AK/HashMap.h>
#include <AK/OwnPtr.h>
#include <LibGfx/Font/FontDatabase.h> #include <LibGfx/Font/FontDatabase.h>
#include <LibGfx/Font/Typeface.h> #include <LibGfx/Font/Typeface.h>

View file

@ -8,8 +8,6 @@
#pragma once #pragma once
#include <AK/FlyString.h> #include <AK/FlyString.h>
#include <AK/HashMap.h>
#include <LibGfx/Bitmap.h>
#include <LibGfx/Font/Font.h> #include <LibGfx/Font/Font.h>
#include <LibGfx/Font/Typeface.h> #include <LibGfx/Font/Typeface.h>
@ -37,7 +35,7 @@ public:
virtual u8 baseline() const override { return m_point_height; } // FIXME: Read from font virtual u8 baseline() const override { return m_point_height; } // FIXME: Read from font
virtual float width(StringView) const override; virtual float width(StringView) const override;
virtual float width(Utf8View const&) const override; virtual float width(Utf8View const&) const override;
virtual FlyString family() const override { return m_typeface->family(); } virtual FlyString const& family() const override { return m_typeface->family(); }
virtual NonnullRefPtr<ScaledFont> scaled_with_size(float point_size) const; virtual NonnullRefPtr<ScaledFont> scaled_with_size(float point_size) const;
virtual NonnullRefPtr<Font> with_size(float point_size) const override; virtual NonnullRefPtr<Font> with_size(float point_size) const override;

View file

@ -4,7 +4,6 @@
* SPDX-License-Identifier: BSD-2-Clause * SPDX-License-Identifier: BSD-2-Clause
*/ */
#include <core/SkTypeface.h>
#include <harfbuzz/hb.h> #include <harfbuzz/hb.h>
#include <LibGfx/Font/ScaledFont.h> #include <LibGfx/Font/ScaledFont.h>

View file

@ -7,9 +7,7 @@
#pragma once #pragma once
#include <AK/HashMap.h> #include <AK/HashMap.h>
#include <AK/Noncopyable.h>
#include <AK/RefCounted.h> #include <AK/RefCounted.h>
#include <LibGfx/Font/Font.h>
#include <LibGfx/Font/FontData.h> #include <LibGfx/Font/FontData.h>
#include <LibGfx/Forward.h> #include <LibGfx/Forward.h>
@ -47,7 +45,7 @@ public:
virtual u32 glyph_count() const = 0; virtual u32 glyph_count() const = 0;
virtual u16 units_per_em() const = 0; virtual u16 units_per_em() const = 0;
virtual u32 glyph_id_for_code_point(u32 code_point) const = 0; virtual u32 glyph_id_for_code_point(u32 code_point) const = 0;
virtual FlyString family() const = 0; virtual FlyString const& family() const = 0;
virtual u16 weight() const = 0; virtual u16 weight() const = 0;
virtual u16 width() const = 0; virtual u16 width() const = 0;
virtual u8 slope() const = 0; virtual u8 slope() const = 0;

View file

@ -6,12 +6,10 @@
#include <AK/LsanSuppressions.h> #include <AK/LsanSuppressions.h>
#include <LibGfx/Font/FontDatabase.h> #include <LibGfx/Font/FontDatabase.h>
#include <LibGfx/Font/Typeface.h>
#include <LibGfx/Font/TypefaceSkia.h> #include <LibGfx/Font/TypefaceSkia.h>
#include <core/SkData.h> #include <core/SkData.h>
#include <core/SkFontMgr.h> #include <core/SkFontMgr.h>
#include <core/SkRefCnt.h>
#include <core/SkTypeface.h> #include <core/SkTypeface.h>
#ifndef AK_OS_ANDROID #ifndef AK_OS_ANDROID
# include <ports/SkFontMgr_fontconfig.h> # include <ports/SkFontMgr_fontconfig.h>
@ -114,7 +112,7 @@ void TypefaceSkia::populate_glyph_page(GlyphPage& glyph_page, size_t page_index)
} }
} }
FlyString TypefaceSkia::family() const FlyString const& TypefaceSkia::family() const
{ {
if (!m_family.has_value()) { if (!m_family.has_value()) {
SkString family_name; SkString family_name;

View file

@ -19,7 +19,7 @@ public:
virtual u32 glyph_count() const override; virtual u32 glyph_count() const override;
virtual u16 units_per_em() const override; virtual u16 units_per_em() const override;
virtual u32 glyph_id_for_code_point(u32 code_point) const override; virtual u32 glyph_id_for_code_point(u32 code_point) const override;
virtual FlyString family() const override; virtual FlyString const& family() const override;
virtual u16 weight() const override; virtual u16 weight() const override;
virtual u16 width() const override; virtual u16 width() const override;
virtual u8 slope() const override; virtual u8 slope() const override;

View file

@ -6,8 +6,6 @@
#pragma once #pragma once
#include <AK/ByteBuffer.h>
namespace Gfx::ICC { namespace Gfx::ICC {
class Profile; class Profile;

View file

@ -7,7 +7,6 @@
#include <LibGfx/ICC/Profile.h> #include <LibGfx/ICC/Profile.h>
#include <LibGfx/ICC/Tags.h> #include <LibGfx/ICC/Tags.h>
#include <LibGfx/ICC/WellKnownProfiles.h> #include <LibGfx/ICC/WellKnownProfiles.h>
#include <time.h>
namespace Gfx::ICC { namespace Gfx::ICC {

View file

@ -7,9 +7,7 @@
#pragma once #pragma once
#include <AK/BitStream.h>
#include <AK/Error.h> #include <AK/Error.h>
#include <AK/Optional.h>
#include <AK/Types.h> #include <AK/Types.h>
namespace Gfx { namespace Gfx {

View file

@ -17,7 +17,6 @@
#include <LibCompress/Lzw.h> #include <LibCompress/Lzw.h>
#include <LibGfx/ImageFormats/GIFLoader.h> #include <LibGfx/ImageFormats/GIFLoader.h>
#include <LibGfx/Painter.h> #include <LibGfx/Painter.h>
#include <string.h>
namespace Gfx { namespace Gfx {

View file

@ -7,7 +7,6 @@
#pragma once #pragma once
#include <AK/MemoryStream.h> #include <AK/MemoryStream.h>
#include <LibGfx/Bitmap.h>
#include <LibGfx/ImageFormats/ImageDecoder.h> #include <LibGfx/ImageFormats/ImageDecoder.h>
namespace Gfx { namespace Gfx {

View file

@ -4,14 +4,12 @@
* SPDX-License-Identifier: BSD-2-Clause * SPDX-License-Identifier: BSD-2-Clause
*/ */
#include <AK/ByteBuffer.h>
#include <AK/Debug.h> #include <AK/Debug.h>
#include <AK/MemoryStream.h> #include <AK/MemoryStream.h>
#include <AK/Types.h> #include <AK/Types.h>
#include <LibGfx/ImageFormats/BMPLoader.h> #include <LibGfx/ImageFormats/BMPLoader.h>
#include <LibGfx/ImageFormats/ICOLoader.h> #include <LibGfx/ImageFormats/ICOLoader.h>
#include <LibGfx/ImageFormats/PNGLoader.h> #include <LibGfx/ImageFormats/PNGLoader.h>
#include <string.h>
namespace Gfx { namespace Gfx {

View file

@ -4,7 +4,6 @@
* SPDX-License-Identifier: BSD-2-Clause * SPDX-License-Identifier: BSD-2-Clause
*/ */
#include <AK/LexicalPath.h>
#include <LibGfx/ImageFormats/AVIFLoader.h> #include <LibGfx/ImageFormats/AVIFLoader.h>
#include <LibGfx/ImageFormats/BMPLoader.h> #include <LibGfx/ImageFormats/BMPLoader.h>
#include <LibGfx/ImageFormats/GIFLoader.h> #include <LibGfx/ImageFormats/GIFLoader.h>

View file

@ -6,7 +6,6 @@
#pragma once #pragma once
#include <AK/ByteBuffer.h>
#include <AK/HashMap.h> #include <AK/HashMap.h>
#include <AK/OwnPtr.h> #include <AK/OwnPtr.h>
#include <AK/RefCounted.h> #include <AK/RefCounted.h>

View file

@ -4,7 +4,6 @@
* SPDX-License-Identifier: BSD-2-Clause * SPDX-License-Identifier: BSD-2-Clause
*/ */
#include <AK/Debug.h>
#include <AK/Error.h> #include <AK/Error.h>
#include <LibGfx/ImageFormats/JPEGXLLoader.h> #include <LibGfx/ImageFormats/JPEGXLLoader.h>
#include <jxl/decode.h> #include <jxl/decode.h>

View file

@ -6,7 +6,6 @@
#pragma once #pragma once
#include <AK/MemoryStream.h>
#include <LibGfx/ImageFormats/ImageDecoder.h> #include <LibGfx/ImageFormats/ImageDecoder.h>
namespace Gfx { namespace Gfx {

View file

@ -10,7 +10,6 @@
#include <AK/LEB128.h> #include <AK/LEB128.h>
#include <AK/MemoryStream.h> #include <AK/MemoryStream.h>
#include <AK/Variant.h> #include <AK/Variant.h>
#include <LibCore/File.h>
#include <LibGfx/AntiAliasingPainter.h> #include <LibGfx/AntiAliasingPainter.h>
#include <LibGfx/DeprecatedPainter.h> #include <LibGfx/DeprecatedPainter.h>
#include <LibGfx/ImageFormats/TinyVGLoader.h> #include <LibGfx/ImageFormats/TinyVGLoader.h>

View file

@ -10,7 +10,6 @@
#include <LibGfx/Color.h> #include <LibGfx/Color.h>
#include <LibGfx/Forward.h> #include <LibGfx/Forward.h>
#include <LibGfx/ImageFormats/WebPWriterLossless.h> #include <LibGfx/ImageFormats/WebPWriterLossless.h>
#include <LibGfx/Point.h>
namespace Gfx { namespace Gfx {

View file

@ -8,11 +8,9 @@
#include <AK/BitStream.h> #include <AK/BitStream.h>
#include <AK/Debug.h> #include <AK/Debug.h>
#include <AK/Endian.h>
#include <AK/HashTable.h> #include <AK/HashTable.h>
#include <AK/MemoryStream.h> #include <AK/MemoryStream.h>
#include <AK/QuickSort.h> #include <AK/QuickSort.h>
#include <LibCompress/DeflateTables.h>
#include <LibCompress/Huffman.h> #include <LibCompress/Huffman.h>
#include <LibGfx/Bitmap.h> #include <LibGfx/Bitmap.h>
#include <LibGfx/ImageFormats/WebPSharedLossless.h> #include <LibGfx/ImageFormats/WebPSharedLossless.h>

View file

@ -13,20 +13,9 @@
#include <LibGfx/PathSkia.h> #include <LibGfx/PathSkia.h>
#include <AK/TypeCasts.h> #include <AK/TypeCasts.h>
#include <core/SkBitmap.h>
#include <core/SkBlurTypes.h>
#include <core/SkCanvas.h> #include <core/SkCanvas.h>
#include <core/SkColorFilter.h>
#include <core/SkMaskFilter.h>
#include <core/SkPath.h> #include <core/SkPath.h>
#include <core/SkPathBuilder.h>
#include <core/SkRRect.h>
#include <core/SkSurface.h>
#include <effects/SkGradientShader.h> #include <effects/SkGradientShader.h>
#include <effects/SkImageFilters.h>
#include <gpu/GrDirectContext.h>
#include <gpu/ganesh/SkSurfaceGanesh.h>
#include <pathops/SkPathOps.h>
namespace Gfx { namespace Gfx {

View file

@ -5,7 +5,6 @@
*/ */
#include <LibGfx/Bitmap.h> #include <LibGfx/Bitmap.h>
#include <LibGfx/ImmutableBitmap.h>
#include <LibGfx/PaintingSurface.h> #include <LibGfx/PaintingSurface.h>
#include <LibGfx/SkiaUtils.h> #include <LibGfx/SkiaUtils.h>
@ -16,9 +15,7 @@
#include <gpu/ganesh/SkSurfaceGanesh.h> #include <gpu/ganesh/SkSurfaceGanesh.h>
#ifdef AK_OS_MACOS #ifdef AK_OS_MACOS
# include <gpu/ganesh/mtl/GrMtlBackendContext.h>
# include <gpu/ganesh/mtl/GrMtlBackendSurface.h> # include <gpu/ganesh/mtl/GrMtlBackendSurface.h>
# include <gpu/ganesh/mtl/GrMtlDirectContext.h>
#endif #endif
namespace Gfx { namespace Gfx {

View file

@ -6,9 +6,7 @@
* SPDX-License-Identifier: BSD-2-Clause * SPDX-License-Identifier: BSD-2-Clause
*/ */
#include <AK/Badge.h>
#include <LibGfx/Palette.h> #include <LibGfx/Palette.h>
#include <string.h>
namespace Gfx { namespace Gfx {

View file

@ -9,9 +9,10 @@
#include <AK/Forward.h> #include <AK/Forward.h>
#include <AK/NonnullOwnPtr.h> #include <AK/NonnullOwnPtr.h>
#include <AK/Utf8View.h> #include <AK/Utf8View.h>
#include <LibGfx/AffineTransform.h>
#include <LibGfx/Forward.h> #include <LibGfx/Forward.h>
#include <LibGfx/PaintStyle.h> #include <LibGfx/Point.h>
#include <LibGfx/ScalingMode.h> #include <LibGfx/Rect.h>
#include <LibGfx/WindingRule.h> #include <LibGfx/WindingRule.h>
namespace Gfx { namespace Gfx {

View file

@ -9,7 +9,7 @@
#include <AK/TypeCasts.h> #include <AK/TypeCasts.h>
#include <LibGfx/Font/ScaledFont.h> #include <LibGfx/Font/ScaledFont.h>
#include <LibGfx/PathSkia.h> #include <LibGfx/PathSkia.h>
#include <core/SkContourMeasure.h> #include <LibGfx/Rect.h>
#include <core/SkFont.h> #include <core/SkFont.h>
#include <core/SkPath.h> #include <core/SkPath.h>
#include <core/SkPathMeasure.h> #include <core/SkPathMeasure.h>

View file

@ -5,8 +5,6 @@
*/ */
#include <AK/ByteString.h> #include <AK/ByteString.h>
#include <AK/Vector.h>
#include <LibGfx/Line.h>
#include <LibGfx/Rect.h> #include <LibGfx/Rect.h>
#include <LibIPC/Decoder.h> #include <LibIPC/Decoder.h>
#include <LibIPC/Encoder.h> #include <LibIPC/Encoder.h>

View file

@ -8,7 +8,6 @@
#include <AK/RefPtr.h> #include <AK/RefPtr.h>
#include <LibGfx/Bitmap.h> #include <LibGfx/Bitmap.h>
#include <LibGfx/Size.h>
#include <LibIPC/Forward.h> #include <LibIPC/Forward.h>
namespace Gfx { namespace Gfx {

View file

@ -11,7 +11,6 @@
#include <LibCore/ConfigFile.h> #include <LibCore/ConfigFile.h>
#include <LibCore/DirIterator.h> #include <LibCore/DirIterator.h>
#include <LibGfx/SystemTheme.h> #include <LibGfx/SystemTheme.h>
#include <string.h>
namespace Gfx { namespace Gfx {

View file

@ -10,7 +10,6 @@
#include <AK/ByteString.h> #include <AK/ByteString.h>
#include <AK/Forward.h> #include <AK/Forward.h>
#include <AK/Types.h>
#include <AK/Vector.h> #include <AK/Vector.h>
#include <LibCore/AnonymousBuffer.h> #include <LibCore/AnonymousBuffer.h>
#include <LibCore/ConfigFile.h> #include <LibCore/ConfigFile.h>

View file

@ -7,7 +7,7 @@
#include "TextLayout.h" #include "TextLayout.h"
#include <AK/TypeCasts.h> #include <AK/TypeCasts.h>
#include <LibGfx/Font/ScaledFont.h> #include <LibGfx/Point.h>
#include <harfbuzz/hb.h> #include <harfbuzz/hb.h>
namespace Gfx { namespace Gfx {

View file

@ -7,16 +7,12 @@
#pragma once #pragma once
#include <AK/ByteString.h>
#include <AK/CharacterTypes.h>
#include <AK/Forward.h> #include <AK/Forward.h>
#include <AK/Utf32View.h>
#include <AK/Utf8View.h> #include <AK/Utf8View.h>
#include <AK/Vector.h> #include <AK/Vector.h>
#include <LibGfx/Font/Font.h> #include <LibGfx/Font/Font.h>
#include <LibGfx/FontCascadeList.h>
#include <LibGfx/Forward.h> #include <LibGfx/Forward.h>
#include <LibGfx/Rect.h> #include <LibGfx/Point.h>
namespace Gfx { namespace Gfx {

View file

@ -15,6 +15,19 @@ public:
HeaderMap() = default; HeaderMap() = default;
~HeaderMap() = default; ~HeaderMap() = default;
HeaderMap(Vector<Header> headers)
: m_headers(move(headers))
{
for (auto& header : m_headers)
m_map.set(header.name, header.value);
}
HeaderMap(HeaderMap const&) = default;
HeaderMap(HeaderMap&&) = default;
HeaderMap& operator=(HeaderMap const&) = default;
HeaderMap& operator=(HeaderMap&&) = default;
void set(ByteString name, ByteString value) void set(ByteString name, ByteString value)
{ {
m_map.set(name, value); m_map.set(name, value);
@ -56,10 +69,7 @@ template<>
inline ErrorOr<HTTP::HeaderMap> decode(Decoder& decoder) inline ErrorOr<HTTP::HeaderMap> decode(Decoder& decoder)
{ {
auto headers = TRY(decoder.decode<Vector<HTTP::Header>>()); auto headers = TRY(decoder.decode<Vector<HTTP::Header>>());
HTTP::HeaderMap header_map; return HTTP::HeaderMap { move(headers) };
for (auto& header : headers)
header_map.set(move(header.name), move(header.value));
return header_map;
} }
} }

View file

@ -205,40 +205,12 @@ set(SOURCES
Runtime/SymbolObject.cpp Runtime/SymbolObject.cpp
Runtime/SymbolPrototype.cpp Runtime/SymbolPrototype.cpp
Runtime/Temporal/AbstractOperations.cpp Runtime/Temporal/AbstractOperations.cpp
Runtime/Temporal/Calendar.cpp
Runtime/Temporal/CalendarConstructor.cpp
Runtime/Temporal/CalendarPrototype.cpp
Runtime/Temporal/Duration.cpp Runtime/Temporal/Duration.cpp
Runtime/Temporal/DurationConstructor.cpp Runtime/Temporal/DurationConstructor.cpp
Runtime/Temporal/DurationPrototype.cpp Runtime/Temporal/DurationPrototype.cpp
Runtime/Temporal/Instant.cpp Runtime/Temporal/Instant.cpp
Runtime/Temporal/InstantConstructor.cpp
Runtime/Temporal/InstantPrototype.cpp
Runtime/Temporal/ISO8601.cpp Runtime/Temporal/ISO8601.cpp
Runtime/Temporal/Now.cpp
Runtime/Temporal/PlainDate.cpp
Runtime/Temporal/PlainDateConstructor.cpp
Runtime/Temporal/PlainDatePrototype.cpp
Runtime/Temporal/PlainDateTime.cpp
Runtime/Temporal/PlainDateTimeConstructor.cpp
Runtime/Temporal/PlainDateTimePrototype.cpp
Runtime/Temporal/PlainMonthDay.cpp
Runtime/Temporal/PlainMonthDayConstructor.cpp
Runtime/Temporal/PlainMonthDayPrototype.cpp
Runtime/Temporal/PlainTime.cpp
Runtime/Temporal/PlainTimeConstructor.cpp
Runtime/Temporal/PlainTimePrototype.cpp
Runtime/Temporal/PlainYearMonth.cpp
Runtime/Temporal/PlainYearMonthConstructor.cpp
Runtime/Temporal/PlainYearMonthPrototype.cpp
Runtime/Temporal/Temporal.cpp Runtime/Temporal/Temporal.cpp
Runtime/Temporal/TimeZone.cpp
Runtime/Temporal/TimeZoneConstructor.cpp
Runtime/Temporal/TimeZoneMethods.cpp
Runtime/Temporal/TimeZonePrototype.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

@ -88,16 +88,7 @@
__JS_ENUMERATE(Segmenter, segmenter, SegmenterPrototype, SegmenterConstructor) __JS_ENUMERATE(Segmenter, segmenter, SegmenterPrototype, SegmenterConstructor)
#define JS_ENUMERATE_TEMPORAL_OBJECTS \ #define JS_ENUMERATE_TEMPORAL_OBJECTS \
__JS_ENUMERATE(Calendar, calendar, CalendarPrototype, CalendarConstructor) \ __JS_ENUMERATE(Duration, duration, DurationPrototype, DurationConstructor)
__JS_ENUMERATE(Duration, duration, DurationPrototype, DurationConstructor) \
__JS_ENUMERATE(Instant, instant, InstantPrototype, InstantConstructor) \
__JS_ENUMERATE(PlainDate, plain_date, PlainDatePrototype, PlainDateConstructor) \
__JS_ENUMERATE(PlainDateTime, plain_date_time, PlainDateTimePrototype, PlainDateTimeConstructor) \
__JS_ENUMERATE(PlainMonthDay, plain_month_day, PlainMonthDayPrototype, PlainMonthDayConstructor) \
__JS_ENUMERATE(PlainTime, plain_time, PlainTimePrototype, PlainTimeConstructor) \
__JS_ENUMERATE(PlainYearMonth, plain_year_month, PlainYearMonthPrototype, PlainYearMonthConstructor) \
__JS_ENUMERATE(TimeZone, time_zone, TimeZonePrototype, TimeZoneConstructor) \
__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) \
@ -284,13 +275,8 @@ namespace Temporal {
class PrototypeName; class PrototypeName;
JS_ENUMERATE_TEMPORAL_OBJECTS JS_ENUMERATE_TEMPORAL_OBJECTS
#undef __JS_ENUMERATE #undef __JS_ENUMERATE
class Temporal; class Temporal;
struct CalendarMethods;
struct DurationRecord;
struct DateDurationRecord;
struct TimeDurationRecord;
struct TimeZoneMethods;
struct PartialDurationRecord;
}; };
template<typename T> template<typename T>

View file

@ -47,16 +47,7 @@
#include <LibJS/Runtime/Shape.h> #include <LibJS/Runtime/Shape.h>
#include <LibJS/Runtime/StringObject.h> #include <LibJS/Runtime/StringObject.h>
#include <LibJS/Runtime/StringPrototype.h> #include <LibJS/Runtime/StringPrototype.h>
#include <LibJS/Runtime/Temporal/Calendar.h>
#include <LibJS/Runtime/Temporal/Duration.h> #include <LibJS/Runtime/Temporal/Duration.h>
#include <LibJS/Runtime/Temporal/Instant.h>
#include <LibJS/Runtime/Temporal/PlainDate.h>
#include <LibJS/Runtime/Temporal/PlainDateTime.h>
#include <LibJS/Runtime/Temporal/PlainMonthDay.h>
#include <LibJS/Runtime/Temporal/PlainTime.h>
#include <LibJS/Runtime/Temporal/PlainYearMonth.h>
#include <LibJS/Runtime/Temporal/TimeZone.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>
@ -516,101 +507,6 @@ ErrorOr<void> print_data_view(JS::PrintContext& print_context, JS::DataView cons
return {}; return {};
} }
ErrorOr<void> print_temporal_calendar(JS::PrintContext& print_context, JS::Temporal::Calendar const& calendar, HashTable<JS::Object*>& seen_objects)
{
TRY(print_type(print_context, "Temporal.Calendar"sv));
TRY(js_out(print_context, " "));
TRY(print_value(print_context, JS::PrimitiveString::create(calendar.vm(), calendar.identifier()), seen_objects));
return {};
}
ErrorOr<void> print_temporal_duration(JS::PrintContext& print_context, JS::Temporal::Duration const& duration, HashTable<JS::Object*>&)
{
TRY(print_type(print_context, "Temporal.Duration"sv));
TRY(js_out(print_context, " \033[34;1m{} y, {} M, {} w, {} d, {} h, {} m, {} s, {} ms, {} us, {} ns\033[0m", duration.years(), duration.months(), duration.weeks(), duration.days(), duration.hours(), duration.minutes(), duration.seconds(), duration.milliseconds(), duration.microseconds(), duration.nanoseconds()));
return {};
}
ErrorOr<void> print_temporal_instant(JS::PrintContext& print_context, JS::Temporal::Instant const& instant, HashTable<JS::Object*>& seen_objects)
{
TRY(print_type(print_context, "Temporal.Instant"sv));
TRY(js_out(print_context, " "));
// FIXME: Print human readable date and time, like in print_date(print_context, ) - ideally handling arbitrarily large values since we get a bigint.
TRY(print_value(print_context, &instant.nanoseconds(), seen_objects));
return {};
}
ErrorOr<void> print_temporal_plain_date(JS::PrintContext& print_context, JS::Temporal::PlainDate const& plain_date, HashTable<JS::Object*>& seen_objects)
{
TRY(print_type(print_context, "Temporal.PlainDate"sv));
TRY(js_out(print_context, " \033[34;1m{:04}-{:02}-{:02}\033[0m", plain_date.iso_year(), plain_date.iso_month(), plain_date.iso_day()));
TRY(js_out(print_context, "\n calendar: "));
TRY(print_value(print_context, &plain_date.calendar(), seen_objects));
return {};
}
ErrorOr<void> print_temporal_plain_date_time(JS::PrintContext& print_context, JS::Temporal::PlainDateTime const& plain_date_time, HashTable<JS::Object*>& seen_objects)
{
TRY(print_type(print_context, "Temporal.PlainDateTime"sv));
TRY(js_out(print_context, " \033[34;1m{:04}-{:02}-{:02} {:02}:{:02}:{:02}.{:03}{:03}{:03}\033[0m", plain_date_time.iso_year(), plain_date_time.iso_month(), plain_date_time.iso_day(), plain_date_time.iso_hour(), plain_date_time.iso_minute(), plain_date_time.iso_second(), plain_date_time.iso_millisecond(), plain_date_time.iso_microsecond(), plain_date_time.iso_nanosecond()));
TRY(js_out(print_context, "\n calendar: "));
TRY(print_value(print_context, &plain_date_time.calendar(), seen_objects));
return {};
}
ErrorOr<void> print_temporal_plain_month_day(JS::PrintContext& print_context, JS::Temporal::PlainMonthDay const& plain_month_day, HashTable<JS::Object*>& seen_objects)
{
TRY(print_type(print_context, "Temporal.PlainMonthDay"sv));
// Also has an [[ISOYear]] internal slot, but showing that here seems rather unexpected.
TRY(js_out(print_context, " \033[34;1m{:02}-{:02}\033[0m", plain_month_day.iso_month(), plain_month_day.iso_day()));
TRY(js_out(print_context, "\n calendar: "));
TRY(print_value(print_context, &plain_month_day.calendar(), seen_objects));
return {};
}
ErrorOr<void> print_temporal_plain_time(JS::PrintContext& print_context, JS::Temporal::PlainTime const& plain_time, HashTable<JS::Object*>& seen_objects)
{
TRY(print_type(print_context, "Temporal.PlainTime"sv));
TRY(js_out(print_context, " \033[34;1m{:02}:{:02}:{:02}.{:03}{:03}{:03}\033[0m", plain_time.iso_hour(), plain_time.iso_minute(), plain_time.iso_second(), plain_time.iso_millisecond(), plain_time.iso_microsecond(), plain_time.iso_nanosecond()));
TRY(js_out(print_context, "\n calendar: "));
TRY(print_value(print_context, &plain_time.calendar(), seen_objects));
return {};
}
ErrorOr<void> print_temporal_plain_year_month(JS::PrintContext& print_context, JS::Temporal::PlainYearMonth const& plain_year_month, HashTable<JS::Object*>& seen_objects)
{
TRY(print_type(print_context, "Temporal.PlainYearMonth"sv));
// Also has an [[ISODay]] internal slot, but showing that here seems rather unexpected.
TRY(js_out(print_context, " \033[34;1m{:04}-{:02}\033[0m", plain_year_month.iso_year(), plain_year_month.iso_month()));
TRY(js_out(print_context, "\n calendar: "));
TRY(print_value(print_context, &plain_year_month.calendar(), seen_objects));
return {};
}
ErrorOr<void> print_temporal_time_zone(JS::PrintContext& print_context, JS::Temporal::TimeZone const& time_zone, HashTable<JS::Object*>& seen_objects)
{
TRY(print_type(print_context, "Temporal.TimeZone"sv));
TRY(js_out(print_context, " "));
TRY(print_value(print_context, JS::PrimitiveString::create(time_zone.vm(), time_zone.identifier()), seen_objects));
if (time_zone.offset_nanoseconds().has_value()) {
TRY(js_out(print_context, "\n offset (ns): "));
TRY(print_value(print_context, JS::Value(*time_zone.offset_nanoseconds()), seen_objects));
}
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.nanoseconds(), seen_objects));
TRY(js_out(print_context, "\n timeZone: "));
TRY(print_value(print_context, &zoned_date_time.time_zone(), seen_objects));
TRY(js_out(print_context, "\n calendar: "));
TRY(print_value(print_context, &zoned_date_time.calendar(), seen_objects));
return {};
}
ErrorOr<void> print_intl_display_names(JS::PrintContext& print_context, JS::Intl::DisplayNames const& display_names, HashTable<JS::Object*>& seen_objects) ErrorOr<void> print_intl_display_names(JS::PrintContext& print_context, JS::Intl::DisplayNames const& display_names, HashTable<JS::Object*>& seen_objects)
{ {
TRY(print_type(print_context, "Intl.DisplayNames"sv)); TRY(print_type(print_context, "Intl.DisplayNames"sv));
@ -932,6 +828,13 @@ ErrorOr<void> print_intl_duration_format(JS::PrintContext& print_context, JS::In
return {}; return {};
} }
ErrorOr<void> print_temporal_duration(JS::PrintContext& print_context, JS::Temporal::Duration const& duration, HashTable<JS::Object*>&)
{
TRY(print_type(print_context, "Temporal.Duration"sv));
TRY(js_out(print_context, " \033[34;1m{} y, {} M, {} w, {} d, {} h, {} m, {} s, {} ms, {} us, {} ns\033[0m", duration.years(), duration.months(), duration.weeks(), duration.days(), duration.hours(), duration.minutes(), duration.seconds(), duration.milliseconds(), duration.microseconds(), duration.nanoseconds()));
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));
@ -1025,26 +928,6 @@ ErrorOr<void> print_value(JS::PrintContext& print_context, JS::Value value, Hash
return print_number_object(print_context, static_cast<JS::NumberObject&>(object), seen_objects); return print_number_object(print_context, static_cast<JS::NumberObject&>(object), seen_objects);
if (is<JS::StringObject>(object)) if (is<JS::StringObject>(object))
return print_string_object(print_context, static_cast<JS::StringObject&>(object), seen_objects); return print_string_object(print_context, static_cast<JS::StringObject&>(object), seen_objects);
if (is<JS::Temporal::Calendar>(object))
return print_temporal_calendar(print_context, static_cast<JS::Temporal::Calendar&>(object), seen_objects);
if (is<JS::Temporal::Duration>(object))
return print_temporal_duration(print_context, static_cast<JS::Temporal::Duration&>(object), seen_objects);
if (is<JS::Temporal::Instant>(object))
return print_temporal_instant(print_context, static_cast<JS::Temporal::Instant&>(object), seen_objects);
if (is<JS::Temporal::PlainDate>(object))
return print_temporal_plain_date(print_context, static_cast<JS::Temporal::PlainDate&>(object), seen_objects);
if (is<JS::Temporal::PlainDateTime>(object))
return print_temporal_plain_date_time(print_context, static_cast<JS::Temporal::PlainDateTime&>(object), seen_objects);
if (is<JS::Temporal::PlainMonthDay>(object))
return print_temporal_plain_month_day(print_context, static_cast<JS::Temporal::PlainMonthDay&>(object), seen_objects);
if (is<JS::Temporal::PlainTime>(object))
return print_temporal_plain_time(print_context, static_cast<JS::Temporal::PlainTime&>(object), seen_objects);
if (is<JS::Temporal::PlainYearMonth>(object))
return print_temporal_plain_year_month(print_context, static_cast<JS::Temporal::PlainYearMonth&>(object), seen_objects);
if (is<JS::Temporal::TimeZone>(object))
return print_temporal_time_zone(print_context, static_cast<JS::Temporal::TimeZone&>(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);
if (is<JS::Intl::DisplayNames>(object)) if (is<JS::Intl::DisplayNames>(object))
return print_intl_display_names(print_context, static_cast<JS::Intl::DisplayNames&>(object), seen_objects); return print_intl_display_names(print_context, static_cast<JS::Intl::DisplayNames&>(object), seen_objects);
if (is<JS::Intl::Locale>(object)) if (is<JS::Intl::Locale>(object))
@ -1067,6 +950,8 @@ ErrorOr<void> print_value(JS::PrintContext& print_context, JS::Value value, Hash
return print_intl_segments(print_context, static_cast<JS::Intl::Segments&>(object), seen_objects); return print_intl_segments(print_context, static_cast<JS::Intl::Segments&>(object), seen_objects);
if (is<JS::Intl::DurationFormat>(object)) if (is<JS::Intl::DurationFormat>(object))
return print_intl_duration_format(print_context, static_cast<JS::Intl::DurationFormat&>(object), seen_objects); return print_intl_duration_format(print_context, static_cast<JS::Intl::DurationFormat&>(object), seen_objects);
if (is<JS::Temporal::Duration>(object))
return print_temporal_duration(print_context, static_cast<JS::Temporal::Duration&>(object), seen_objects);
return print_object(print_context, object, seen_objects); return print_object(print_context, object, seen_objects);
} }

View file

@ -21,7 +21,6 @@
#include <LibJS/Runtime/GlobalObject.h> #include <LibJS/Runtime/GlobalObject.h>
#include <LibJS/Runtime/Intl/DateTimeFormat.h> #include <LibJS/Runtime/Intl/DateTimeFormat.h>
#include <LibJS/Runtime/Intl/DateTimeFormatConstructor.h> #include <LibJS/Runtime/Intl/DateTimeFormatConstructor.h>
#include <LibJS/Runtime/Temporal/Instant.h>
#include <LibJS/Runtime/Value.h> #include <LibJS/Runtime/Value.h>
#include <LibJS/Runtime/ValueInlines.h> #include <LibJS/Runtime/ValueInlines.h>
#include <LibUnicode/DisplayNames.h> #include <LibUnicode/DisplayNames.h>
@ -82,7 +81,6 @@ void DatePrototype::initialize(Realm& realm)
define_native_function(realm, vm.names.toLocaleString, to_locale_string, 0, attr); define_native_function(realm, vm.names.toLocaleString, to_locale_string, 0, attr);
define_native_function(realm, vm.names.toLocaleTimeString, to_locale_time_string, 0, attr); define_native_function(realm, vm.names.toLocaleTimeString, to_locale_time_string, 0, attr);
define_native_function(realm, vm.names.toString, to_string, 0, attr); define_native_function(realm, vm.names.toString, to_string, 0, attr);
define_native_function(realm, vm.names.toTemporalInstant, to_temporal_instant, 0, attr);
define_native_function(realm, vm.names.toTimeString, to_time_string, 0, attr); define_native_function(realm, vm.names.toTimeString, to_time_string, 0, attr);
define_native_function(realm, vm.names.toUTCString, to_utc_string, 0, attr); define_native_function(realm, vm.names.toUTCString, to_utc_string, 0, attr);
@ -1179,20 +1177,6 @@ ByteString to_date_string(double time)
return ByteString::formatted("{} {}{}", date_string(time), time_string(time), time_zone_string(time)); return ByteString::formatted("{} {}{}", date_string(time), time_string(time), time_zone_string(time));
} }
// 14.1.1 Date.prototype.toTemporalInstant ( ), https://tc39.es/proposal-temporal/#sec-date.prototype.totemporalinstant
JS_DEFINE_NATIVE_FUNCTION(DatePrototype::to_temporal_instant)
{
// 1. Let t be ? thisTimeValue(this value).
auto t = TRY(this_time_value(vm, vm.this_value()));
// 2. Let ns be ? NumberToBigInt(t) × (10^6).
auto* ns = TRY(number_to_bigint(vm, Value(t)));
ns = BigInt::create(vm, ns->big_integer().multiplied_by(Crypto::UnsignedBigInteger { 1'000'000 }));
// 3. Return ! CreateTemporalInstant(ns).
return MUST(Temporal::create_temporal_instant(vm, *ns));
}
// 21.4.4.42 Date.prototype.toTimeString ( ), https://tc39.es/ecma262/#sec-date.prototype.totimestring // 21.4.4.42 Date.prototype.toTimeString ( ), https://tc39.es/ecma262/#sec-date.prototype.totimestring
JS_DEFINE_NATIVE_FUNCTION(DatePrototype::to_time_string) JS_DEFINE_NATIVE_FUNCTION(DatePrototype::to_time_string)
{ {

View file

@ -62,7 +62,6 @@ private:
JS_DECLARE_NATIVE_FUNCTION(to_locale_string); JS_DECLARE_NATIVE_FUNCTION(to_locale_string);
JS_DECLARE_NATIVE_FUNCTION(to_locale_time_string); JS_DECLARE_NATIVE_FUNCTION(to_locale_time_string);
JS_DECLARE_NATIVE_FUNCTION(to_string); JS_DECLARE_NATIVE_FUNCTION(to_string);
JS_DECLARE_NATIVE_FUNCTION(to_temporal_instant);
JS_DECLARE_NATIVE_FUNCTION(to_time_string); JS_DECLARE_NATIVE_FUNCTION(to_time_string);
JS_DECLARE_NATIVE_FUNCTION(to_utc_string); JS_DECLARE_NATIVE_FUNCTION(to_utc_string);

View file

@ -261,6 +261,7 @@
M(TemporalInvalidInstantString, "Invalid instant string '{}'") \ M(TemporalInvalidInstantString, "Invalid instant string '{}'") \
M(TemporalInvalidISODate, "Invalid ISO date") \ M(TemporalInvalidISODate, "Invalid ISO date") \
M(TemporalInvalidISODateTime, "Invalid ISO date time") \ M(TemporalInvalidISODateTime, "Invalid ISO date time") \
M(TemporalInvalidLargestUnit, "Largest unit must not be {}") \
M(TemporalInvalidMonthCode, "Invalid month code") \ M(TemporalInvalidMonthCode, "Invalid month code") \
M(TemporalInvalidMonthDayString, "Invalid month day string '{}'") \ M(TemporalInvalidMonthDayString, "Invalid month day string '{}'") \
M(TemporalInvalidMonthDayStringUTCDesignator, "Invalid month day string '{}': must not contain a UTC designator") \ M(TemporalInvalidMonthDayStringUTCDesignator, "Invalid month day string '{}': must not contain a UTC designator") \
@ -286,7 +287,7 @@
M(TemporalOnlyISO8601WithMonthDayString, "MM-DD string format can only be used with the iso8601 calendar") \ M(TemporalOnlyISO8601WithMonthDayString, "MM-DD string format can only be used with the iso8601 calendar") \
M(TemporalOnlyISO8601WithYearMonthString, "YYYY-MM string format can only be used with the iso8601 calendar") \ M(TemporalOnlyISO8601WithYearMonthString, "YYYY-MM string format can only be used with the iso8601 calendar") \
M(TemporalMissingOptionsObject, "Required options object is missing or undefined") \ M(TemporalMissingOptionsObject, "Required options object is missing or undefined") \
M(TemporalMissingStartingPoint, "A starting point is required for balancing {}") \ M(TemporalMissingStartingPoint, "A starting point is required for comparing {}") \
M(TemporalMissingUnits, "One or both of smallestUnit or largestUnit is required") \ M(TemporalMissingUnits, "One or both of smallestUnit or largestUnit is required") \
M(TemporalNanosecondsConvertedToDaysWithOppositeSign, "Time zone or calendar converted nanoseconds into a number of days with " \ M(TemporalNanosecondsConvertedToDaysWithOppositeSign, "Time zone or calendar converted nanoseconds into a number of days with " \
"the opposite sign") \ "the opposite sign") \

View file

@ -67,17 +67,7 @@
#include <LibJS/Runtime/StringPrototype.h> #include <LibJS/Runtime/StringPrototype.h>
#include <LibJS/Runtime/SuppressedErrorConstructor.h> #include <LibJS/Runtime/SuppressedErrorConstructor.h>
#include <LibJS/Runtime/SymbolConstructor.h> #include <LibJS/Runtime/SymbolConstructor.h>
#include <LibJS/Runtime/Temporal/CalendarConstructor.h>
#include <LibJS/Runtime/Temporal/DurationConstructor.h>
#include <LibJS/Runtime/Temporal/InstantConstructor.h>
#include <LibJS/Runtime/Temporal/PlainDateConstructor.h>
#include <LibJS/Runtime/Temporal/PlainDateTimeConstructor.h>
#include <LibJS/Runtime/Temporal/PlainMonthDayConstructor.h>
#include <LibJS/Runtime/Temporal/PlainTimeConstructor.h>
#include <LibJS/Runtime/Temporal/PlainYearMonthConstructor.h>
#include <LibJS/Runtime/Temporal/Temporal.h> #include <LibJS/Runtime/Temporal/Temporal.h>
#include <LibJS/Runtime/Temporal/TimeZoneConstructor.h>
#include <LibJS/Runtime/Temporal/ZonedDateTimeConstructor.h>
#include <LibJS/Runtime/TypedArray.h> #include <LibJS/Runtime/TypedArray.h>
#include <LibJS/Runtime/Value.h> #include <LibJS/Runtime/Value.h>
#include <LibJS/Runtime/ValueInlines.h> #include <LibJS/Runtime/ValueInlines.h>

View file

@ -64,7 +64,7 @@ ThrowCompletionOr<GC::Ref<Object>> DisplayNamesConstructor::construct(FunctionOb
return vm.throw_completion<TypeError>(ErrorType::IsUndefined, "options"sv); return vm.throw_completion<TypeError>(ErrorType::IsUndefined, "options"sv);
// 5. Set options to ? GetOptionsObject(options). // 5. Set options to ? GetOptionsObject(options).
auto* options = TRY(Temporal::get_options_object(vm, options_value)); auto options = TRY(Temporal::get_options_object(vm, options_value));
// 6. Let opt be a new Record. // 6. Let opt be a new Record.
LocaleOptions opt {}; LocaleOptions opt {};

View file

@ -59,7 +59,7 @@ ThrowCompletionOr<GC::Ref<Object>> DurationFormatConstructor::construct(Function
auto requested_locales = TRY(canonicalize_locale_list(vm, locales)); auto requested_locales = TRY(canonicalize_locale_list(vm, locales));
// 4. Let options be ? GetOptionsObject(options). // 4. Let options be ? GetOptionsObject(options).
auto* options = TRY(Temporal::get_options_object(vm, options_value)); auto options = TRY(Temporal::get_options_object(vm, options_value));
// 5. Let matcher be ? GetOption(options, "localeMatcher", string, « "lookup", "best fit" », "best fit"). // 5. Let matcher be ? GetOption(options, "localeMatcher", string, « "lookup", "best fit" », "best fit").
auto matcher = TRY(get_option(vm, *options, vm.names.localeMatcher, OptionType::String, { "lookup"sv, "best fit"sv }, "best fit"sv)); auto matcher = TRY(get_option(vm, *options, vm.names.localeMatcher, OptionType::String, { "lookup"sv, "best fit"sv }, "best fit"sv));

View file

@ -59,7 +59,7 @@ ThrowCompletionOr<GC::Ref<Object>> ListFormatConstructor::construct(FunctionObje
auto requested_locales = TRY(canonicalize_locale_list(vm, locale_value)); auto requested_locales = TRY(canonicalize_locale_list(vm, locale_value));
// 4. Set options to ? GetOptionsObject(options). // 4. Set options to ? GetOptionsObject(options).
auto* options = TRY(Temporal::get_options_object(vm, options_value)); auto options = TRY(Temporal::get_options_object(vm, options_value));
// 5. Let opt be a new Record. // 5. Let opt be a new Record.
LocaleOptions opt {}; LocaleOptions opt {};

View file

@ -60,7 +60,7 @@ ThrowCompletionOr<GC::Ref<Object>> SegmenterConstructor::construct(FunctionObjec
auto requested_locales = TRY(canonicalize_locale_list(vm, locales)); auto requested_locales = TRY(canonicalize_locale_list(vm, locales));
// 5. Set options to ? GetOptionsObject(options). // 5. Set options to ? GetOptionsObject(options).
auto* options = TRY(Temporal::get_options_object(vm, options_value)); auto options = TRY(Temporal::get_options_object(vm, options_value));
// 6. Let opt be a new Record. // 6. Let opt be a new Record.
LocaleOptions opt {}; LocaleOptions opt {};

View file

@ -99,27 +99,9 @@
#include <LibJS/Runtime/SuppressedErrorPrototype.h> #include <LibJS/Runtime/SuppressedErrorPrototype.h>
#include <LibJS/Runtime/SymbolConstructor.h> #include <LibJS/Runtime/SymbolConstructor.h>
#include <LibJS/Runtime/SymbolPrototype.h> #include <LibJS/Runtime/SymbolPrototype.h>
#include <LibJS/Runtime/Temporal/CalendarConstructor.h>
#include <LibJS/Runtime/Temporal/CalendarPrototype.h>
#include <LibJS/Runtime/Temporal/DurationConstructor.h> #include <LibJS/Runtime/Temporal/DurationConstructor.h>
#include <LibJS/Runtime/Temporal/DurationPrototype.h> #include <LibJS/Runtime/Temporal/DurationPrototype.h>
#include <LibJS/Runtime/Temporal/InstantConstructor.h>
#include <LibJS/Runtime/Temporal/InstantPrototype.h>
#include <LibJS/Runtime/Temporal/PlainDateConstructor.h>
#include <LibJS/Runtime/Temporal/PlainDatePrototype.h>
#include <LibJS/Runtime/Temporal/PlainDateTimeConstructor.h>
#include <LibJS/Runtime/Temporal/PlainDateTimePrototype.h>
#include <LibJS/Runtime/Temporal/PlainMonthDayConstructor.h>
#include <LibJS/Runtime/Temporal/PlainMonthDayPrototype.h>
#include <LibJS/Runtime/Temporal/PlainTimeConstructor.h>
#include <LibJS/Runtime/Temporal/PlainTimePrototype.h>
#include <LibJS/Runtime/Temporal/PlainYearMonthConstructor.h>
#include <LibJS/Runtime/Temporal/PlainYearMonthPrototype.h>
#include <LibJS/Runtime/Temporal/Temporal.h> #include <LibJS/Runtime/Temporal/Temporal.h>
#include <LibJS/Runtime/Temporal/TimeZoneConstructor.h>
#include <LibJS/Runtime/Temporal/TimeZonePrototype.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>

File diff suppressed because it is too large Load diff

View file

@ -1,21 +1,22 @@
/* /*
* Copyright (c) 2021, Idan Horowitz <idan.horowitz@serenityos.org> * Copyright (c) 2021, Idan Horowitz <idan.horowitz@serenityos.org>
* Copyright (c) 2021-2023, Linus Groh <linusg@serenityos.org> * Copyright (c) 2021-2023, Linus Groh <linusg@serenityos.org>
* Copyright (c) 2024, Tim Flynn <trflynn89@ladybird.org>
* *
* SPDX-License-Identifier: BSD-2-Clause * SPDX-License-Identifier: BSD-2-Clause
*/ */
#pragma once #pragma once
#include <AK/Forward.h>
#include <AK/Variant.h> #include <AK/Variant.h>
#include <LibCrypto/BigInt/SignedBigInteger.h> #include <LibCrypto/BigInt/SignedBigInteger.h>
#include <LibCrypto/BigInt/UnsignedBigInteger.h>
#include <LibGC/Ptr.h>
#include <LibJS/Forward.h> #include <LibJS/Forward.h>
#include <LibJS/Runtime/Completion.h> #include <LibJS/Runtime/Completion.h>
#include <LibJS/Runtime/GlobalObject.h> #include <LibJS/Runtime/VM.h>
#include <LibJS/Runtime/Temporal/ISO8601.h>
#include <LibJS/Runtime/Temporal/TimeZoneMethods.h>
#include <LibJS/Runtime/ValueInlines.h> #include <LibJS/Runtime/ValueInlines.h>
#include <math.h>
namespace JS::Temporal { namespace JS::Temporal {
@ -24,11 +25,48 @@ enum class ArithmeticOperation {
Subtract, Subtract,
}; };
enum class DifferenceOperation { // https://tc39.es/proposal-temporal/#sec-temporal-units
Since, enum class Unit {
Until, Year,
Month,
Week,
Day,
Hour,
Minute,
Second,
Millisecond,
Microsecond,
Nanosecond,
};
StringView temporal_unit_to_string(Unit);
// https://tc39.es/proposal-temporal/#sec-temporal-units
enum class UnitCategory {
Date,
Time,
}; };
// https://tc39.es/proposal-temporal/#sec-temporal-units
enum class UnitGroup {
Date,
Time,
DateTime,
};
// https://tc39.es/proposal-temporal/#table-unsigned-rounding-modes
enum class RoundingMode {
Ceil,
Floor,
Expand,
Trunc,
HalfCeil,
HalfFloor,
HalfExpand,
HalfTrunc,
HalfEven,
};
// https://tc39.es/proposal-temporal/#table-unsigned-rounding-modes
enum class UnsignedRoundingMode { enum class UnsignedRoundingMode {
HalfEven, HalfEven,
HalfInfinity, HalfInfinity,
@ -37,157 +75,108 @@ enum class UnsignedRoundingMode {
Zero, Zero,
}; };
// https://tc39.es/proposal-temporal/#table-unsigned-rounding-modes
enum class Sign {
Negative,
Positive,
};
struct Auto { };
struct Required { };
struct Unset { };
using Precision = Variant<Auto, u8>;
using RoundingIncrement = Variant<Unset, u64>;
using UnitDefault = Variant<Required, Unset, Auto, Unit>;
using UnitValue = Variant<Unset, Auto, Unit>;
struct SecondsStringPrecision {
struct Minute { };
Variant<Minute, Auto, u8> precision;
Unit unit;
u8 increment { 0 };
};
struct RelativeTo {
// FIXME: Make these objects represent their actual types when we re-implement them.
GC::Ptr<JS::Object> plain_relative_to; // [[PlainRelativeTo]]
GC::Ptr<JS::Object> zoned_relative_to; // [[ZonedRelativeTo]]
};
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);
SecondsStringPrecision to_seconds_string_precision_record(UnitValue, Precision);
ThrowCompletionOr<UnitValue> get_temporal_unit_valued_option(VM&, Object const& options, PropertyKey const&, UnitGroup, UnitDefault const&, ReadonlySpan<UnitValue> extra_values = {});
ThrowCompletionOr<RelativeTo> get_temporal_relative_to_option(VM&, Object const& options);
Unit larger_of_two_temporal_units(Unit, Unit);
bool is_calendar_unit(Unit);
UnitCategory temporal_unit_category(Unit);
RoundingIncrement maximum_temporal_duration_rounding_increment(Unit);
Crypto::UnsignedBigInteger const& temporal_unit_length_in_nanoseconds(Unit);
String format_fractional_seconds(u64, Precision);
UnsignedRoundingMode get_unsigned_rounding_mode(RoundingMode, Sign);
double apply_unsigned_rounding_mode(double, double r1, double r2, UnsignedRoundingMode);
Crypto::SignedBigInteger apply_unsigned_rounding_mode(Crypto::SignedDivisionResult const&, Crypto::SignedBigInteger const& r1, Crypto::SignedBigInteger const& r2, UnsignedRoundingMode, Crypto::UnsignedBigInteger const& increment);
double round_number_to_increment(double, u64 increment, RoundingMode);
Crypto::SignedBigInteger round_number_to_increment(Crypto::SignedBigInteger const&, Crypto::UnsignedBigInteger const& increment, RoundingMode);
ThrowCompletionOr<GC::Ref<Duration>> parse_temporal_duration_string(VM&, StringView iso_string);
// 13.38 ToIntegerWithTruncation ( argument ), https://tc39.es/proposal-temporal/#sec-tointegerwithtruncation
template<typename... Args>
ThrowCompletionOr<double> to_integer_with_truncation(VM& vm, Value argument, ErrorType error_type, Args&&... args)
{
// 1. Let number be ? ToNumber(argument).
auto number = TRY(argument.to_number(vm));
// 2. If number is NaN, +∞𝔽 or -∞𝔽, throw a RangeError exception.
if (number.is_nan() || number.is_infinity())
return vm.throw_completion<RangeError>(error_type, forward<Args>(args)...);
// 3. Return truncate((number)).
return trunc(number.as_double());
}
// 13.38 ToIntegerWithTruncation ( argument ), https://tc39.es/proposal-temporal/#sec-tointegerwithtruncation
// AD-HOC: We often need to use this AO when we have a parsed StringView. This overload allows callers to avoid creating
// a PrimitiveString for the primary definition.
template<typename... Args>
ThrowCompletionOr<double> to_integer_with_truncation(VM& vm, StringView argument, ErrorType error_type, Args&&... args)
{
// 1. Let number be ? ToNumber(argument).
auto number = string_to_number(argument);
// 2. If number is NaN, +∞𝔽 or -∞𝔽, throw a RangeError exception.
if (isnan(number) || isinf(number))
return vm.throw_completion<RangeError>(error_type, forward<Args>(args)...);
// 3. Return truncate((number)).
return trunc(number);
}
// 13.39 ToIntegerIfIntegral ( argument ), https://tc39.es/proposal-temporal/#sec-tointegerifintegral
template<typename... Args>
ThrowCompletionOr<double> to_integer_if_integral(VM& vm, Value argument, ErrorType error_type, Args&&... args)
{
// 1. Let number be ? ToNumber(argument).
auto number = TRY(argument.to_number(vm));
// 2. If number is not an integral Number, throw a RangeError exception.
if (!number.is_integral_number())
return vm.throw_completion<RangeError>(error_type, forward<Args>(args)...);
// 3. Return (number).
return number.as_double();
}
enum class OptionType { enum class OptionType {
Boolean, Boolean,
String, String,
Number
}; };
enum class UnitGroup { using OptionDefault = Variant<Required, Empty, bool, StringView, double>;
Date,
Time,
DateTime,
};
struct TemporalInstant { ThrowCompletionOr<GC::Ref<Object>> get_options_object(VM&, Value options);
i32 year;
u8 month;
u8 day;
u8 hour;
u8 minute;
u8 second;
u16 millisecond;
u16 microsecond;
u16 nanosecond;
Optional<String> time_zone_offset;
};
struct TemporalDate {
i32 year;
u8 month;
u8 day;
Optional<String> calendar;
};
struct TemporalTime {
u8 hour;
u8 minute;
u8 second;
u16 millisecond;
u16 microsecond;
u16 nanosecond;
Optional<String> calendar = {};
};
struct TemporalTimeZone {
bool z;
Optional<String> offset_string;
Optional<String> name;
};
struct TemporalYearMonth {
i32 year;
u8 month;
u8 day;
Optional<String> calendar = {};
};
struct TemporalMonthDay {
Optional<i32> year;
u8 month;
u8 day;
Optional<String> calendar = {};
};
struct ISODateTime {
i32 year;
u8 month;
u8 day;
u8 hour;
u8 minute;
u8 second;
u16 millisecond;
u16 microsecond;
u16 nanosecond;
TemporalTimeZone time_zone { .z = false, .offset_string = {}, .name = {} };
Optional<String> calendar = {};
};
struct SecondsStringPrecision {
Variant<StringView, u8> precision;
StringView unit;
u32 increment;
};
struct DifferenceSettings {
String smallest_unit;
String largest_unit;
String rounding_mode;
u64 rounding_increment;
GC::Ref<Object> options;
};
struct TemporalUnitRequired { };
struct PrepareTemporalFieldsPartial { };
struct GetOptionRequired { };
using OptionDefault = Variant<GetOptionRequired, Empty, bool, StringView, double>;
using TemporalUnitDefault = Variant<TemporalUnitRequired, Optional<StringView>>;
ThrowCompletionOr<GC::MarkedVector<Value>> iterable_to_list_of_type(VM&, Value items, Vector<OptionType> const& element_types);
ThrowCompletionOr<Object*> get_options_object(VM&, Value options);
ThrowCompletionOr<Value> get_option(VM&, Object const& options, PropertyKey const& property, OptionType type, ReadonlySpan<StringView> values, OptionDefault const&); ThrowCompletionOr<Value> get_option(VM&, Object const& options, PropertyKey const& property, OptionType type, ReadonlySpan<StringView> values, OptionDefault const&);
ThrowCompletionOr<String> to_temporal_overflow(VM&, Object const* options);
ThrowCompletionOr<String> to_temporal_disambiguation(VM&, Object const* options);
ThrowCompletionOr<String> to_temporal_rounding_mode(VM&, Object const& normalized_options, StringView fallback);
StringView negate_temporal_rounding_mode(StringView rounding_mode);
ThrowCompletionOr<String> to_temporal_offset(VM&, Object const* options, StringView fallback);
ThrowCompletionOr<String> to_calendar_name_option(VM&, Object const& normalized_options);
ThrowCompletionOr<String> to_time_zone_name_option(VM&, Object const& normalized_options);
ThrowCompletionOr<String> to_show_offset_option(VM&, Object const& normalized_options);
ThrowCompletionOr<u64> to_temporal_rounding_increment(VM& vm, Object const& normalized_options);
ThrowCompletionOr<void> validate_temporal_rounding_increment(VM& vm, u64 increment, u64 dividend, bool inclusive);
ThrowCompletionOr<SecondsStringPrecision> to_seconds_string_precision_record(VM&, Object const& normalized_options);
ThrowCompletionOr<Optional<String>> get_temporal_unit(VM&, Object const& normalized_options, PropertyKey const&, UnitGroup, TemporalUnitDefault const& default_, Vector<StringView> const& extra_values = {});
struct RelativeTo {
GC::Ptr<PlainDate> plain_relative_to; // [[PlainRelativeTo]]
GC::Ptr<ZonedDateTime> zoned_relative_to; // [[ZonedRelativeTo]]
Optional<TimeZoneMethods> time_zone_record; // [[TimeZoneRec]]
};
ThrowCompletionOr<RelativeTo> to_relative_temporal_object(VM&, Object const& options);
Value relative_to_converted_to_value(RelativeTo const&);
StringView larger_of_two_temporal_units(StringView, StringView);
ThrowCompletionOr<Object*> merge_largest_unit_option(VM&, Object const& options, String largest_unit);
Optional<u16> maximum_temporal_duration_rounding_increment(StringView unit);
ThrowCompletionOr<void> reject_object_with_calendar_or_time_zone(VM&, Object&);
ThrowCompletionOr<String> format_seconds_string_part(VM&, u8 second, u16 millisecond, u16 microsecond, u16 nanosecond, Variant<StringView, u8> const& precision);
double sign(double);
double sign(Crypto::SignedBigInteger const&);
UnsignedRoundingMode get_unsigned_rounding_mode(StringView rounding_mode, bool is_negative);
double apply_unsigned_rounding_mode(double x, double r1, double r2, Optional<UnsignedRoundingMode> const&);
Crypto::SignedBigInteger apply_unsigned_rounding_mode(Crypto::SignedDivisionResult const&, Crypto::SignedBigInteger const& r1, Crypto::SignedBigInteger const& r2, Optional<UnsignedRoundingMode> const&, Crypto::UnsignedBigInteger const& increment);
double round_number_to_increment(double, u64 increment, StringView rounding_mode);
Crypto::SignedBigInteger round_number_to_increment(Crypto::SignedBigInteger const&, u64 increment, StringView rounding_mode);
Crypto::SignedBigInteger round_number_to_increment_as_if_positive(Crypto::SignedBigInteger const&, u64 increment, StringView rounding_mode);
ThrowCompletionOr<ISODateTime> parse_iso_date_time(VM&, StringView iso_string);
ThrowCompletionOr<ISODateTime> parse_iso_date_time(VM&, ParseResult const& parse_result);
ThrowCompletionOr<TemporalInstant> parse_temporal_instant_string(VM&, StringView iso_string);
ThrowCompletionOr<ISODateTime> parse_temporal_zoned_date_time_string(VM&, StringView iso_string);
ThrowCompletionOr<String> parse_temporal_calendar_string(VM&, StringView iso_string);
ThrowCompletionOr<TemporalDate> parse_temporal_date_string(VM&, StringView iso_string);
ThrowCompletionOr<ISODateTime> parse_temporal_date_time_string(VM&, StringView iso_string);
ThrowCompletionOr<DurationRecord> parse_temporal_duration_string(VM&, StringView iso_string);
ThrowCompletionOr<TemporalMonthDay> parse_temporal_month_day_string(VM&, StringView iso_string);
ThrowCompletionOr<ISODateTime> parse_temporal_relative_to_string(VM&, StringView iso_string);
ThrowCompletionOr<TemporalTime> parse_temporal_time_string(VM&, StringView iso_string);
ThrowCompletionOr<TemporalTimeZone> parse_temporal_time_zone_string(VM&, StringView iso_string);
ThrowCompletionOr<TemporalYearMonth> parse_temporal_year_month_string(VM&, StringView iso_string);
ThrowCompletionOr<double> to_positive_integer_with_truncation(VM&, Value argument);
ThrowCompletionOr<Object*> prepare_temporal_fields(VM&, Object const& fields, Vector<String> const& field_names, Variant<PrepareTemporalFieldsPartial, Vector<StringView>> const& required_fields);
ThrowCompletionOr<DifferenceSettings> get_difference_settings(VM&, DifferenceOperation, Value options_value, UnitGroup unit_group, Vector<StringView> const& disallowed_units, TemporalUnitDefault const& fallback_smallest_unit, StringView smallest_largest_default_unit);
template<size_t Size> template<size_t Size>
ThrowCompletionOr<Value> get_option(VM& vm, Object const& options, PropertyKey const& property, OptionType type, StringView const (&values)[Size], OptionDefault const& default_) ThrowCompletionOr<Value> get_option(VM& vm, Object const& options, PropertyKey const& property, OptionType type, StringView const (&values)[Size], OptionDefault const& default_)
@ -195,43 +184,7 @@ ThrowCompletionOr<Value> get_option(VM& vm, Object const& options, PropertyKey c
return get_option(vm, options, property, type, ReadonlySpan<StringView> { values }, default_); return get_option(vm, options, property, type, ReadonlySpan<StringView> { values }, default_);
} }
// 13.40 ToIntegerWithTruncation ( argument ), https://tc39.es/proposal-temporal/#sec-tointegerwithtruncation ThrowCompletionOr<RoundingMode> get_rounding_mode_option(VM&, Object const& options, RoundingMode fallback);
template<typename... Args> ThrowCompletionOr<u64> get_rounding_increment_option(VM&, Object const& options);
ThrowCompletionOr<double> to_integer_with_truncation(VM& vm, Value argument, ErrorType error_type, Args... args)
{
// 1. Let number be ? ToIntegerOrInfinity(argument).
auto number = TRY(argument.to_number(vm));
// 2. If number is NaN, return 0.
if (number.is_nan())
return 0;
// 3. If number is +∞𝔽 or -∞𝔽, throw a RangeError exception.
if (Value(number).is_infinity()) {
return vm.template throw_completion<RangeError>(error_type, args...);
}
// 4. Return truncate((number)).
return trunc(number.as_double());
}
// 13.41 ToIntegerIfIntegral ( argument ), https://tc39.es/proposal-temporal/#sec-tointegerifintegral
template<typename... Args>
ThrowCompletionOr<double> to_integer_if_integral(VM& vm, Value argument, ErrorType error_type, Args... args)
{
// 1. Let number be ? ToNumber(argument).
auto number = TRY(argument.to_number(vm));
// 2. If number is NaN, +0𝔽, or -0𝔽, return 0.
if (number.is_nan() || number.is_positive_zero() || number.is_negative_zero())
return 0;
// 3. If IsIntegralNumber(number) is false, throw a RangeError exception.
if (!number.is_integral_number())
return vm.template throw_completion<RangeError>(error_type, args...);
// 4. Return (number).
return number.as_double();
}
} }

File diff suppressed because it is too large Load diff

View file

@ -1,141 +0,0 @@
/*
* Copyright (c) 2021, Idan Horowitz <idan.horowitz@serenityos.org>
* Copyright (c) 2021-2023, Linus Groh <linusg@serenityos.org>
* Copyright (c) 2023-2024, Shannon Booth <shannon@serenityos.org>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#pragma once
#include <LibJS/Runtime/Completion.h>
#include <LibJS/Runtime/Object.h>
#include <LibJS/Runtime/Temporal/PlainDate.h>
#include <LibJS/Runtime/Temporal/PlainMonthDay.h>
#include <LibJS/Runtime/Temporal/PlainYearMonth.h>
#include <LibJS/Runtime/Value.h>
namespace JS::Temporal {
class Calendar final : public Object {
JS_OBJECT(Calendar, Object);
GC_DECLARE_ALLOCATOR(Calendar);
public:
virtual ~Calendar() override = default;
[[nodiscard]] String const& identifier() const { return m_identifier; }
private:
Calendar(String identifier, Object& prototype);
// 12.5 Properties of Temporal.Calendar Instances, https://tc39.es/proposal-temporal/#sec-properties-of-temporal-calendar-instances
String m_identifier; // [[Identifier]]
};
// 14.2 The Year-Week Record Specification Type, https://tc39.es/proposal-temporal/#sec-year-week-record-specification-type
struct YearWeekRecord {
u8 week { 0 };
i32 year { 0 };
};
bool is_builtin_calendar(StringView identifier);
ReadonlySpan<StringView> available_calendars();
ThrowCompletionOr<Calendar*> create_temporal_calendar(VM&, String const& identifier, FunctionObject const* new_target = nullptr);
ThrowCompletionOr<Calendar*> get_builtin_calendar(VM&, String const& identifier);
Calendar* get_iso8601_calendar(VM&);
ThrowCompletionOr<Vector<String>> calendar_fields(VM&, Object& calendar, Vector<StringView> const& field_names);
ThrowCompletionOr<Object*> calendar_merge_fields(VM&, Object& calendar, Object& fields, Object& additional_fields);
ThrowCompletionOr<PlainDate*> calendar_date_add(VM&, Object& calendar, Value date, Duration&, Object* options = nullptr, FunctionObject* date_add = nullptr);
ThrowCompletionOr<GC::Ref<Duration>> calendar_date_until(VM&, CalendarMethods const&, Value one, Value two, Object const& options);
ThrowCompletionOr<double> calendar_year(VM&, Object& calendar, Object& date_like);
ThrowCompletionOr<double> calendar_month(VM&, Object& calendar, Object& date_like);
ThrowCompletionOr<String> calendar_month_code(VM&, Object& calendar, Object& date_like);
ThrowCompletionOr<double> calendar_day(VM&, Object& calendar, Object& date_like);
ThrowCompletionOr<double> calendar_day_of_week(VM&, Object& calendar, Object& date_like);
ThrowCompletionOr<double> calendar_day_of_year(VM&, Object& calendar, Object& date_like);
ThrowCompletionOr<double> calendar_week_of_year(VM&, Object& calendar, Object& date_like);
ThrowCompletionOr<double> calendar_year_of_week(VM&, Object& calendar, Object& date_like);
ThrowCompletionOr<double> calendar_days_in_week(VM&, Object& calendar, Object& date_like);
ThrowCompletionOr<double> calendar_days_in_month(VM&, Object& calendar, Object& date_like);
ThrowCompletionOr<double> calendar_days_in_year(VM&, Object& calendar, Object& date_like);
ThrowCompletionOr<double> calendar_months_in_year(VM&, Object& calendar, Object& date_like);
ThrowCompletionOr<Value> calendar_in_leap_year(VM&, Object& calendar, Object& date_like);
ThrowCompletionOr<Value> calendar_era(VM&, Object& calendar, Object& date_like);
ThrowCompletionOr<Value> calendar_era_year(VM&, Object& calendar, Object& date_like);
ThrowCompletionOr<Object*> to_temporal_calendar(VM&, Value);
ThrowCompletionOr<Object*> to_temporal_calendar_with_iso_default(VM&, Value);
ThrowCompletionOr<Object*> get_temporal_calendar_with_iso_default(VM&, Object&);
ThrowCompletionOr<PlainDate*> calendar_date_from_fields(VM&, Object& calendar, Object const& fields, Object const* options = nullptr);
ThrowCompletionOr<PlainYearMonth*> calendar_year_month_from_fields(VM&, Object& calendar, Object const& fields, Object const* options = nullptr);
ThrowCompletionOr<PlainMonthDay*> calendar_month_day_from_fields(VM&, Object& calendar, Object const& fields, Object const* options = nullptr);
ThrowCompletionOr<String> maybe_format_calendar_annotation(VM&, Object const* calendar_object, StringView show_calendar);
ThrowCompletionOr<String> format_calendar_annotation(VM&, StringView id, StringView show_calendar);
ThrowCompletionOr<bool> calendar_equals(VM&, Object& one, Object& two);
ThrowCompletionOr<Object*> consolidate_calendars(VM&, Object& one, Object& two);
u8 iso_days_in_month(i32 year, u8 month);
YearWeekRecord to_iso_week_of_year(i32 year, u8 month, u8 day);
ThrowCompletionOr<String> iso_month_code(VM&, u8 month);
ThrowCompletionOr<double> resolve_iso_month(VM&, Object const& fields);
ThrowCompletionOr<ISODateRecord> iso_date_from_fields(VM&, Object const& fields, Object const& options);
ThrowCompletionOr<ISOYearMonth> iso_year_month_from_fields(VM&, Object const& fields, Object const& options);
ThrowCompletionOr<ISOMonthDay> iso_month_day_from_fields(VM&, Object const& fields, Object const& options);
ThrowCompletionOr<Object*> default_merge_calendar_fields(VM&, Object const& fields, Object const& additional_fields);
u16 to_iso_day_of_year(i32 year, u8 month, u8 day);
u8 to_iso_day_of_week(i32 year, u8 month, u8 day);
// https://tc39.es/proposal-temporal/#table-temporal-calendar-methods-record-fields
struct CalendarMethods {
// The calendar object, or a string indicating a built-in time zone.
Variant<String, GC::Ref<Object>> receiver; // [[Reciever]]
// The calendar's dateAdd method. For a built-in calendar this is always %Temporal.Calendar.prototype.dateAdd%.
GC::Ptr<FunctionObject> date_add; // [[DateAdd]]
// The calendar's dateFromFields method. For a built-in calendar this is always %Temporal.Calendar.prototype.dateFromFields%.
GC::Ptr<FunctionObject> date_from_fields; // [[DateFromFields]]
// The calendar's dateUntil method. For a built-in calendar this is always %Temporal.Calendar.prototype.dateUntil%.
GC::Ptr<FunctionObject> date_until; // [[DateUntil]]
// The calendar's day method. For a built-in calendar this is always %Temporal.Calendar.prototype.day%.
GC::Ptr<FunctionObject> day; // [[Day]]
// The calendar's fields method. For a built-in calendar this is always %Temporal.Calendar.prototype.fields%.
GC::Ptr<FunctionObject> fields; // [[Fields]]
// The calendar's mergeFields method. For a built-in calendar this is always %Temporal.Calendar.prototype.mergeFields%.
GC::Ptr<FunctionObject> merge_fields; // [[MergeFields]]
// The calendar's monthDayFromFields method. For a built-in calendar this is always %Temporal.Calendar.prototype.monthDayFromFields%.
GC::Ptr<FunctionObject> month_day_from_fields; // [[MonthDayFromFields]]
// The calendar's yearMonthFromFields method. For a built-in calendar this is always %Temporal.Calendar.prototype.yearMonthFromFields%.
GC::Ptr<FunctionObject> year_month_from_fields; // [[YearMonthFromFields]]
};
#define JS_ENUMERATE_CALENDAR_METHODS \
__JS_ENUMERATE(DateAdd, dateAdd, date_add) \
__JS_ENUMERATE(DateFromFields, dateFromFields, date_from_fields) \
__JS_ENUMERATE(DateUntil, dateUntil, date_until) \
__JS_ENUMERATE(Day, day, day) \
__JS_ENUMERATE(Fields, fields, fields) \
__JS_ENUMERATE(MergeFields, mergeFields, merge_fields) \
__JS_ENUMERATE(MonthDayFromFields, monthDayFromFields, month_day_from_fields) \
__JS_ENUMERATE(YearMonthFromFields, yearMonthFromFields, year_month_from_fields)
enum class CalendarMethod {
#define __JS_ENUMERATE(PascalName, camelName, snake_name) \
PascalName,
JS_ENUMERATE_CALENDAR_METHODS
#undef __JS_ENUMERATE
};
ThrowCompletionOr<void> calendar_methods_record_lookup(VM&, CalendarMethods&, CalendarMethod);
ThrowCompletionOr<CalendarMethods> create_calendar_methods_record(VM&, Variant<String, GC::Ref<Object>> calendar, ReadonlySpan<CalendarMethod>);
ThrowCompletionOr<Optional<CalendarMethods>> create_calendar_methods_record_from_relative_to(VM&, GC::Ptr<PlainDate>, GC::Ptr<ZonedDateTime>, ReadonlySpan<CalendarMethod>);
bool calendar_methods_record_has_looked_up(CalendarMethods const&, CalendarMethod);
bool calendar_methods_record_is_builtin(CalendarMethods const&);
ThrowCompletionOr<Value> calendar_methods_record_call(VM&, CalendarMethods const&, CalendarMethod, ReadonlySpan<Value> arguments);
}

View file

@ -1,73 +0,0 @@
/*
* Copyright (c) 2021-2023, Linus Groh <linusg@serenityos.org>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#include <LibJS/Runtime/GlobalObject.h>
#include <LibJS/Runtime/Temporal/Calendar.h>
#include <LibJS/Runtime/Temporal/CalendarConstructor.h>
namespace JS::Temporal {
GC_DEFINE_ALLOCATOR(CalendarConstructor);
// 12.2 The Temporal.Calendar Constructor, https://tc39.es/proposal-temporal/#sec-temporal-calendar-constructor
CalendarConstructor::CalendarConstructor(Realm& realm)
: NativeFunction(realm.vm().names.Calendar.as_string(), realm.intrinsics().function_prototype())
{
}
void CalendarConstructor::initialize(Realm& realm)
{
Base::initialize(realm);
auto& vm = this->vm();
// 12.3.1 Temporal.Calendar.prototype, https://tc39.es/proposal-temporal/#sec-temporal.calendar.prototype
define_direct_property(vm.names.prototype, realm.intrinsics().temporal_calendar_prototype(), 0);
u8 attr = Attribute::Writable | Attribute::Configurable;
define_native_function(realm, vm.names.from, from, 1, attr);
define_direct_property(vm.names.length, Value(1), Attribute::Configurable);
}
// 12.2.1 Temporal.Calendar ( id ), https://tc39.es/proposal-temporal/#sec-temporal.calendar
ThrowCompletionOr<Value> CalendarConstructor::call()
{
auto& vm = this->vm();
// 1. If NewTarget is undefined, then
// a. Throw a TypeError exception.
return vm.throw_completion<TypeError>(ErrorType::ConstructorWithoutNew, "Temporal.Calendar");
}
// 12.2.1 Temporal.Calendar ( id ), https://tc39.es/proposal-temporal/#sec-temporal.calendar
ThrowCompletionOr<GC::Ref<Object>> CalendarConstructor::construct(FunctionObject& new_target)
{
auto& vm = this->vm();
// 2. Set id to ? ToString(id).
auto identifier = TRY(vm.argument(0).to_string(vm));
// 3. If IsBuiltinCalendar(id) is false, then
if (!is_builtin_calendar(identifier)) {
// a. Throw a RangeError exception.
return vm.throw_completion<RangeError>(ErrorType::TemporalInvalidCalendarIdentifier, identifier);
}
// 4. Return ? CreateTemporalCalendar(id, NewTarget).
return *TRY(create_temporal_calendar(vm, identifier, &new_target));
}
// 12.3.2 Temporal.Calendar.from ( calendarLike ), https://tc39.es/proposal-temporal/#sec-temporal.calendar.from
JS_DEFINE_NATIVE_FUNCTION(CalendarConstructor::from)
{
auto calendar_like = vm.argument(0);
// 1. Return ? ToTemporalCalendar(calendarLike).
return TRY(to_temporal_calendar(vm, calendar_like));
}
}

View file

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

View file

@ -1,730 +0,0 @@
/*
* Copyright (c) 2021-2023, Linus Groh <linusg@serenityos.org>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#include <AK/TypeCasts.h>
#include <LibJS/Runtime/Array.h>
#include <LibJS/Runtime/GlobalObject.h>
#include <LibJS/Runtime/Iterator.h>
#include <LibJS/Runtime/Temporal/AbstractOperations.h>
#include <LibJS/Runtime/Temporal/Calendar.h>
#include <LibJS/Runtime/Temporal/CalendarPrototype.h>
#include <LibJS/Runtime/Temporal/Duration.h>
#include <LibJS/Runtime/Temporal/PlainDate.h>
#include <LibJS/Runtime/Temporal/PlainDateTime.h>
#include <LibJS/Runtime/Temporal/PlainMonthDay.h>
#include <LibJS/Runtime/Temporal/PlainYearMonth.h>
namespace JS::Temporal {
GC_DEFINE_ALLOCATOR(CalendarPrototype);
[[nodiscard]] static i32 iso_year(Object& temporal_object);
[[nodiscard]] static u8 iso_month(Object& temporal_object);
[[nodiscard]] static u8 iso_day(Object& temporal_object);
// 12.4 Properties of the Temporal.Calendar Prototype Object, https://tc39.es/proposal-temporal/#sec-properties-of-the-temporal-calendar-prototype-object
CalendarPrototype::CalendarPrototype(Realm& realm)
: PrototypeObject(realm.intrinsics().object_prototype())
{
}
void CalendarPrototype::initialize(Realm& realm)
{
Base::initialize(realm);
auto& vm = this->vm();
// 12.4.2 Temporal.Calendar.prototype[ @@toStringTag ], https://tc39.es/proposal-temporal/#sec-temporal.calendar.prototype-@@tostringtag
define_direct_property(vm.well_known_symbol_to_string_tag(), PrimitiveString::create(vm, "Temporal.Calendar"_string), Attribute::Configurable);
define_native_accessor(realm, vm.names.id, id_getter, {}, Attribute::Configurable);
u8 attr = Attribute::Writable | Attribute::Configurable;
define_native_function(realm, vm.names.dateFromFields, date_from_fields, 1, attr);
define_native_function(realm, vm.names.yearMonthFromFields, year_month_from_fields, 1, attr);
define_native_function(realm, vm.names.monthDayFromFields, month_day_from_fields, 1, attr);
define_native_function(realm, vm.names.dateAdd, date_add, 2, attr);
define_native_function(realm, vm.names.dateUntil, date_until, 2, attr);
define_native_function(realm, vm.names.year, year, 1, attr);
define_native_function(realm, vm.names.month, month, 1, attr);
define_native_function(realm, vm.names.monthCode, month_code, 1, attr);
define_native_function(realm, vm.names.day, day, 1, attr);
define_native_function(realm, vm.names.dayOfWeek, day_of_week, 1, attr);
define_native_function(realm, vm.names.dayOfYear, day_of_year, 1, attr);
define_native_function(realm, vm.names.weekOfYear, week_of_year, 1, attr);
define_native_function(realm, vm.names.yearOfWeek, year_of_week, 1, attr);
define_native_function(realm, vm.names.daysInWeek, days_in_week, 1, attr);
define_native_function(realm, vm.names.daysInMonth, days_in_month, 1, attr);
define_native_function(realm, vm.names.daysInYear, days_in_year, 1, attr);
define_native_function(realm, vm.names.monthsInYear, months_in_year, 1, attr);
define_native_function(realm, vm.names.inLeapYear, in_leap_year, 1, attr);
define_native_function(realm, vm.names.fields, fields, 1, attr);
define_native_function(realm, vm.names.mergeFields, merge_fields, 2, attr);
define_native_function(realm, vm.names.toString, to_string, 0, attr);
define_native_function(realm, vm.names.toJSON, to_json, 0, attr);
define_native_function(realm, vm.names.era, era, 1, attr);
define_native_function(realm, vm.names.eraYear, era_year, 1, attr);
}
// 12.4.3 get Temporal.Calendar.prototype.id, https://tc39.es/proposal-temporal/#sec-get-temporal.calendar.prototype.id
JS_DEFINE_NATIVE_FUNCTION(CalendarPrototype::id_getter)
{
// 1. Let calendar be the this value.
// 2. Perform ? RequireInternalSlot(calendar, [[InitializedTemporalCalendar]]).
auto calendar = TRY(typed_this_object(vm));
// 3. Return calendar.[[Identifier]].
return { PrimitiveString::create(vm, calendar->identifier()) };
}
// 12.4.4 Temporal.Calendar.prototype.dateFromFields ( fields [ , options ] ), https://tc39.es/proposal-temporal/#sec-temporal.calendar.prototype.datefromfields
// NOTE: This is the minimum dateFromFields implementation for engines without ECMA-402.
JS_DEFINE_NATIVE_FUNCTION(CalendarPrototype::date_from_fields)
{
// 1. Let calendar be the this value.
// 2. Perform ? RequireInternalSlot(calendar, [[InitializedTemporalCalendar]]).
auto calendar = TRY(typed_this_object(vm));
// 3. Assert: calendar.[[Identifier]] is "iso8601".
VERIFY(calendar->identifier() == "iso8601"sv);
// 4. If Type(fields) is not Object, throw a TypeError exception.
auto fields = vm.argument(0);
if (!fields.is_object())
return vm.throw_completion<TypeError>(ErrorType::NotAnObject, fields.to_string_without_side_effects());
// 5. Set options to ? GetOptionsObject(options).
auto const* options = TRY(get_options_object(vm, vm.argument(1)));
// 6. Let result be ? ISODateFromFields(fields, options).
auto result = TRY(iso_date_from_fields(vm, fields.as_object(), *options));
// 7. Return ? CreateTemporalDate(result.[[Year]], result.[[Month]], result.[[Day]], calendar).
return TRY(create_temporal_date(vm, result.year, result.month, result.day, calendar));
}
// 12.4.5 Temporal.Calendar.prototype.yearMonthFromFields ( fields [ , options ] ), https://tc39.es/proposal-temporal/#sec-temporal.calendar.prototype.yearmonthfromfields
// NOTE: This is the minimum yearMonthFromFields implementation for engines without ECMA-402.
JS_DEFINE_NATIVE_FUNCTION(CalendarPrototype::year_month_from_fields)
{
// 1. Let calendar be the this value.
// 2. Perform ? RequireInternalSlot(calendar, [[InitializedTemporalCalendar]]).
auto calendar = TRY(typed_this_object(vm));
// 3. Assert: calendar.[[Identifier]] is "iso8601".
VERIFY(calendar->identifier() == "iso8601"sv);
// 4. If Type(fields) is not Object, throw a TypeError exception.
auto fields = vm.argument(0);
if (!fields.is_object())
return vm.throw_completion<TypeError>(ErrorType::NotAnObject, fields.to_string_without_side_effects());
// 5. Set options to ? GetOptionsObject(options).
auto const* options = TRY(get_options_object(vm, vm.argument(1)));
// 6. Let result be ? ISOYearMonthFromFields(fields, options).
auto result = TRY(iso_year_month_from_fields(vm, fields.as_object(), *options));
// 7. Return ? CreateTemporalYearMonth(result.[[Year]], result.[[Month]], calendar, result.[[ReferenceISODay]]).
return TRY(create_temporal_year_month(vm, result.year, result.month, calendar, result.reference_iso_day));
}
// 12.4.6 Temporal.Calendar.prototype.monthDayFromFields ( fields [ , options ] ), https://tc39.es/proposal-temporal/#sec-temporal.calendar.prototype.monthdayfromfields
// NOTE: This is the minimum monthDayFromFields implementation for engines without ECMA-402.
JS_DEFINE_NATIVE_FUNCTION(CalendarPrototype::month_day_from_fields)
{
// 1. Let calendar be the this value.
// 2. Perform ? RequireInternalSlot(calendar, [[InitializedTemporalCalendar]]).
auto calendar = TRY(typed_this_object(vm));
// 3. Assert: calendar.[[Identifier]] is "iso8601".
VERIFY(calendar->identifier() == "iso8601"sv);
// 4. If Type(fields) is not Object, throw a TypeError exception.
auto fields = vm.argument(0);
if (!fields.is_object())
return vm.throw_completion<TypeError>(ErrorType::NotAnObject, fields.to_string_without_side_effects());
// 5. Set options to ? GetOptionsObject(options).
auto const* options = TRY(get_options_object(vm, vm.argument(1)));
// 6. Let result be ? ISOMonthDayFromFields(fields, options).
auto result = TRY(iso_month_day_from_fields(vm, fields.as_object(), *options));
// 7. Return ? CreateTemporalMonthDay(result.[[Month]], result.[[Day]], calendar, result.[[ReferenceISOYear]]).
return TRY(create_temporal_month_day(vm, result.month, result.day, calendar, result.reference_iso_year));
}
// 12.4.7 Temporal.Calendar.prototype.dateAdd ( date, duration [ , options ] ), https://tc39.es/proposal-temporal/#sec-temporal.calendar.prototype.dateadd
// NOTE: This is the minimum dateAdd implementation for engines without ECMA-402.
JS_DEFINE_NATIVE_FUNCTION(CalendarPrototype::date_add)
{
// 1. Let calendar be the this value.
// 2. Perform ? RequireInternalSlot(calendar, [[InitializedTemporalCalendar]]).
auto calendar = TRY(typed_this_object(vm));
// 3. Assert: calendar.[[Identifier]] is "iso8601".
VERIFY(calendar->identifier() == "iso8601"sv);
// 4. Set date to ? ToTemporalDate(date).
auto* date = TRY(to_temporal_date(vm, vm.argument(0)));
// 5. Set duration to ? ToTemporalDuration(duration).
auto duration = TRY(to_temporal_duration(vm, vm.argument(1)));
// 6. Set options to ? GetOptionsObject(options).
auto const* options = TRY(get_options_object(vm, vm.argument(2)));
// 7. Let overflow be ? ToTemporalOverflow(options).
auto overflow = TRY(to_temporal_overflow(vm, options));
// 8. Let balanceResult be ? BalanceDuration(duration.[[Days]], duration.[[Hours]], duration.[[Minutes]], duration.[[Seconds]], duration.[[Milliseconds]], duration.[[Microseconds]], duration.[[Nanoseconds]], "day").
auto balance_result = TRY(balance_duration(vm, duration->days(), duration->hours(), duration->minutes(), duration->seconds(), duration->milliseconds(), duration->microseconds(), Crypto::SignedBigInteger { duration->nanoseconds() }, "day"sv));
// 9. Let result be ? AddISODate(date.[[ISOYear]], date.[[ISOMonth]], date.[[ISODay]], duration.[[Years]], duration.[[Months]], duration.[[Weeks]], balanceResult.[[Days]], overflow).
auto result = TRY(add_iso_date(vm, date->iso_year(), date->iso_month(), date->iso_day(), duration->years(), duration->months(), duration->weeks(), balance_result.days, overflow));
// 10. Return ? CreateTemporalDate(result.[[Year]], result.[[Month]], result.[[Day]], calendar).
return TRY(create_temporal_date(vm, result.year, result.month, result.day, calendar));
}
// 12.4.8 Temporal.Calendar.prototype.dateUntil ( one, two [ , options ] ), https://tc39.es/proposal-temporal/#sec-temporal.calendar.prototype.dateuntil
// NOTE: This is the minimum dateUntil implementation for engines without ECMA-402.
JS_DEFINE_NATIVE_FUNCTION(CalendarPrototype::date_until)
{
// 1. Let calendar be the this value.
// 2. Perform ? RequireInternalSlot(calendar, [[InitializedTemporalCalendar]]).
auto calendar = TRY(typed_this_object(vm));
// 3. Assert: calendar.[[Identifier]] is "iso8601".
VERIFY(calendar->identifier() == "iso8601"sv);
// 4. Set one to ? ToTemporalDate(one).
auto* one = TRY(to_temporal_date(vm, vm.argument(0)));
// 5. Set two to ? ToTemporalDate(two).
auto* two = TRY(to_temporal_date(vm, vm.argument(1)));
// 6. Set options to ? GetOptionsObject(options).
auto const* options = TRY(get_options_object(vm, vm.argument(2)));
// 7. Let largestUnit be ? GetTemporalUnit(options, "largestUnit", date, "auto").
auto largest_unit = TRY(get_temporal_unit(vm, *options, vm.names.largestUnit, UnitGroup::Date, { "auto"sv }));
// 8. If largestUnit is "auto", set largestUnit to "day".
if (largest_unit == "auto")
largest_unit = "day"_string;
// 9. Let result be DifferenceISODate(one.[[ISOYear]], one.[[ISOMonth]], one.[[ISODay]], two.[[ISOYear]], two.[[ISOMonth]], two.[[ISODay]], largestUnit).
auto result = difference_iso_date(vm, one->iso_year(), one->iso_month(), one->iso_day(), two->iso_year(), two->iso_month(), two->iso_day(), *largest_unit);
// 10. Return ! CreateTemporalDuration(result.[[Years]], result.[[Months]], result.[[Weeks]], result.[[Days]], 0, 0, 0, 0, 0, 0).
return MUST(create_temporal_duration(vm, result.years, result.months, result.weeks, result.days, 0, 0, 0, 0, 0, 0));
}
// 12.4.9 Temporal.Calendar.prototype.year ( temporalDateLike ), https://tc39.es/proposal-temporal/#sec-temporal.calendar.prototype.year
// NOTE: This is the minimum year implementation for engines without ECMA-402.
JS_DEFINE_NATIVE_FUNCTION(CalendarPrototype::year)
{
// 1. Let calendar be the this value.
// 2. Perform ? RequireInternalSlot(calendar, [[InitializedTemporalCalendar]]).
auto calendar = TRY(typed_this_object(vm));
// 3. Assert: calendar.[[Identifier]] is "iso8601".
VERIFY(calendar->identifier() == "iso8601"sv);
auto temporal_date_like = vm.argument(0);
// 4. If Type(temporalDateLike) is not Object or temporalDateLike does not have an [[InitializedTemporalDate]], [[InitializedTemporalDateTime]], or [[InitializedTemporalYearMonth]] internal slot, then
if (!temporal_date_like.is_object() || !(is<PlainDate>(temporal_date_like.as_object()) || is<PlainDateTime>(temporal_date_like.as_object()) || is<PlainYearMonth>(temporal_date_like.as_object()))) {
// a. Set temporalDateLike to ? ToTemporalDate(temporalDateLike).
temporal_date_like = TRY(to_temporal_date(vm, temporal_date_like));
}
// 5. Assert: temporalDateLike has an [[ISOYear]] internal slot.
// NOTE: The assertion happens in iso_year() call.
// 6. Return 𝔽(temporalDateLike.[[ISOYear]]).
return Value(iso_year(temporal_date_like.as_object()));
}
// 12.4.10 Temporal.Calendar.prototype.month ( temporalDateLike ), https://tc39.es/proposal-temporal/#sec-temporal.calendar.prototype.month
// NOTE: This is the minimum month implementation for engines without ECMA-402.
JS_DEFINE_NATIVE_FUNCTION(CalendarPrototype::month)
{
// 1. Let calendar be the this value.
// 2. Perform ? RequireInternalSlot(calendar, [[InitializedTemporalCalendar]]).
auto calendar = TRY(typed_this_object(vm));
// 3. Assert: calendar.[[Identifier]] is "iso8601".
VERIFY(calendar->identifier() == "iso8601"sv);
auto temporal_date_like = vm.argument(0);
// 4. If Type(temporalDateLike) is Object and temporalDateLike has an [[InitializedTemporalMonthDay]] internal slot, then
if (temporal_date_like.is_object() && is<PlainMonthDay>(temporal_date_like.as_object())) {
// a. Throw a TypeError exception.
return vm.throw_completion<TypeError>(ErrorType::TemporalAmbiguousMonthOfPlainMonthDay);
}
// 5. If Type(temporalDateLike) is not Object or temporalDateLike does not have an [[InitializedTemporalDate]], [[InitializedTemporalDateTime]], or [[InitializedTemporalYearMonth]] internal slot, then
if (!temporal_date_like.is_object() || !(is<PlainDate>(temporal_date_like.as_object()) || is<PlainDateTime>(temporal_date_like.as_object()) || is<PlainYearMonth>(temporal_date_like.as_object()))) {
// a. Set temporalDateLike to ? ToTemporalDate(temporalDateLike).
temporal_date_like = TRY(to_temporal_date(vm, temporal_date_like));
}
// 6. Assert: temporalDateLike has an [[ISOMonth]] internal slot.
// NOTE: The assertion happens in iso_month() call.
// 7. Return 𝔽(temporalDateLike.[[ISOMonth]]).
return Value(iso_month(temporal_date_like.as_object()));
}
// 12.4.11 Temporal.Calendar.prototype.monthCode ( temporalDateLike ), https://tc39.es/proposal-temporal/#sec-temporal.calendar.prototype.monthcode
// NOTE: This is the minimum monthCode implementation for engines without ECMA-402.
JS_DEFINE_NATIVE_FUNCTION(CalendarPrototype::month_code)
{
// 1. Let calendar be the this value.
// 2. Perform ? RequireInternalSlot(calendar, [[InitializedTemporalCalendar]]).
auto calendar = TRY(typed_this_object(vm));
// 3. Assert: calendar.[[Identifier]] is "iso8601".
VERIFY(calendar->identifier() == "iso8601"sv);
auto temporal_date_like = vm.argument(0);
// 4. If Type(temporalDateLike) is not Object or temporalDateLike does not have an [[InitializedTemporalDate]], [[InitializedTemporalDateTime]], [[InitializedTemporalMonthDay]], or [[InitializedTemporalYearMonth]] internal slot, then
if (!temporal_date_like.is_object() || !(is<PlainDate>(temporal_date_like.as_object()) || is<PlainDateTime>(temporal_date_like.as_object()) || is<PlainMonthDay>(temporal_date_like.as_object()) || is<PlainYearMonth>(temporal_date_like.as_object()))) {
// a. Set temporalDateLike to ? ToTemporalDate(temporalDateLike).
temporal_date_like = TRY(to_temporal_date(vm, temporal_date_like));
}
// 5. Assert: temporalDateLike has an [[ISOMonth]] internal slot.
// NOTE: The assertion happens in iso_month() call.
// 6. Return ISOMonthCode(temporalDateLike.[[ISOMonth]]).
return PrimitiveString::create(vm, TRY(iso_month_code(vm, iso_month(temporal_date_like.as_object()))));
}
// 12.4.12 Temporal.Calendar.prototype.day ( temporalDateLike ), https://tc39.es/proposal-temporal/#sec-temporal.calendar.prototype.day
// NOTE: This is the minimum day implementation for engines without ECMA-402.
JS_DEFINE_NATIVE_FUNCTION(CalendarPrototype::day)
{
// 1. Let calendar be the this value.
// 2. Perform ? RequireInternalSlot(calendar, [[InitializedTemporalCalendar]]).
auto calendar = TRY(typed_this_object(vm));
// 3. Assert: calendar.[[Identifier]] is "iso8601".
VERIFY(calendar->identifier() == "iso8601"sv);
auto temporal_date_like = vm.argument(0);
// 4. If Type(temporalDateLike) is not Object or temporalDateLike does not have an [[InitializedTemporalDate]], [[InitializedTemporalDateTime]] or [[InitializedTemporalMonthDay]] internal slot, then
if (!temporal_date_like.is_object() || !(is<PlainDate>(temporal_date_like.as_object()) || is<PlainDateTime>(temporal_date_like.as_object()) || is<PlainMonthDay>(temporal_date_like.as_object()))) {
// a. Set temporalDateLike to ? ToTemporalDate(temporalDateLike).
temporal_date_like = TRY(to_temporal_date(vm, temporal_date_like));
}
// 5. Assert: temporalDateLike has an [[ISODay]] internal slot.
// NOTE: The assertion happens in iso_day() call.
// 6. Return 𝔽(temporalDateLike.[[ISODay]]).
return Value(iso_day(temporal_date_like.as_object()));
}
// 12.4.13 Temporal.Calendar.prototype.dayOfWeek ( temporalDateLike ), https://tc39.es/proposal-temporal/#sec-temporal.calendar.prototype.dayofweek
// NOTE: This is the minimum dayOfWeek implementation for engines without ECMA-402.
JS_DEFINE_NATIVE_FUNCTION(CalendarPrototype::day_of_week)
{
// 1. Let calendar be the this value.
// 2. Perform ? RequireInternalSlot(calendar, [[InitializedTemporalCalendar]]).
auto calendar = TRY(typed_this_object(vm));
// 3. Assert: calendar.[[Identifier]] is "iso8601".
VERIFY(calendar->identifier() == "iso8601"sv);
// 4. Let temporalDate be ? ToTemporalDate(temporalDateLike).
auto* temporal_date = TRY(to_temporal_date(vm, vm.argument(0)));
// 5. Return 𝔽(ToISODayOfWeek(temporalDate.[[ISOYear]], temporalDate.[[ISOMonth]], temporalDate.[[ISODay]])).
return Value(to_iso_day_of_week(temporal_date->iso_year(), temporal_date->iso_month(), temporal_date->iso_day()));
}
// 12.4.14 Temporal.Calendar.prototype.dayOfYear ( temporalDateLike ), https://tc39.es/proposal-temporal/#sec-temporal.calendar.prototype.dayofyear
// NOTE: This is the minimum dayOfYear implementation for engines without ECMA-402.
JS_DEFINE_NATIVE_FUNCTION(CalendarPrototype::day_of_year)
{
// 1. Let calendar be the this value.
// 2. Perform ? RequireInternalSlot(calendar, [[InitializedTemporalCalendar]]).
auto calendar = TRY(typed_this_object(vm));
// 3. Assert: calendar.[[Identifier]] is "iso8601".
VERIFY(calendar->identifier() == "iso8601"sv);
// 4. Let temporalDate be ? ToTemporalDate(temporalDateLike).
auto* temporal_date = TRY(to_temporal_date(vm, vm.argument(0)));
// 5. Return 𝔽(ToISODayOfYear(temporalDate.[[ISOYear]], temporalDate.[[ISOMonth]], temporalDate.[[ISODay]])).
return Value(to_iso_day_of_year(temporal_date->iso_year(), temporal_date->iso_month(), temporal_date->iso_day()));
}
// 12.4.15 Temporal.Calendar.prototype.weekOfYear ( temporalDateLike ), https://tc39.es/proposal-temporal/#sec-temporal.calendar.prototype.weekofyear
// NOTE: This is the minimum weekOfYear implementation for engines without ECMA-402.
JS_DEFINE_NATIVE_FUNCTION(CalendarPrototype::week_of_year)
{
// 1. Let calendar be the this value.
// 2. Perform ? RequireInternalSlot(calendar, [[InitializedTemporalCalendar]]).
auto calendar = TRY(typed_this_object(vm));
// 3. Assert: calendar.[[Identifier]] is "iso8601".
VERIFY(calendar->identifier() == "iso8601"sv);
// 4. Let temporalDate be ? ToTemporalDate(temporalDateLike).
auto* temporal_date = TRY(to_temporal_date(vm, vm.argument(0)));
// 5. Let isoYearWeek be ToISOWeekOfYear(temporalDate.[[ISOYear]], temporalDate.[[ISOMonth]], temporalDate.[[ISODay]]).
auto iso_year_week = to_iso_week_of_year(temporal_date->iso_year(), temporal_date->iso_month(), temporal_date->iso_day());
// 6. Return 𝔽(isoYearWeek.[[Week]]).
return Value(iso_year_week.week);
}
// 12.5.16 Temporal.Calendar.prototype.yearOfWeek ( temporalDateLike ), https://tc39.es/proposal-temporal/#sec-temporal.calendar.prototype.yearofweek
// NOTE: This is the minimum yearOfWeek implementation for engines without ECMA-402.
JS_DEFINE_NATIVE_FUNCTION(CalendarPrototype::year_of_week)
{
// 1. Let calendar be the this value.
// 2. Perform ? RequireInternalSlot(calendar, [[InitializedTemporalCalendar]]).
auto calendar = TRY(typed_this_object(vm));
// 3. Assert: calendar.[[Identifier]] is "iso8601".
VERIFY(calendar->identifier() == "iso8601"sv);
// 4. Let temporalDate be ? ToTemporalDate(temporalDateLike).
auto* temporal_date = TRY(to_temporal_date(vm, vm.argument(0)));
// 5. Let isoYearWeek be ToISOWeekOfYear(temporalDate.[[ISOYear]], temporalDate.[[ISOMonth]], temporalDate.[[ISODay]]).
auto iso_year_week = to_iso_week_of_year(temporal_date->iso_year(), temporal_date->iso_month(), temporal_date->iso_day());
// 6. Return 𝔽(isoYearWeek.[[Year]]).
return Value(iso_year_week.year);
}
// 12.4.17 Temporal.Calendar.prototype.daysInWeek ( temporalDateLike ), https://tc39.es/proposal-temporal/#sec-temporal.calendar.prototype.daysinweek
// NOTE: This is the minimum daysInWeek implementation for engines without ECMA-402.
JS_DEFINE_NATIVE_FUNCTION(CalendarPrototype::days_in_week)
{
// 1. Let calendar be the this value.
// 2. Perform ? RequireInternalSlot(calendar, [[InitializedTemporalCalendar]]).
auto calendar = TRY(typed_this_object(vm));
// 3. Assert: calendar.[[Identifier]] is "iso8601".
VERIFY(calendar->identifier() == "iso8601"sv);
// 4. Let temporalDate be ? ToTemporalDate(temporalDateLike).
[[maybe_unused]] auto* temporal_date = TRY(to_temporal_date(vm, vm.argument(0)));
// 5. Return 7𝔽.
return Value(7);
}
// 12.4.18 Temporal.Calendar.prototype.daysInMonth ( temporalDateLike ), https://tc39.es/proposal-temporal/#sec-temporal.calendar.prototype.daysinweek
// NOTE: This is the minimum daysInMonth implementation for engines without ECMA-402.
JS_DEFINE_NATIVE_FUNCTION(CalendarPrototype::days_in_month)
{
// 1. Let calendar be the this value.
// 2. Perform ? RequireInternalSlot(calendar, [[InitializedTemporalCalendar]]).
auto calendar = TRY(typed_this_object(vm));
// 3. Assert: calendar.[[Identifier]] is "iso8601".
VERIFY(calendar->identifier() == "iso8601"sv);
auto temporal_date_like = vm.argument(0);
// 4. If Type(temporalDateLike) is not Object or temporalDateLike does not have an [[InitializedTemporalDate]], [[InitializedTemporalDateTime]], or [[InitializedTemporalYearMonth]] internal slots, then
if (!temporal_date_like.is_object() || !(is<PlainDate>(temporal_date_like.as_object()) || is<PlainDateTime>(temporal_date_like.as_object()) || is<PlainYearMonth>(temporal_date_like.as_object()))) {
// a. Set temporalDateLike to ? ToTemporalDate(temporalDateLike).
temporal_date_like = TRY(to_temporal_date(vm, temporal_date_like));
}
// 5. Return 𝔽(! ISODaysInMonth(temporalDateLike.[[ISOYear]], temporalDateLike.[[ISOMonth]])).
return Value(iso_days_in_month(iso_year(temporal_date_like.as_object()), iso_month(temporal_date_like.as_object())));
}
// 12.4.19 Temporal.Calendar.prototype.daysInYear ( temporalDateLike ), https://tc39.es/proposal-temporal/#sec-temporal.calendar.prototype.daysinyear
// NOTE: This is the minimum daysInYear implementation for engines without ECMA-402.
JS_DEFINE_NATIVE_FUNCTION(CalendarPrototype::days_in_year)
{
// 1. Let calendar be the this value.
// 2. Perform ? RequireInternalSlot(calendar, [[InitializedTemporalCalendar]]).
auto calendar = TRY(typed_this_object(vm));
// 3. Assert: calendar.[[Identifier]] is "iso8601".
VERIFY(calendar->identifier() == "iso8601"sv);
auto temporal_date_like = vm.argument(0);
// 4. If Type(temporalDateLike) is not Object or temporalDateLike does not have an [[InitializedTemporalDate]], [[InitializedTemporalDateTime]], or [[InitializedTemporalYearMonth]] internal slot, then
if (!temporal_date_like.is_object() || !(is<PlainDate>(temporal_date_like.as_object()) || is<PlainDateTime>(temporal_date_like.as_object()) || is<PlainYearMonth>(temporal_date_like.as_object()))) {
// a. Set temporalDateLike to ? ToTemporalDate(temporalDateLike).
temporal_date_like = TRY(to_temporal_date(vm, temporal_date_like));
}
// 5. Return DaysInYear(𝔽(temporalDateLike.[[ISOYear]])).
return Value(JS::days_in_year(iso_year(temporal_date_like.as_object())));
}
// 12.4.20 Temporal.Calendar.prototype.monthsInYear ( temporalDateLike ), https://tc39.es/proposal-temporal/#sec-temporal.calendar.prototype.monthsinyear
// NOTE: This is the minimum monthsInYear implementation for engines without ECMA-402.
JS_DEFINE_NATIVE_FUNCTION(CalendarPrototype::months_in_year)
{
// 1. Let calendar be the this value.
// 2. Perform ? RequireInternalSlot(calendar, [[InitializedTemporalCalendar]]).
auto calendar = TRY(typed_this_object(vm));
// 3. Assert: calendar.[[Identifier]] is "iso8601".
VERIFY(calendar->identifier() == "iso8601"sv);
auto temporal_date_like = vm.argument(0);
// 4. If Type(temporalDateLike) is not Object or temporalDateLike does not have an [[InitializedTemporalDate]], [[InitializedTemporalDateTime]], or [[InitializedTemporalYearMonth]] internal slot, then
if (!temporal_date_like.is_object() || !(is<PlainDate>(temporal_date_like.as_object()) || is<PlainDateTime>(temporal_date_like.as_object()) || is<PlainYearMonth>(temporal_date_like.as_object()))) {
// a. Perform ? ToTemporalDate(temporalDateLike).
(void)TRY(to_temporal_date(vm, temporal_date_like));
}
// 5. Return 12𝔽.
return Value(12);
}
// 12.4.21 Temporal.Calendar.prototype.inLeapYear ( temporalDateLike ), https://tc39.es/proposal-temporal/#sec-temporal.calendar.prototype.inleapyear
// NOTE: This is the minimum inLeapYear implementation for engines without ECMA-402.
JS_DEFINE_NATIVE_FUNCTION(CalendarPrototype::in_leap_year)
{
// 1. Let calendar be the this value.
// 2. Perform ? RequireInternalSlot(calendar, [[InitializedTemporalCalendar]]).
auto calendar = TRY(typed_this_object(vm));
// 3. Assert: calendar.[[Identifier]] is "iso8601".
VERIFY(calendar->identifier() == "iso8601"sv);
auto temporal_date_like = vm.argument(0);
// 4. If Type(temporalDateLike) is not Object or temporalDateLike does not have an [[InitializedTemporalDate]], [[InitializedTemporalDateTime]], or [[InitializedTemporalYearMonth]] internal slot, then
if (!temporal_date_like.is_object() || !(is<PlainDate>(temporal_date_like.as_object()) || is<PlainDateTime>(temporal_date_like.as_object()) || is<PlainYearMonth>(temporal_date_like.as_object()))) {
// a. Set temporalDateLike to ? ToTemporalDate(temporalDateLike).
temporal_date_like = TRY(to_temporal_date(vm, temporal_date_like));
}
// 5. If InLeapYear(TimeFromYear(𝔽(temporalDateLike.[[ISOYear]]))) is 1𝔽, return true.
if (JS::in_leap_year(time_from_year(iso_year(temporal_date_like.as_object()))))
return Value(true);
// 6. Return false.
return Value(false);
}
// 12.4.22 Temporal.Calendar.prototype.fields ( fields ), https://tc39.es/proposal-temporal/#sec-temporal.calendar.prototype.fields
// NOTE: This is the minimum fields implementation for engines without ECMA-402.
JS_DEFINE_NATIVE_FUNCTION(CalendarPrototype::fields)
{
auto& realm = *vm.current_realm();
auto fields = vm.argument(0);
// 1. Let calendar be the this value.
// 2. Perform ? RequireInternalSlot(calendar, [[InitializedTemporalCalendar]]).
auto calendar = TRY(typed_this_object(vm));
// 3. Assert: calendar.[[Identifier]] is "iso8601".
VERIFY(calendar->identifier() == "iso8601"sv);
// 4. Let iteratorRecord be ? GetIterator(fields, sync).
auto iterator_record = TRY(get_iterator(vm, fields, IteratorHint::Sync));
// 5. Let fieldNames be a new empty List.
auto field_names = GC::MarkedVector<Value> { vm.heap() };
// 6. Let next be true.
// 7. Repeat, while next is not false,
while (true) {
// a. Set next to ? IteratorStep(iteratorRecord).
auto next = TRY(iterator_step(vm, iterator_record));
// b. If next is not false, then
if (!next)
break;
// i. Let nextValue be ? IteratorValue(next).
auto next_value = TRY(iterator_value(vm, *next));
// ii. If Type(nextValue) is not String, then
if (!next_value.is_string()) {
// 1. Let completion be ThrowCompletion(a newly created TypeError object).
auto completion = vm.throw_completion<TypeError>(ErrorType::TemporalInvalidCalendarFieldValue, next_value.to_string_without_side_effects());
// 2. Return ? IteratorClose(iteratorRecord, completion).
return *TRY(iterator_close(vm, iterator_record, move(completion)));
}
auto next_value_string = next_value.as_string().utf8_string();
// iii. If fieldNames contains nextValue, then
if (field_names.contains_slow(next_value)) {
// 1. Let completion be ThrowCompletion(a newly created RangeError object).
auto completion = vm.throw_completion<RangeError>(ErrorType::TemporalDuplicateCalendarField, next_value_string);
// 2. Return ? IteratorClose(iteratorRecord, completion).
return *TRY(iterator_close(vm, iterator_record, move(completion)));
}
// iv. If nextValue is not one of "year", "month", "monthCode", "day", "hour", "minute", "second", "millisecond", "microsecond", "nanosecond", then
if (!next_value_string.is_one_of("year"sv, "month"sv, "monthCode"sv, "day"sv, "hour"sv, "minute"sv, "second"sv, "millisecond"sv, "microsecond"sv, "nanosecond"sv)) {
// 1. Let completion be ThrowCompletion(a newly created RangeError object).
auto completion = vm.throw_completion<RangeError>(ErrorType::TemporalInvalidCalendarFieldName, next_value_string);
// 2. Return ? IteratorClose(iteratorRecord, completion).
return *TRY(iterator_close(vm, iterator_record, move(completion)));
}
// v. Append nextValue to the end of the List fieldNames.
field_names.append(next_value);
}
// 8. Return CreateArrayFromList(fieldNames).
return Array::create_from(realm, field_names);
}
// 12.4.23 Temporal.Calendar.prototype.mergeFields ( fields, additionalFields ), https://tc39.es/proposal-temporal/#sec-temporal.calendar.prototype.mergefields
// NOTE: This is the minimum mergeFields implementation for engines without ECMA-402.
JS_DEFINE_NATIVE_FUNCTION(CalendarPrototype::merge_fields)
{
// 1. Let calendar be the this value.
// 2. Perform ? RequireInternalSlot(calendar, [[InitializedTemporalCalendar]]).
auto calendar = TRY(typed_this_object(vm));
// 3. Set fields to ? ToObject(fields).
auto fields = TRY(vm.argument(0).to_object(vm));
// 4. Set additionalFields to ? ToObject(additionalFields).
auto additional_fields = TRY(vm.argument(1).to_object(vm));
// 5. Assert: calendar.[[Identifier]] is "iso8601".
VERIFY(calendar->identifier() == "iso8601"sv);
// 6. Return ? DefaultMergeCalendarFields(fields, additionalFields).
return TRY(default_merge_calendar_fields(vm, fields, additional_fields));
}
// 12.4.24 Temporal.Calendar.prototype.toString ( ), https://tc39.es/proposal-temporal/#sec-temporal.calendar.prototype.tostring
JS_DEFINE_NATIVE_FUNCTION(CalendarPrototype::to_string)
{
// 1. Let calendar be the this value.
// 2. Perform ? RequireInternalSlot(calendar, [[InitializedTemporalCalendar]]).
auto calendar = TRY(typed_this_object(vm));
// 3. Return calendar.[[Identifier]].
return PrimitiveString::create(vm, calendar->identifier());
}
// 12.4.25 Temporal.Calendar.prototype.toJSON ( ), https://tc39.es/proposal-temporal/#sec-temporal.calendar.prototype.tojson
JS_DEFINE_NATIVE_FUNCTION(CalendarPrototype::to_json)
{
// 1. Let calendar be the this value.
// 2. Perform ? RequireInternalSlot(calendar, [[InitializedTemporalCalendar]]).
auto calendar = TRY(typed_this_object(vm));
// 3. Return ? ToString(calendar).
return PrimitiveString::create(vm, TRY(Value(calendar).to_string(vm)));
}
// 15.6.2.6 Temporal.Calendar.prototype.era ( temporalDateLike ), https://tc39.es/proposal-temporal/#sec-temporal.calendar.prototype.era
JS_DEFINE_NATIVE_FUNCTION(CalendarPrototype::era)
{
auto temporal_date_like = vm.argument(0);
// 1. Let calendar be the this value.
// 2. Perform ? RequireInternalSlot(calendar, [[InitializedTemporalCalendar]]).
auto calendar = TRY(typed_this_object(vm));
// 3. If Type(temporalDateLike) is not Object or temporalDateLike does not have an [[InitializedTemporalDate]], [[InitializedTemporalDateTime]], or [[InitializedTemporalYearMonth]] internal slot, then
if (!temporal_date_like.is_object() || !(is<PlainDate>(temporal_date_like.as_object()) || is<PlainDateTime>(temporal_date_like.as_object()) || is<PlainYearMonth>(temporal_date_like.as_object()))) {
// a. Set temporalDateLike to ? ToTemporalDate(temporalDateLike).
temporal_date_like = TRY(to_temporal_date(vm, temporal_date_like));
}
// 4. If calendar.[[Identifier]] is "iso8601", then
if (calendar->identifier() == "iso8601"sv) {
// a. Return undefined.
return js_undefined();
}
// 5. Let era be the result of implementation-defined processing of temporalDateLike and calendar.[[Identifier]].
// 6. Return era.
// NOTE: No support for non-iso8601 calendars yet.
VERIFY_NOT_REACHED();
}
// 15.6.2.7 Temporal.Calendar.prototype.eraYear ( temporalDateLike ), https://tc39.es/proposal-temporal/#sec-temporal.calendar.prototype.erayear
JS_DEFINE_NATIVE_FUNCTION(CalendarPrototype::era_year)
{
auto temporal_date_like = vm.argument(0);
// 1. Let calendar be the this value.
// 2. Perform ? RequireInternalSlot(calendar, [[InitializedTemporalCalendar]]).
auto calendar = TRY(typed_this_object(vm));
// 3. If Type(temporalDateLike) is not Object or temporalDateLike does not have an [[InitializedTemporalDate]], [[InitializedTemporalDateTime]], or [[InitializedTemporalYearMonth]] internal slot, then
if (!temporal_date_like.is_object() || !(is<PlainDate>(temporal_date_like.as_object()) || is<PlainDateTime>(temporal_date_like.as_object()) || is<PlainYearMonth>(temporal_date_like.as_object()))) {
// a. Set temporalDateLike to ? ToTemporalDate(temporalDateLike).
temporal_date_like = TRY(to_temporal_date(vm, temporal_date_like));
}
// 4. If calendar.[[Identifier]] is "iso8601", then
if (calendar->identifier() == "iso8601"sv) {
// a. Return undefined.
return js_undefined();
}
// 5. Let eraYear be the result of implementation-defined processing of temporalDateLike and calendar.[[Identifier]].
// 6. Return 𝔽(eraYear).
// NOTE: No support for non-iso8601 calendars yet.
VERIFY_NOT_REACHED();
}
static i32 iso_year(Object& temporal_object)
{
if (is<PlainDate>(temporal_object))
return static_cast<PlainDate&>(temporal_object).iso_year();
if (is<PlainDateTime>(temporal_object))
return static_cast<PlainDateTime&>(temporal_object).iso_year();
if (is<PlainYearMonth>(temporal_object))
return static_cast<PlainYearMonth&>(temporal_object).iso_year();
if (is<PlainMonthDay>(temporal_object))
return static_cast<PlainMonthDay&>(temporal_object).iso_year();
VERIFY_NOT_REACHED();
}
static u8 iso_month(Object& temporal_object)
{
if (is<PlainDate>(temporal_object))
return static_cast<PlainDate&>(temporal_object).iso_month();
if (is<PlainDateTime>(temporal_object))
return static_cast<PlainDateTime&>(temporal_object).iso_month();
if (is<PlainYearMonth>(temporal_object))
return static_cast<PlainYearMonth&>(temporal_object).iso_month();
if (is<PlainMonthDay>(temporal_object))
return static_cast<PlainMonthDay&>(temporal_object).iso_month();
VERIFY_NOT_REACHED();
}
static u8 iso_day(Object& temporal_object)
{
if (is<PlainDate>(temporal_object))
return static_cast<PlainDate&>(temporal_object).iso_day();
if (is<PlainDateTime>(temporal_object))
return static_cast<PlainDateTime&>(temporal_object).iso_day();
if (is<PlainYearMonth>(temporal_object))
return static_cast<PlainYearMonth&>(temporal_object).iso_day();
if (is<PlainMonthDay>(temporal_object))
return static_cast<PlainMonthDay&>(temporal_object).iso_day();
VERIFY_NOT_REACHED();
}
}

View file

@ -1,52 +0,0 @@
/*
* Copyright (c) 2021, Linus Groh <linusg@serenityos.org>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#pragma once
#include <LibJS/Runtime/PrototypeObject.h>
#include <LibJS/Runtime/Temporal/Calendar.h>
namespace JS::Temporal {
class CalendarPrototype final : public PrototypeObject<CalendarPrototype, Calendar> {
JS_PROTOTYPE_OBJECT(CalendarPrototype, Calendar, Temporal.Calendar);
GC_DECLARE_ALLOCATOR(CalendarPrototype);
public:
virtual void initialize(Realm&) override;
virtual ~CalendarPrototype() override = default;
private:
explicit CalendarPrototype(Realm&);
JS_DECLARE_NATIVE_FUNCTION(id_getter);
JS_DECLARE_NATIVE_FUNCTION(date_from_fields);
JS_DECLARE_NATIVE_FUNCTION(year_month_from_fields);
JS_DECLARE_NATIVE_FUNCTION(month_day_from_fields);
JS_DECLARE_NATIVE_FUNCTION(date_add);
JS_DECLARE_NATIVE_FUNCTION(date_until);
JS_DECLARE_NATIVE_FUNCTION(year);
JS_DECLARE_NATIVE_FUNCTION(month);
JS_DECLARE_NATIVE_FUNCTION(month_code);
JS_DECLARE_NATIVE_FUNCTION(day);
JS_DECLARE_NATIVE_FUNCTION(day_of_week);
JS_DECLARE_NATIVE_FUNCTION(day_of_year);
JS_DECLARE_NATIVE_FUNCTION(week_of_year);
JS_DECLARE_NATIVE_FUNCTION(year_of_week);
JS_DECLARE_NATIVE_FUNCTION(days_in_week);
JS_DECLARE_NATIVE_FUNCTION(days_in_month);
JS_DECLARE_NATIVE_FUNCTION(days_in_year);
JS_DECLARE_NATIVE_FUNCTION(months_in_year);
JS_DECLARE_NATIVE_FUNCTION(in_leap_year);
JS_DECLARE_NATIVE_FUNCTION(fields);
JS_DECLARE_NATIVE_FUNCTION(merge_fields);
JS_DECLARE_NATIVE_FUNCTION(to_string);
JS_DECLARE_NATIVE_FUNCTION(to_json);
JS_DECLARE_NATIVE_FUNCTION(era);
JS_DECLARE_NATIVE_FUNCTION(era_year);
};
}

File diff suppressed because it is too large Load diff

View file

@ -1,6 +1,7 @@
/* /*
* Copyright (c) 2021-2023, Linus Groh <linusg@serenityos.org> * Copyright (c) 2021-2023, Linus Groh <linusg@serenityos.org>
* Copyright (c) 2024, Shannon Booth <shannon@serenityos.org> * Copyright (c) 2024, Shannon Booth <shannon@serenityos.org>
* Copyright (c) 2024, Tim Flynn <trflynn89@ladybird.org>
* *
* SPDX-License-Identifier: BSD-2-Clause * SPDX-License-Identifier: BSD-2-Clause
*/ */
@ -8,17 +9,26 @@
#pragma once #pragma once
#include <AK/Optional.h> #include <AK/Optional.h>
#include <LibGC/Root.h> #include <LibCrypto/BigInt/SignedBigInteger.h>
#include <LibJS/Runtime/Completion.h> #include <LibJS/Runtime/Completion.h>
#include <LibJS/Runtime/Date.h>
#include <LibJS/Runtime/Object.h> #include <LibJS/Runtime/Object.h>
#include <LibJS/Runtime/Temporal/Calendar.h> #include <LibJS/Runtime/Temporal/AbstractOperations.h>
#include <LibJS/Runtime/Temporal/PlainDate.h> #include <LibJS/Runtime/Value.h>
#include <LibJS/Runtime/Temporal/ZonedDateTime.h>
#include <LibJS/Runtime/VM.h>
namespace JS::Temporal { namespace JS::Temporal {
#define JS_ENUMERATE_DURATION_UNITS \
__JS_ENUMERATE(years) \
__JS_ENUMERATE(months) \
__JS_ENUMERATE(weeks) \
__JS_ENUMERATE(days) \
__JS_ENUMERATE(hours) \
__JS_ENUMERATE(minutes) \
__JS_ENUMERATE(seconds) \
__JS_ENUMERATE(milliseconds) \
__JS_ENUMERATE(microseconds) \
__JS_ENUMERATE(nanoseconds)
class Duration final : public Object { class Duration final : public Object {
JS_OBJECT(Duration, Object); JS_OBJECT(Duration, Object);
GC_DECLARE_ALLOCATOR(Duration); GC_DECLARE_ALLOCATOR(Duration);
@ -26,68 +36,46 @@ class Duration final : public Object {
public: public:
virtual ~Duration() override = default; virtual ~Duration() override = default;
[[nodiscard]] double years() const { return m_years; } #define __JS_ENUMERATE(unit) \
[[nodiscard]] double months() const { return m_months; } [[nodiscard]] double unit() const { return m_##unit; }
[[nodiscard]] double weeks() const { return m_weeks; } JS_ENUMERATE_DURATION_UNITS
[[nodiscard]] double days() const { return m_days; } #undef __JS_ENUMERATE
[[nodiscard]] double hours() const { return m_hours; }
[[nodiscard]] double minutes() const { return m_minutes; }
[[nodiscard]] double seconds() const { return m_seconds; }
[[nodiscard]] double milliseconds() const { return m_milliseconds; }
[[nodiscard]] double microseconds() const { return m_microseconds; }
[[nodiscard]] double nanoseconds() const { return m_nanoseconds; }
private: private:
Duration(double years, double months, double weeks, double days, double hours, double minutes, double seconds, double milliseconds, double microseconds, double nanoseconds, Object& prototype); Duration(double years, double months, double weeks, double days, double hours, double minutes, double seconds, double milliseconds, double microseconds, double nanoseconds, Object& prototype);
// 7.4 Properties of Temporal.Duration Instances, https://tc39.es/proposal-temporal/#sec-properties-of-temporal-duration-instances double m_years { 0 }; // [[Years]]
double m_years; // [[Years]] double m_months { 0 }; // [[Months]]
double m_months; // [[Months]] double m_weeks { 0 }; // [[Weeks]]
double m_weeks; // [[Weeks]] double m_days { 0 }; // [[Days]]
double m_days; // [[Days]] double m_hours { 0 }; // [[Hours]]
double m_hours; // [[Hours]] double m_minutes { 0 }; // [[Minutes]]
double m_minutes; // [[Minutes]] double m_seconds { 0 }; // [[Seconds]]
double m_seconds; // [[Seconds]] double m_milliseconds { 0 }; // [[Milliseconds]]
double m_milliseconds; // [[Milliseconds]] double m_microseconds { 0 }; // [[Microseconds]]
double m_microseconds; // [[Microseconds]] double m_nanoseconds { 0 }; // [[Nanoseconds]]
double m_nanoseconds; // [[Nanoseconds]]
}; };
// 7.5.1 Duration Records, https://tc39.es/proposal-temporal/#sec-temporal-duration-records // 7.5.1 Date Duration Records, https://tc39.es/proposal-temporal/#sec-temporal-date-duration-records
struct DurationRecord { struct DateDuration {
double years; double years { 0 };
double months; double months { 0 };
double weeks; double weeks { 0 };
double days; double days { 0 };
double hours;
double minutes;
double seconds;
double milliseconds;
double microseconds;
double nanoseconds;
}; };
// 7.5.2 Date Duration Records, https://tc39.es/proposal-temporal/#sec-temporal-date-duration-records // 7.5.2 Partial Duration Records, https://tc39.es/proposal-temporal/#sec-temporal-partial-duration-records
struct DateDurationRecord { struct PartialDuration {
double years; static PartialDuration zero()
double months; {
double weeks; return { .years = 0, .months = 0, .weeks = 0, .days = 0, .hours = 0, .minutes = 0, .seconds = 0, .milliseconds = 0, .microseconds = 0, .nanoseconds = 0 };
double days; }
};
// 7.5.3 Time Duration Records, https://tc39.es/proposal-temporal/#sec-temporal-time-duration-records bool any_field_defined() const
struct TimeDurationRecord { {
double days; return years.has_value() || months.has_value() || weeks.has_value() || days.has_value() || hours.has_value() || minutes.has_value() || seconds.has_value() || milliseconds.has_value() || microseconds.has_value() || nanoseconds.has_value();
double hours; }
double minutes;
double seconds;
double milliseconds;
double microseconds;
double nanoseconds;
};
// 7.5.4 Partial Duration Records, https://tc39.es/proposal-temporal/#sec-temporal-partial-duration-records
struct PartialDurationRecord {
Optional<double> years; Optional<double> years;
Optional<double> months; Optional<double> months;
Optional<double> weeks; Optional<double> weeks;
@ -100,79 +88,41 @@ struct PartialDurationRecord {
Optional<double> nanoseconds; Optional<double> nanoseconds;
}; };
// Used by MoveRelativeDate to temporarily hold values // A time duration is an integer in the inclusive interval from -maxTimeDuration to maxTimeDuration, where
struct MoveRelativeDateResult { // maxTimeDuration = 2**53 × 10**9 - 1 = 9,007,199,254,740,991,999,999,999. It represents the portion of a
GC::Root<PlainDate> relative_to; // Temporal.Duration object that deals with time units, but as a combined value of total nanoseconds.
double days; using TimeDuration = Crypto::SignedBigInteger;
extern TimeDuration const MAX_TIME_DURATION;
// 7.5.3 Internal Duration Records, https://tc39.es/proposal-temporal/#sec-temporal-internal-duration-records
struct InternalDuration {
DateDuration date;
TimeDuration time;
}; };
// Used by RoundDuration to temporarily hold values DateDuration zero_date_duration(VM&);
struct RoundedDuration { InternalDuration to_internal_duration_record(VM&, Duration const&);
DurationRecord duration_record; InternalDuration to_internal_duration_record_with_24_hour_days(VM&, Duration const&);
double total; ThrowCompletionOr<GC::Ref<Duration>> temporal_duration_from_internal(VM&, InternalDuration const&, Unit largest_unit);
}; ThrowCompletionOr<DateDuration> create_date_duration_record(VM&, double years, double months, double weeks, double days);
ThrowCompletionOr<InternalDuration> combine_date_and_time_duration(VM&, DateDuration, TimeDuration);
// Table 8: Duration Record Fields, https://tc39.es/proposal-temporal/#table-temporal-duration-record-fields ThrowCompletionOr<GC::Ref<Duration>> to_temporal_duration(VM&, Value);
i8 duration_sign(Duration const&);
template<typename StructT, typename ValueT> i8 date_duration_sign(DateDuration const&);
struct TemporalDurationRecordField {
ValueT StructT::*field_name { nullptr };
PropertyKey property_name;
};
DurationRecord create_duration_record(double years, double months, double weeks, double days, double hours, double minutes, double seconds, double milliseconds, double microseconds, double nanoseconds);
ThrowCompletionOr<DurationRecord> create_duration_record(VM&, double years, double months, double weeks, double days, double hours, double minutes, double seconds, double milliseconds, double microseconds, double nanoseconds);
DateDurationRecord create_date_duration_record(double years, double months, double weeks, double days);
ThrowCompletionOr<DateDurationRecord> create_date_duration_record(VM&, double years, double months, double weeks, double days);
ThrowCompletionOr<TimeDurationRecord> create_time_duration_record(VM&, double days, double hours, double minutes, double seconds, double milliseconds, double microseconds, double nanoseconds);
ThrowCompletionOr<GC::Ref<Duration>> to_temporal_duration(VM&, Value item);
ThrowCompletionOr<DurationRecord> to_temporal_duration_record(VM&, Value temporal_duration_like);
i8 duration_sign(double years, double months, double weeks, double days, double hours, double minutes, double seconds, double milliseconds, double microseconds, double nanoseconds);
bool is_valid_duration(double years, double months, double weeks, double days, double hours, double minutes, double seconds, double milliseconds, double microseconds, double nanoseconds); bool is_valid_duration(double years, double months, double weeks, double days, double hours, double minutes, double seconds, double milliseconds, double microseconds, double nanoseconds);
StringView default_temporal_largest_unit(double years, double months, double weeks, double days, double hours, double minutes, double seconds, double milliseconds, double microseconds); Unit default_temporal_largest_unit(Duration const&);
ThrowCompletionOr<PartialDurationRecord> to_temporal_partial_duration_record(VM&, Value temporal_duration_like); ThrowCompletionOr<PartialDuration> to_temporal_partial_duration_record(VM&, Value temporal_duration_like);
ThrowCompletionOr<GC::Ref<Duration>> create_temporal_duration(VM&, double years, double months, double weeks, double days, double hours, double minutes, double seconds, double milliseconds, double microseconds, double nanoseconds, FunctionObject const* new_target = nullptr); ThrowCompletionOr<GC::Ref<Duration>> create_temporal_duration(VM&, double years, double months, double weeks, double days, double hours, double minutes, double seconds, double milliseconds, double microseconds, double nanoseconds, GC::Ptr<FunctionObject> new_target = {});
GC::Ref<Duration> create_negated_temporal_duration(VM&, Duration const& duration); GC::Ref<Duration> create_negated_temporal_duration(VM&, Duration const&);
ThrowCompletionOr<double> calculate_offset_shift(VM&, Value relative_to_value, double years, double months, double weeks, double days); TimeDuration time_duration_from_components(double hours, double minutes, double seconds, double milliseconds, double microseconds, double nanoseconds);
Crypto::SignedBigInteger total_duration_nanoseconds(double days, double hours, double minutes, double seconds, double milliseconds, double microseconds, Crypto::SignedBigInteger const& nanoseconds, double offset_shift); ThrowCompletionOr<TimeDuration> add_time_duration(VM&, TimeDuration const&, TimeDuration const&);
ThrowCompletionOr<TimeDurationRecord> balance_time_duration(VM& vm, double days, double hours, double minutes, double seconds, double milliseconds, double microseconds, Crypto::SignedBigInteger const& nanoseconds, StringView largest_unit); ThrowCompletionOr<TimeDuration> add_24_hour_days_to_time_duration(VM&, TimeDuration const&, double days);
ThrowCompletionOr<TimeDurationRecord> balance_duration(VM&, double days, double hours, double minutes, double seconds, double milliseconds, double microseconds, Crypto::SignedBigInteger const& nanoseconds, StringView largest_unit, Object* relative_to = nullptr); i8 compare_time_duration(TimeDuration const&, TimeDuration const&);
ThrowCompletionOr<TimeDuration> round_time_duration_to_increment(VM&, TimeDuration const&, Crypto::UnsignedBigInteger const& increment, RoundingMode);
enum class Overflow { i8 time_duration_sign(TimeDuration const&);
Positive, ThrowCompletionOr<TimeDuration> round_time_duration(VM&, TimeDuration const&, Crypto::UnsignedBigInteger const& increment, Unit, RoundingMode);
Negative, double total_time_duration(TimeDuration const&, Unit);
}; String temporal_duration_to_string(Duration const&, Precision);
ThrowCompletionOr<GC::Ref<Duration>> add_durations(VM&, ArithmeticOperation, Duration const&, Value);
Variant<TimeDurationRecord, Overflow> balance_possibly_infinite_time_duration(VM& vm, double days, double hours, double minutes, double seconds, double milliseconds, double microseconds, Crypto::SignedBigInteger const& nanoseconds, StringView largest_unit);
ThrowCompletionOr<DateDurationRecord> unbalance_duration_relative(VM&, double years, double months, double weeks, double days, StringView largest_unit, Value relative_to);
ThrowCompletionOr<DateDurationRecord> balance_duration_relative(VM&, double years, double months, double weeks, double days, StringView largest_unit, Value relative_to);
ThrowCompletionOr<DurationRecord> add_duration(VM&, double years1, double months1, double weeks1, double days1, double hours1, double minutes1, double seconds1, double milliseconds1, double microseconds1, double nanoseconds1, double years2, double months2, double weeks2, double days2, double hours2, double minutes2, double seconds2, double milliseconds2, double microseconds2, double nanoseconds2, Value relative_to_value);
ThrowCompletionOr<MoveRelativeDateResult> move_relative_date(VM&, Object& calendar, PlainDate& relative_to, Duration& duration, FunctionObject* date_add);
ThrowCompletionOr<ZonedDateTime*> move_relative_zoned_date_time(VM&, ZonedDateTime&, double years, double months, double weeks, double days);
ThrowCompletionOr<RoundedDuration> round_duration(VM&, double years, double months, double weeks, double days, double hours, double minutes, double seconds, double milliseconds, double microseconds, double nanoseconds, u32 increment, StringView unit, StringView rounding_mode, Object* relative_to_object = nullptr, Optional<CalendarMethods> const& = {});
ThrowCompletionOr<DurationRecord> adjust_rounded_duration_days(VM&, double years, double months, double weeks, double days, double hours, double minutes, double seconds, double milliseconds, double microseconds, double nanoseconds, u32 increment, StringView unit, StringView rounding_mode, Object* relative_to_object);
ThrowCompletionOr<String> temporal_duration_to_string(VM&, double years, double months, double weeks, double days, double hours, double minutes, double seconds, double milliseconds, double microseconds, double nanoseconds, Variant<StringView, u8> const& precision);
ThrowCompletionOr<GC::Ref<Duration>> add_duration_to_or_subtract_duration_from_duration(VM&, ArithmeticOperation, Duration const&, Value other_value, Value options_value);
// 7.5.22 DaysUntil ( earlier, later ), https://tc39.es/proposal-temporal/#sec-temporal-daysuntil
template<typename EarlierObjectType, typename LaterObjectType>
double days_until(EarlierObjectType& earlier, LaterObjectType& later)
{
// 1. Let epochDays1 be MakeDay(𝔽(earlier.[[ISOYear]]), 𝔽(earlier.[[ISOMonth]] - 1), 𝔽(earlier.[[ISODay]])).
auto epoch_days_1 = make_day(earlier.iso_year(), earlier.iso_month() - 1, earlier.iso_day());
// 2. Assert: epochDays1 is finite.
VERIFY(isfinite(epoch_days_1));
// 3. Let epochDays2 be MakeDay(𝔽(later.[[ISOYear]]), 𝔽(later.[[ISOMonth]] - 1), 𝔽(later.[[ISODay]])).
auto epoch_days_2 = make_day(later.iso_year(), later.iso_month() - 1, later.iso_day());
// 4. Assert: epochDays2 is finite.
VERIFY(isfinite(epoch_days_2));
// 5. Return (epochDays2) - (epochDays1).
return epoch_days_2 - epoch_days_1;
}
} }

View file

@ -1,16 +1,14 @@
/* /*
* Copyright (c) 2021-2023, Linus Groh <linusg@serenityos.org> * Copyright (c) 2021-2023, Linus Groh <linusg@serenityos.org>
* Copyright (c) 2021, Luke Wilde <lukew@serenityos.org> * Copyright (c) 2021, Luke Wilde <lukew@serenityos.org>
* Copyright (c) 2024, Tim Flynn <trflynn89@ladybird.org>
* *
* SPDX-License-Identifier: BSD-2-Clause * SPDX-License-Identifier: BSD-2-Clause
*/ */
#include <AK/TypeCasts.h>
#include <LibJS/Runtime/GlobalObject.h>
#include <LibJS/Runtime/Temporal/AbstractOperations.h> #include <LibJS/Runtime/Temporal/AbstractOperations.h>
#include <LibJS/Runtime/Temporal/Duration.h> #include <LibJS/Runtime/Temporal/Duration.h>
#include <LibJS/Runtime/Temporal/DurationConstructor.h> #include <LibJS/Runtime/Temporal/DurationConstructor.h>
#include <LibJS/Runtime/ValueInlines.h>
namespace JS::Temporal { namespace JS::Temporal {
@ -53,55 +51,51 @@ ThrowCompletionOr<GC::Ref<Object>> DurationConstructor::construct(FunctionObject
{ {
auto& vm = this->vm(); auto& vm = this->vm();
// 2. Let y be ? ToIntegerIfIntegral(years). auto next_integer_argument = [&, index = 0]() mutable -> ThrowCompletionOr<double> {
auto y = TRY(to_integer_if_integral(vm, vm.argument(0), ErrorType::TemporalInvalidDuration)); if (auto value = vm.argument(index++); !value.is_undefined())
return to_integer_if_integral(vm, value, ErrorType::TemporalInvalidDuration);
return 0;
};
// 3. Let mo be ? ToIntegerIfIntegral(months). // 2. If years is undefined, let y be 0; else let y be ? ToIntegerIfIntegral(years).
auto mo = TRY(to_integer_if_integral(vm, vm.argument(1), ErrorType::TemporalInvalidDuration)); auto years = TRY(next_integer_argument());
// 4. Let w be ? ToIntegerIfIntegral(weeks). // 3. If months is undefined, let mo be 0; else let mo be ? ToIntegerIfIntegral(months).
auto w = TRY(to_integer_if_integral(vm, vm.argument(2), ErrorType::TemporalInvalidDuration)); auto months = TRY(next_integer_argument());
// 5. Let d be ? ToIntegerIfIntegral(days). // 4. If weeks is undefined, let w be 0; else let w be ? ToIntegerIfIntegral(weeks).
auto d = TRY(to_integer_if_integral(vm, vm.argument(3), ErrorType::TemporalInvalidDuration)); auto weeks = TRY(next_integer_argument());
// 6. Let h be ? ToIntegerIfIntegral(hours). // 5. If days is undefined, let d be 0; else let d be ? ToIntegerIfIntegral(days).
auto h = TRY(to_integer_if_integral(vm, vm.argument(4), ErrorType::TemporalInvalidDuration)); auto days = TRY(next_integer_argument());
// 7. Let m be ? ToIntegerIfIntegral(minutes). // 6. If hours is undefined, let h be 0; else let h be ? ToIntegerIfIntegral(hours).
auto m = TRY(to_integer_if_integral(vm, vm.argument(5), ErrorType::TemporalInvalidDuration)); auto hours = TRY(next_integer_argument());
// 8. Let s be ? ToIntegerIfIntegral(seconds). // 7. If minutes is undefined, let m be 0; else let m be ? ToIntegerIfIntegral(minutes).
auto s = TRY(to_integer_if_integral(vm, vm.argument(6), ErrorType::TemporalInvalidDuration)); auto minutes = TRY(next_integer_argument());
// 9. Let ms be ? ToIntegerIfIntegral(milliseconds). // 8. If seconds is undefined, let s be 0; else let s be ? ToIntegerIfIntegral(seconds).
auto ms = TRY(to_integer_if_integral(vm, vm.argument(7), ErrorType::TemporalInvalidDuration)); auto seconds = TRY(next_integer_argument());
// 10. Let mis be ? ToIntegerIfIntegral(microseconds). // 9. If milliseconds is undefined, let ms be 0; else let ms be ? ToIntegerIfIntegral(milliseconds).
auto mis = TRY(to_integer_if_integral(vm, vm.argument(8), ErrorType::TemporalInvalidDuration)); auto milliseconds = TRY(next_integer_argument());
// 11. Let ns be ? ToIntegerIfIntegral(nanoseconds). // 10. If microseconds is undefined, let mis be 0; else let mis be ? ToIntegerIfIntegral(microseconds).
auto ns = TRY(to_integer_if_integral(vm, vm.argument(9), ErrorType::TemporalInvalidDuration)); auto microseconds = TRY(next_integer_argument());
// 11. If nanoseconds is undefined, let ns be 0; else let ns be ? ToIntegerIfIntegral(nanoseconds).
auto nanoseconds = TRY(next_integer_argument());
// 12. Return ? CreateTemporalDuration(y, mo, w, d, h, m, s, ms, mis, ns, NewTarget). // 12. Return ? CreateTemporalDuration(y, mo, w, d, h, m, s, ms, mis, ns, NewTarget).
return TRY(create_temporal_duration(vm, y, mo, w, d, h, m, s, ms, mis, ns, &new_target)); return TRY(create_temporal_duration(vm, years, months, weeks, days, hours, minutes, seconds, milliseconds, microseconds, nanoseconds, new_target));
} }
// 7.2.2 Temporal.Duration.from ( item ), https://tc39.es/proposal-temporal/#sec-temporal.duration.from // 7.2.2 Temporal.Duration.from ( item ), https://tc39.es/proposal-temporal/#sec-temporal.duration.from
JS_DEFINE_NATIVE_FUNCTION(DurationConstructor::from) JS_DEFINE_NATIVE_FUNCTION(DurationConstructor::from)
{ {
auto item = vm.argument(0); // 1. Return ? ToTemporalDuration(item).
return TRY(to_temporal_duration(vm, vm.argument(0)));
// 1. If Type(item) is Object and item has an [[InitializedTemporalDuration]] internal slot, then
if (item.is_object() && is<Duration>(item.as_object())) {
auto& duration = static_cast<Duration&>(item.as_object());
// a. Return ! CreateTemporalDuration(item.[[Years]], item.[[Months]], item.[[Weeks]], item.[[Days]], item.[[Hours]], item.[[Minutes]], item.[[Seconds]], item.[[Milliseconds]], item.[[Microseconds]], item.[[Nanoseconds]]).
return MUST(create_temporal_duration(vm, duration.years(), duration.months(), duration.weeks(), duration.days(), duration.hours(), duration.minutes(), duration.seconds(), duration.milliseconds(), duration.microseconds(), duration.nanoseconds()));
}
// 2. Return ? ToTemporalDuration(item).
return TRY(to_temporal_duration(vm, item));
} }
// 7.2.3 Temporal.Duration.compare ( one, two [ , options ] ), https://tc39.es/proposal-temporal/#sec-temporal.duration.compare // 7.2.3 Temporal.Duration.compare ( one, two [ , options ] ), https://tc39.es/proposal-temporal/#sec-temporal.duration.compare
@ -113,36 +107,72 @@ JS_DEFINE_NATIVE_FUNCTION(DurationConstructor::compare)
// 2. Set two to ? ToTemporalDuration(two). // 2. Set two to ? ToTemporalDuration(two).
auto two = TRY(to_temporal_duration(vm, vm.argument(1))); auto two = TRY(to_temporal_duration(vm, vm.argument(1)));
// 3. Set options to ? GetOptionsObject(options). // 3. Let resolvedOptions be ? GetOptionsObject(options).
auto const* options = TRY(get_options_object(vm, vm.argument(2))); auto resolved_options = TRY(get_options_object(vm, vm.argument(2)));
// 4. Let relativeTo be ? ToRelativeTemporalObject(options). // 4. Let relativeToRecord be ? GetTemporalRelativeToOption(resolvedOptions).
auto relative_to = relative_to_converted_to_value(TRY(to_relative_temporal_object(vm, *options))); auto relative_to_record = TRY(get_temporal_relative_to_option(vm, resolved_options));
// 5. Let shift1 be ? CalculateOffsetShift(relativeTo, one.[[Years]], one.[[Months]], one.[[Weeks]], one.[[Days]]). // 5. If one.[[Years]] = two.[[Years]], and one.[[Months]] = two.[[Months]], and one.[[Weeks]] = two.[[Weeks]], and
auto shift1 = TRY(calculate_offset_shift(vm, relative_to, one->years(), one->months(), one->weeks(), one->days())); // one.[[Days]] = two.[[Days]], and one.[[Hours]] = two.[[Hours]], and one.[[Minutes]] = two.[[Minutes]], and
// one.[[Seconds]] = two.[[Seconds]], and one.[[Milliseconds]] = two.[[Milliseconds]], and
// 6. Let shift2 be ? CalculateOffsetShift(relativeTo, two.[[Years]], two.[[Months]], two.[[Weeks]], two.[[Days]]). // one.[[Microseconds]] = two.[[Microseconds]], and one.[[Nanoseconds]] = two.[[Nanoseconds]], then
auto shift2 = TRY(calculate_offset_shift(vm, relative_to, two->years(), two->months(), two->weeks(), two->days())); if (one->years() == two->years()
&& one->months() == two->months()
double days1; && one->weeks() == two->weeks()
double days2; && one->days() == two->days()
&& one->hours() == two->hours()
// 7. If any of one.[[Years]], two.[[Years]], one.[[Months]], two.[[Months]], one.[[Weeks]], or two.[[Weeks]] are not 0, then && one->minutes() == two->minutes()
if (one->years() != 0 || two->years() != 0 || one->months() != 0 || two->months() != 0 || one->weeks() != 0 || two->weeks() != 0) { && one->seconds() == two->seconds()
// a. Let unbalanceResult1 be ? UnbalanceDurationRelative(one.[[Years]], one.[[Months]], one.[[Weeks]], one.[[Days]], "day", relativeTo). && one->milliseconds() == two->milliseconds()
auto unbalance_result1 = TRY(unbalance_duration_relative(vm, one->years(), one->months(), one->weeks(), one->days(), "day"sv, relative_to)); && one->microseconds() == two->microseconds()
&& one->nanoseconds() == two->nanoseconds()) {
// b. Let unbalanceResult2 be ? UnbalanceDurationRelative(two.[[Years]], two.[[Months]], two.[[Weeks]], two.[[Days]], "day", relativeTo). // a. Return +0𝔽.
auto unbalance_result2 = TRY(unbalance_duration_relative(vm, two->years(), two->months(), two->weeks(), two->days(), "day"sv, relative_to)); return 0;
// c. Let days1 be unbalanceResult1.[[Days]].
days1 = unbalance_result1.days;
// d. Let days2 be unbalanceResult2.[[Days]].
days2 = unbalance_result2.days;
} }
// 8. Else,
// 6. Let zonedRelativeTo be relativeToRecord.[[ZonedRelativeTo]].
// 7. Let plainRelativeTo be relativeToRecord.[[PlainRelativeTo]].
auto [zoned_relative_to, plain_relative_to] = relative_to_record;
// 8. Let largestUnit1 be DefaultTemporalLargestUnit(one).
auto largest_unit1 = default_temporal_largest_unit(one);
// 9. Let largestUnit2 be DefaultTemporalLargestUnit(two).
auto largest_unit2 = default_temporal_largest_unit(two);
// 10. Let duration1 be ToInternalDurationRecord(one).
auto duration1 = to_internal_duration_record(vm, one);
// 11. Let duration2 be ToInternalDurationRecord(two).
auto duration2 = to_internal_duration_record(vm, two);
// 12. If zonedRelativeTo is not undefined, and either TemporalUnitCategory(largestUnit1) or TemporalUnitCategory(largestUnit2) is date, then
if (zoned_relative_to && (temporal_unit_category(largest_unit1) == UnitCategory::Date || temporal_unit_category(largest_unit2) == UnitCategory::Date)) {
// FIXME: a. Let timeZone be zonedRelativeTo.[[TimeZone]].
// FIXME: b. Let calendar be zonedRelativeTo.[[Calendar]].
// FIXME: c. Let after1 be ? AddZonedDateTime(zonedRelativeTo.[[EpochNanoseconds]], timeZone, calendar, duration1, constrain).
// FIXME: d. Let after2 be ? AddZonedDateTime(zonedRelativeTo.[[EpochNanoseconds]], timeZone, calendar, duration2, constrain).
// FIXME: e. If after1 > after2, return 1𝔽.
// FIXME: f. If after1 < after2, return -1𝔽.
// g. Return +0𝔽.
return 0;
}
double days1 = 0;
double days2 = 0;
// 13. If IsCalendarUnit(largestUnit1) is true or IsCalendarUnit(largestUnit2) is true, then
if (is_calendar_unit(largest_unit1) || is_calendar_unit(largest_unit2)) {
// a. If plainRelativeTo is undefined, throw a RangeError exception.
if (!plain_relative_to)
return vm.throw_completion<RangeError>(ErrorType::TemporalMissingStartingPoint, "calendar units");
// FIXME: b. Let days1 be ? DateDurationDays(duration1.[[Date]], plainRelativeTo).
// FIXME: c. Let days2 be ? DateDurationDays(duration2.[[Date]], plainRelativeTo).
}
// 14. Else,
else { else {
// a. Let days1 be one.[[Days]]. // a. Let days1 be one.[[Days]].
days1 = one->days(); days1 = one->days();
@ -151,22 +181,14 @@ JS_DEFINE_NATIVE_FUNCTION(DurationConstructor::compare)
days2 = two->days(); days2 = two->days();
} }
// 9. Let ns1 be ! TotalDurationNanoseconds(days1, one.[[Hours]], one.[[Minutes]], one.[[Seconds]], one.[[Milliseconds]], one.[[Microseconds]], one.[[Nanoseconds]], shift1). // 15. Let timeDuration1 be ? Add24HourDaysToTimeDuration(duration1.[[Time]], days1).
auto ns1 = total_duration_nanoseconds(days1, one->hours(), one->minutes(), one->seconds(), one->milliseconds(), one->microseconds(), Crypto::SignedBigInteger { one->nanoseconds() }, shift1); auto time_duration1 = TRY(add_24_hour_days_to_time_duration(vm, duration1.time, days1));
// 10. Let ns2 be ! TotalDurationNanoseconds(days2, two.[[Hours]], two.[[Minutes]], two.[[Seconds]], two.[[Milliseconds]], two.[[Microseconds]], two.[[Nanoseconds]], shift2). // 16. Let timeDuration2 be ? Add24HourDaysToTimeDuration(duration2.[[Time]], days2).
auto ns2 = total_duration_nanoseconds(days2, two->hours(), two->minutes(), two->seconds(), two->milliseconds(), two->microseconds(), Crypto::SignedBigInteger { two->nanoseconds() }, shift2); auto time_duration2 = TRY(add_24_hour_days_to_time_duration(vm, duration2.time, days2));
// 11. If ns1 > ns2, return 1𝔽. // 17. Return 𝔽(CompareTimeDuration(timeDuration1, timeDuration2)).
if (ns1 > ns2) return compare_time_duration(time_duration1, time_duration2);
return Value(1);
// 12. If ns1 < ns2, return -1𝔽.
if (ns1 < ns2)
return Value(-1);
// 13. Return +0𝔽.
return Value(0);
} }
} }

View file

@ -1,5 +1,6 @@
/* /*
* Copyright (c) 2021-2022, Linus Groh <linusg@serenityos.org> * Copyright (c) 2021-2022, Linus Groh <linusg@serenityos.org>
* Copyright (c) 2024, Tim Flynn <trflynn89@ladybird.org>
* *
* SPDX-License-Identifier: BSD-2-Clause * SPDX-License-Identifier: BSD-2-Clause
*/ */

View file

@ -1,16 +1,12 @@
/* /*
* Copyright (c) 2021-2023, Linus Groh <linusg@serenityos.org> * Copyright (c) 2021-2023, Linus Groh <linusg@serenityos.org>
* Copyright (c) 2024, Tim Flynn <trflynn89@ladybird.org>
* *
* SPDX-License-Identifier: BSD-2-Clause * SPDX-License-Identifier: BSD-2-Clause
*/ */
#include <AK/TypeCasts.h>
#include <LibJS/Runtime/GlobalObject.h>
#include <LibJS/Runtime/Temporal/AbstractOperations.h>
#include <LibJS/Runtime/Temporal/Duration.h> #include <LibJS/Runtime/Temporal/Duration.h>
#include <LibJS/Runtime/Temporal/DurationPrototype.h> #include <LibJS/Runtime/Temporal/DurationPrototype.h>
#include <LibJS/Runtime/Temporal/ZonedDateTime.h>
#include <math.h>
namespace JS::Temporal { namespace JS::Temporal {
@ -28,19 +24,14 @@ void DurationPrototype::initialize(Realm& realm)
auto& vm = this->vm(); auto& vm = this->vm();
// 7.3.2 Temporal.Duration.prototype[ @@toStringTag ], https://tc39.es/proposal-temporal/#sec-temporal.duration.prototype-@@tostringtag // 7.3.2 Temporal.Duration.prototype[ %Symbol.toStringTag% ], https://tc39.es/proposal-temporal/#sec-temporal.duration.prototype-%symbol.tostringtag%
define_direct_property(vm.well_known_symbol_to_string_tag(), PrimitiveString::create(vm, "Temporal.Duration"_string), Attribute::Configurable); define_direct_property(vm.well_known_symbol_to_string_tag(), PrimitiveString::create(vm, "Temporal.Duration"_string), Attribute::Configurable);
define_native_accessor(realm, vm.names.years, years_getter, {}, Attribute::Configurable); #define __JS_ENUMERATE(unit) \
define_native_accessor(realm, vm.names.months, months_getter, {}, Attribute::Configurable); define_native_accessor(realm, vm.names.unit, unit##_getter, {}, Attribute::Configurable);
define_native_accessor(realm, vm.names.weeks, weeks_getter, {}, Attribute::Configurable); JS_ENUMERATE_DURATION_UNITS
define_native_accessor(realm, vm.names.days, days_getter, {}, Attribute::Configurable); #undef __JS_ENUMERATE
define_native_accessor(realm, vm.names.hours, hours_getter, {}, Attribute::Configurable);
define_native_accessor(realm, vm.names.minutes, minutes_getter, {}, Attribute::Configurable);
define_native_accessor(realm, vm.names.seconds, seconds_getter, {}, Attribute::Configurable);
define_native_accessor(realm, vm.names.milliseconds, milliseconds_getter, {}, Attribute::Configurable);
define_native_accessor(realm, vm.names.microseconds, microseconds_getter, {}, Attribute::Configurable);
define_native_accessor(realm, vm.names.nanoseconds, nanoseconds_getter, {}, Attribute::Configurable);
define_native_accessor(realm, vm.names.sign, sign_getter, {}, Attribute::Configurable); define_native_accessor(realm, vm.names.sign, sign_getter, {}, Attribute::Configurable);
define_native_accessor(realm, vm.names.blank, blank_getter, {}, Attribute::Configurable); define_native_accessor(realm, vm.names.blank, blank_getter, {}, Attribute::Configurable);
@ -59,114 +50,27 @@ void DurationPrototype::initialize(Realm& realm)
} }
// 7.3.3 get Temporal.Duration.prototype.years, https://tc39.es/proposal-temporal/#sec-get-temporal.duration.prototype.years // 7.3.3 get Temporal.Duration.prototype.years, https://tc39.es/proposal-temporal/#sec-get-temporal.duration.prototype.years
JS_DEFINE_NATIVE_FUNCTION(DurationPrototype::years_getter)
{
// 1. Let duration be the this value.
// 2. Perform ? RequireInternalSlot(duration, [[InitializedTemporalDuration]]).
auto duration = TRY(typed_this_object(vm));
// 3. Return 𝔽(duration.[[Years]]).
return Value(duration->years());
}
// 7.3.4 get Temporal.Duration.prototype.months, https://tc39.es/proposal-temporal/#sec-get-temporal.duration.prototype.months // 7.3.4 get Temporal.Duration.prototype.months, https://tc39.es/proposal-temporal/#sec-get-temporal.duration.prototype.months
JS_DEFINE_NATIVE_FUNCTION(DurationPrototype::months_getter)
{
// 1. Let duration be the this value.
// 2. Perform ? RequireInternalSlot(duration, [[InitializedTemporalDuration]]).
auto duration = TRY(typed_this_object(vm));
// 3. Return 𝔽(duration.[[Months]]).
return Value(duration->months());
}
// 7.3.5 get Temporal.Duration.prototype.weeks, https://tc39.es/proposal-temporal/#sec-get-temporal.duration.prototype.weeks // 7.3.5 get Temporal.Duration.prototype.weeks, https://tc39.es/proposal-temporal/#sec-get-temporal.duration.prototype.weeks
JS_DEFINE_NATIVE_FUNCTION(DurationPrototype::weeks_getter)
{
// 1. Let duration be the this value.
// 2. Perform ? RequireInternalSlot(duration, [[InitializedTemporalDuration]]).
auto duration = TRY(typed_this_object(vm));
// 3. Return 𝔽(duration.[[Weeks]]).
return Value(duration->weeks());
}
// 7.3.6 get Temporal.Duration.prototype.days, https://tc39.es/proposal-temporal/#sec-get-temporal.duration.prototype.days // 7.3.6 get Temporal.Duration.prototype.days, https://tc39.es/proposal-temporal/#sec-get-temporal.duration.prototype.days
JS_DEFINE_NATIVE_FUNCTION(DurationPrototype::days_getter)
{
// 1. Let duration be the this value.
// 2. Perform ? RequireInternalSlot(duration, [[InitializedTemporalDuration]]).
auto duration = TRY(typed_this_object(vm));
// 3. Return 𝔽(duration.[[Days]]).
return Value(duration->days());
}
// 7.3.7 get Temporal.Duration.prototype.hours, https://tc39.es/proposal-temporal/#sec-get-temporal.duration.prototype.hours // 7.3.7 get Temporal.Duration.prototype.hours, https://tc39.es/proposal-temporal/#sec-get-temporal.duration.prototype.hours
JS_DEFINE_NATIVE_FUNCTION(DurationPrototype::hours_getter)
{
// 1. Let duration be the this value.
// 2. Perform ? RequireInternalSlot(duration, [[InitializedTemporalDuration]]).
auto duration = TRY(typed_this_object(vm));
// 3. Return 𝔽(duration.[[Hours]]).
return Value(duration->hours());
}
// 7.3.8 get Temporal.Duration.prototype.minutes, https://tc39.es/proposal-temporal/#sec-get-temporal.duration.prototype.minutes // 7.3.8 get Temporal.Duration.prototype.minutes, https://tc39.es/proposal-temporal/#sec-get-temporal.duration.prototype.minutes
JS_DEFINE_NATIVE_FUNCTION(DurationPrototype::minutes_getter)
{
// 1. Let duration be the this value.
// 2. Perform ? RequireInternalSlot(duration, [[InitializedTemporalDuration]]).
auto duration = TRY(typed_this_object(vm));
// 3. Return 𝔽(duration.[[Minutes]]).
return Value(duration->minutes());
}
// 7.3.9 get Temporal.Duration.prototype.seconds, https://tc39.es/proposal-temporal/#sec-get-temporal.duration.prototype.seconds // 7.3.9 get Temporal.Duration.prototype.seconds, https://tc39.es/proposal-temporal/#sec-get-temporal.duration.prototype.seconds
JS_DEFINE_NATIVE_FUNCTION(DurationPrototype::seconds_getter)
{
// 1. Let duration be the this value.
// 2. Perform ? RequireInternalSlot(duration, [[InitializedTemporalDuration]]).
auto duration = TRY(typed_this_object(vm));
// 3. Return 𝔽(duration.[[Seconds]]).
return Value(duration->seconds());
}
// 7.3.10 get Temporal.Duration.prototype.milliseconds, https://tc39.es/proposal-temporal/#sec-get-temporal.duration.prototype.milliseconds // 7.3.10 get Temporal.Duration.prototype.milliseconds, https://tc39.es/proposal-temporal/#sec-get-temporal.duration.prototype.milliseconds
JS_DEFINE_NATIVE_FUNCTION(DurationPrototype::milliseconds_getter)
{
// 1. Let duration be the this value.
// 2. Perform ? RequireInternalSlot(duration, [[InitializedTemporalDuration]]).
auto duration = TRY(typed_this_object(vm));
// 3. Return 𝔽(duration.[[Milliseconds]]).
return Value(duration->milliseconds());
}
// 7.3.11 get Temporal.Duration.prototype.microseconds, https://tc39.es/proposal-temporal/#sec-get-temporal.duration.prototype.microseconds // 7.3.11 get Temporal.Duration.prototype.microseconds, https://tc39.es/proposal-temporal/#sec-get-temporal.duration.prototype.microseconds
JS_DEFINE_NATIVE_FUNCTION(DurationPrototype::microseconds_getter)
{
// 1. Let duration be the this value.
// 2. Perform ? RequireInternalSlot(duration, [[InitializedTemporalDuration]]).
auto duration = TRY(typed_this_object(vm));
// 3. Return 𝔽(duration.[[Microseconds]]).
return Value(duration->microseconds());
}
// 7.3.12 get Temporal.Duration.prototype.nanoseconds, https://tc39.es/proposal-temporal/#sec-get-temporal.duration.prototype.nanoseconds // 7.3.12 get Temporal.Duration.prototype.nanoseconds, https://tc39.es/proposal-temporal/#sec-get-temporal.duration.prototype.nanoseconds
JS_DEFINE_NATIVE_FUNCTION(DurationPrototype::nanoseconds_getter) #define __JS_ENUMERATE(unit) \
{ JS_DEFINE_NATIVE_FUNCTION(DurationPrototype::unit##_getter) \
// 1. Let duration be the this value. { \
// 2. Perform ? RequireInternalSlot(duration, [[InitializedTemporalDuration]]). /* 1. Let duration be the this value. */ \
auto duration = TRY(typed_this_object(vm)); /* 2. Perform ? RequireInternalSlot(duration, [[InitializedTemporalDuration]]). */ \
auto duration = TRY(typed_this_object(vm)); \
// 3. Return 𝔽(duration.[[Nanoseconds]]). \
return Value(duration->nanoseconds()); /* 3. Return 𝔽(duration.[[<unit>]]). */ \
return duration->unit(); \
} }
JS_ENUMERATE_DURATION_UNITS
#undef __JS_ENUMERATE
// 7.3.13 get Temporal.Duration.prototype.sign, https://tc39.es/proposal-temporal/#sec-get-temporal.duration.prototype.sign // 7.3.13 get Temporal.Duration.prototype.sign, https://tc39.es/proposal-temporal/#sec-get-temporal.duration.prototype.sign
JS_DEFINE_NATIVE_FUNCTION(DurationPrototype::sign_getter) JS_DEFINE_NATIVE_FUNCTION(DurationPrototype::sign_getter)
@ -175,8 +79,8 @@ JS_DEFINE_NATIVE_FUNCTION(DurationPrototype::sign_getter)
// 2. Perform ? RequireInternalSlot(duration, [[InitializedTemporalDuration]]). // 2. Perform ? RequireInternalSlot(duration, [[InitializedTemporalDuration]]).
auto duration = TRY(typed_this_object(vm)); auto duration = TRY(typed_this_object(vm));
// 3. Return 𝔽(! DurationSign(duration.[[Years]], duration.[[Months]], duration.[[Weeks]], duration.[[Days]], duration.[[Hours]], duration.[[Minutes]], duration.[[Seconds]], duration.[[Milliseconds]], duration.[[Microseconds]], duration.[[Nanoseconds]])). // 3. Return 𝔽(DurationSign(duration)).
return Value(duration_sign(duration->years(), duration->months(), duration->weeks(), duration->days(), duration->hours(), duration->minutes(), duration->seconds(), duration->milliseconds(), duration->microseconds(), duration->nanoseconds())); return duration_sign(duration);
} }
// 7.3.14 get Temporal.Duration.prototype.blank, https://tc39.es/proposal-temporal/#sec-get-temporal.duration.prototype.blank // 7.3.14 get Temporal.Duration.prototype.blank, https://tc39.es/proposal-temporal/#sec-get-temporal.duration.prototype.blank
@ -186,15 +90,12 @@ JS_DEFINE_NATIVE_FUNCTION(DurationPrototype::blank_getter)
// 2. Perform ? RequireInternalSlot(duration, [[InitializedTemporalDuration]]). // 2. Perform ? RequireInternalSlot(duration, [[InitializedTemporalDuration]]).
auto duration = TRY(typed_this_object(vm)); auto duration = TRY(typed_this_object(vm));
// 3. Let sign be ! DurationSign(duration.[[Years]], duration.[[Months]], duration.[[Weeks]], duration.[[Days]], duration.[[Hours]], duration.[[Minutes]], duration.[[Seconds]], duration.[[Milliseconds]], duration.[[Microseconds]], duration.[[Nanoseconds]]). // 3. If DurationSign(duration) = 0, return true.
auto sign = duration_sign(duration->years(), duration->months(), duration->weeks(), duration->days(), duration->hours(), duration->minutes(), duration->seconds(), duration->milliseconds(), duration->microseconds(), duration->nanoseconds()); if (duration_sign(duration) == 0)
return true;
// 4. If sign = 0, return true. // 4. Return false.
if (sign == 0) return false;
return Value(true);
// 5. Return false.
return Value(false);
} }
// 7.3.15 Temporal.Duration.prototype.with ( temporalDurationLike ), https://tc39.es/proposal-temporal/#sec-temporal.duration.prototype.with // 7.3.15 Temporal.Duration.prototype.with ( temporalDurationLike ), https://tc39.es/proposal-temporal/#sec-temporal.duration.prototype.with
@ -278,7 +179,7 @@ JS_DEFINE_NATIVE_FUNCTION(DurationPrototype::negated)
// 2. Perform ? RequireInternalSlot(duration, [[InitializedTemporalDuration]]). // 2. Perform ? RequireInternalSlot(duration, [[InitializedTemporalDuration]]).
auto duration = TRY(typed_this_object(vm)); auto duration = TRY(typed_this_object(vm));
// 3. Return ! CreateNegatedTemporalDuration(duration). // 3. Return CreateNegatedTemporalDuration(duration).
return create_negated_temporal_duration(vm, duration); return create_negated_temporal_duration(vm, duration);
} }
@ -290,35 +191,33 @@ JS_DEFINE_NATIVE_FUNCTION(DurationPrototype::abs)
auto duration = TRY(typed_this_object(vm)); auto duration = TRY(typed_this_object(vm));
// 3. Return ! CreateTemporalDuration(abs(duration.[[Years]]), abs(duration.[[Months]]), abs(duration.[[Weeks]]), abs(duration.[[Days]]), abs(duration.[[Hours]]), abs(duration.[[Minutes]]), abs(duration.[[Seconds]]), abs(duration.[[Milliseconds]]), abs(duration.[[Microseconds]]), abs(duration.[[Nanoseconds]])). // 3. Return ! CreateTemporalDuration(abs(duration.[[Years]]), abs(duration.[[Months]]), abs(duration.[[Weeks]]), abs(duration.[[Days]]), abs(duration.[[Hours]]), abs(duration.[[Minutes]]), abs(duration.[[Seconds]]), abs(duration.[[Milliseconds]]), abs(duration.[[Microseconds]]), abs(duration.[[Nanoseconds]])).
return TRY(create_temporal_duration(vm, fabs(duration->years()), fabs(duration->months()), fabs(duration->weeks()), fabs(duration->days()), fabs(duration->hours()), fabs(duration->minutes()), fabs(duration->seconds()), fabs(duration->milliseconds()), fabs(duration->microseconds()), fabs(duration->nanoseconds()))); return MUST(create_temporal_duration(vm, fabs(duration->years()), fabs(duration->months()), fabs(duration->weeks()), fabs(duration->days()), fabs(duration->hours()), fabs(duration->minutes()), fabs(duration->seconds()), fabs(duration->milliseconds()), fabs(duration->microseconds()), fabs(duration->nanoseconds())));
} }
// 7.3.18 Temporal.Duration.prototype.add ( other [ , options ] ), https://tc39.es/proposal-temporal/#sec-temporal.duration.prototype.add // 7.3.18 Temporal.Duration.prototype.add ( other ), https://tc39.es/proposal-temporal/#sec-temporal.duration.prototype.add
JS_DEFINE_NATIVE_FUNCTION(DurationPrototype::add) JS_DEFINE_NATIVE_FUNCTION(DurationPrototype::add)
{ {
auto other = vm.argument(0); auto other = vm.argument(0);
auto options = vm.argument(1);
// 1. Let duration be the this value. // 1. Let duration be the this value.
// 2. Perform ? RequireInternalSlot(duration, [[InitializedTemporalDuration]]). // 2. Perform ? RequireInternalSlot(duration, [[InitializedTemporalDuration]]).
auto duration = TRY(typed_this_object(vm)); auto duration = TRY(typed_this_object(vm));
// 3. Return ? AddDurationToOrSubtractDurationFromDuration(add, duration, other, options). // 3. Return ? AddDurations(ADD, duration, other).
return TRY(add_duration_to_or_subtract_duration_from_duration(vm, ArithmeticOperation::Add, duration, other, options)); return TRY(add_durations(vm, ArithmeticOperation::Add, duration, other));
} }
// 7.3.19 Temporal.Duration.prototype.subtract ( other [ , options ] ), https://tc39.es/proposal-temporal/#sec-temporal.duration.prototype.subtract // 7.3.19 Temporal.Duration.prototype.subtract ( other ), https://tc39.es/proposal-temporal/#sec-temporal.duration.prototype.subtract
JS_DEFINE_NATIVE_FUNCTION(DurationPrototype::subtract) JS_DEFINE_NATIVE_FUNCTION(DurationPrototype::subtract)
{ {
auto other = vm.argument(0); auto other = vm.argument(0);
auto options = vm.argument(1);
// 1. Let duration be the this value. // 1. Let duration be the this value.
// 2. Perform ? RequireInternalSlot(duration, [[InitializedTemporalDuration]]). // 2. Perform ? RequireInternalSlot(duration, [[InitializedTemporalDuration]]).
auto duration = TRY(typed_this_object(vm)); auto duration = TRY(typed_this_object(vm));
// 3. Return ? AddDurationToOrSubtractDurationFromDuration(subtract, duration, other, options). // 3. Return ? AddDurations(SUBTRACT, duration, other).
return TRY(add_duration_to_or_subtract_duration_from_duration(vm, ArithmeticOperation::Subtract, duration, other, options)); return TRY(add_durations(vm, ArithmeticOperation::Subtract, duration, other));
} }
// 7.3.20 Temporal.Duration.prototype.round ( roundTo ), https://tc39.es/proposal-temporal/#sec-temporal.duration.prototype.round // 7.3.20 Temporal.Duration.prototype.round ( roundTo ), https://tc39.es/proposal-temporal/#sec-temporal.duration.prototype.round
@ -338,17 +237,18 @@ JS_DEFINE_NATIVE_FUNCTION(DurationPrototype::round)
return vm.throw_completion<TypeError>(ErrorType::TemporalMissingOptionsObject); return vm.throw_completion<TypeError>(ErrorType::TemporalMissingOptionsObject);
} }
Object* round_to; GC::Ptr<Object> round_to;
// 4. If Type(roundTo) is String, then // 4. If roundTo is a String, then
if (round_to_value.is_string()) { if (round_to_value.is_string()) {
// a. Let paramString be roundTo. // a. Let paramString be roundTo.
auto param_string = round_to_value;
// b. Set roundTo to OrdinaryObjectCreate(null). // b. Set roundTo to OrdinaryObjectCreate(null).
round_to = Object::create(realm, nullptr); round_to = Object::create(realm, nullptr);
// c. Perform ! CreateDataPropertyOrThrow(roundTo, "smallestUnit", paramString). // c. Perform ! CreateDataPropertyOrThrow(roundTo, "smallestUnit", paramString).
MUST(round_to->create_data_property_or_throw(vm.names.smallestUnit, vm.argument(0))); MUST(round_to->create_data_property_or_throw(vm.names.smallestUnit, param_string));
} }
// 5. Else, // 5. Else,
else { else {
@ -362,156 +262,244 @@ JS_DEFINE_NATIVE_FUNCTION(DurationPrototype::round)
// 7. Let largestUnitPresent be true. // 7. Let largestUnitPresent be true.
bool largest_unit_present = true; bool largest_unit_present = true;
// 8. Let smallestUnit be ? GetTemporalUnit(roundTo, "smallestUnit", datetime, undefined). // 8. NOTE: The following steps read options and perform independent validation in alphabetical order
auto smallest_unit = TRY(get_temporal_unit(vm, *round_to, vm.names.smallestUnit, UnitGroup::DateTime, Optional<StringView> {})); // (GetTemporalRelativeToOption reads "relativeTo", GetRoundingIncrementOption reads "roundingIncrement" and
// GetRoundingModeOption reads "roundingMode").
// 9. If smallestUnit is undefined, then // 9. Let largestUnit be ? GetTemporalUnitValuedOption(roundTo, "largestUnit", DATETIME, UNSET, « auto »).
if (!smallest_unit.has_value()) { auto largest_unit = TRY(get_temporal_unit_valued_option(vm, *round_to, vm.names.largestUnit, UnitGroup::DateTime, Unset {}, { { Auto {} } }));
// 10. Let relativeToRecord be ? GetTemporalRelativeToOption(roundTo).
// 11. Let zonedRelativeTo be relativeToRecord.[[ZonedRelativeTo]].
// 12. Let plainRelativeTo be relativeToRecord.[[PlainRelativeTo]].
auto [zoned_relative_to, plain_relative_to] = TRY(get_temporal_relative_to_option(vm, *round_to));
// 13. Let roundingIncrement be ? GetRoundingIncrementOption(roundTo).
auto rounding_increment = TRY(get_rounding_increment_option(vm, *round_to));
// 14. Let roundingMode be ? GetRoundingModeOption(roundTo, HALF-EXPAND).
auto rounding_mode = TRY(get_rounding_mode_option(vm, *round_to, RoundingMode::HalfExpand));
// 15. Let smallestUnit be ? GetTemporalUnitValuedOption(roundTo, "smallestUnit", DATETIME, UNSET).
auto smallest_unit = TRY(get_temporal_unit_valued_option(vm, *round_to, vm.names.smallestUnit, UnitGroup::DateTime, Unset {}));
// 16. If smallestUnit is UNSET, then
if (smallest_unit.has<Unset>()) {
// a. Set smallestUnitPresent to false. // a. Set smallestUnitPresent to false.
smallest_unit_present = false; smallest_unit_present = false;
// b. Set smallestUnit to "nanosecond". // b. Set smallestUnit to NANOSECOND.
smallest_unit = "nanosecond"_string; smallest_unit = Unit::Nanosecond;
} }
// 10. Let defaultLargestUnit be ! DefaultTemporalLargestUnit(duration.[[Years]], duration.[[Months]], duration.[[Weeks]], duration.[[Days]], duration.[[Hours]], duration.[[Minutes]], duration.[[Seconds]], duration.[[Milliseconds]], duration.[[Microseconds]]). auto smallest_unit_value = smallest_unit.get<Unit>();
auto default_largest_unit = default_temporal_largest_unit(duration->years(), duration->months(), duration->weeks(), duration->days(), duration->hours(), duration->minutes(), duration->seconds(), duration->milliseconds(), duration->microseconds());
// 11. Set defaultLargestUnit to ! LargerOfTwoTemporalUnits(defaultLargestUnit, smallestUnit). // 17. Let existingLargestUnit be DefaultTemporalLargestUnit(duration).
default_largest_unit = larger_of_two_temporal_units(default_largest_unit, *smallest_unit); auto existing_largest_unit = default_temporal_largest_unit(duration);
// 12. Let largestUnit be ? GetTemporalUnit(roundTo, "largestUnit", datetime, undefined, « "auto" »). // 18. Let defaultLargestUnit be LargerOfTwoTemporalUnits(existingLargestUnit, smallestUnit).
auto largest_unit = TRY(get_temporal_unit(vm, *round_to, vm.names.largestUnit, UnitGroup::DateTime, Optional<StringView> {}, { "auto"sv })); auto default_largest_unit = larger_of_two_temporal_units(existing_largest_unit, smallest_unit_value);
// 13. If largestUnit is undefined, then // 19. If largestUnit is UNSET, then
if (!largest_unit.has_value()) { if (largest_unit.has<Unset>()) {
// a. Set largestUnitPresent to false. // a. Set largestUnitPresent to false.
largest_unit_present = false; largest_unit_present = false;
// b. Set largestUnit to defaultLargestUnit. // b. Set largestUnit to defaultLargestUnit.
largest_unit = MUST(String::from_utf8(default_largest_unit)); largest_unit = default_largest_unit;
} }
// 14. Else if largestUnit is "auto", then // 20. Else if largestUnit is AUTO, then
else if (*largest_unit == "auto"sv) { else if (largest_unit.has<Auto>()) {
// a. Set largestUnit to defaultLargestUnit. // a. Set largestUnit to defaultLargestUnit.
largest_unit = MUST(String::from_utf8(default_largest_unit)); largest_unit = default_largest_unit;
} }
// 15. If smallestUnitPresent is false and largestUnitPresent is false, then // 21. If smallestUnitPresent is false and largestUnitPresent is false, then
if (!smallest_unit_present && !largest_unit_present) { if (!smallest_unit_present && !largest_unit_present) {
// a. Throw a RangeError exception. // a. Throw a RangeError exception.
return vm.throw_completion<RangeError>(ErrorType::TemporalMissingUnits); return vm.throw_completion<RangeError>(ErrorType::TemporalMissingUnits);
} }
// 16. If LargerOfTwoTemporalUnits(largestUnit, smallestUnit) is not largestUnit, throw a RangeError exception. auto largest_unit_value = largest_unit.get<Unit>();
if (larger_of_two_temporal_units(*largest_unit, *smallest_unit) != largest_unit)
return vm.throw_completion<RangeError>(ErrorType::TemporalInvalidUnitRange, *smallest_unit, *largest_unit);
// 17. Let roundingMode be ? ToTemporalRoundingMode(roundTo, "halfExpand"). // 22. If LargerOfTwoTemporalUnits(largestUnit, smallestUnit) is not largestUnit, throw a RangeError exception.
auto rounding_mode = TRY(to_temporal_rounding_mode(vm, *round_to, "halfExpand"sv)); if (larger_of_two_temporal_units(largest_unit_value, smallest_unit_value) != largest_unit_value)
return vm.throw_completion<RangeError>(ErrorType::TemporalInvalidUnitRange, temporal_unit_to_string(smallest_unit_value), temporal_unit_to_string(largest_unit_value));
// 18. Let maximum be ! MaximumTemporalDurationRoundingIncrement(smallestUnit). // 23. Let maximum be MaximumTemporalDurationRoundingIncrement(smallestUnit).
auto maximum = maximum_temporal_duration_rounding_increment(*smallest_unit); auto maximum = maximum_temporal_duration_rounding_increment(smallest_unit_value);
// 19. Let roundingIncrement be ? ToTemporalRoundingIncrement(options). // 24. If maximum is not UNSET, perform ? ValidateTemporalRoundingIncrement(roundingIncrement, maximum, false).
auto rounding_increment = TRY(to_temporal_rounding_increment(vm, *round_to)); if (!maximum.has<Unset>())
TRY(validate_temporal_rounding_increment(vm, rounding_increment, maximum.get<u64>(), false));
// 20. If maximum is not undefined, perform ? ValidateTemporalRoundingIncrement(roundingIncrement, maximum, false). // 25. If roundingIncrement > 1, and largestUnit is not smallestUnit, and TemporalUnitCategory(smallestUnit) is DATE,
if (maximum.has_value()) { // throw a RangeError exception.
TRY(validate_temporal_rounding_increment(vm, rounding_increment, *maximum, false)); if (rounding_increment > 1 && largest_unit_value != smallest_unit_value && temporal_unit_category(smallest_unit_value) == UnitCategory::Date)
return vm.throw_completion<RangeError>(ErrorType::OptionIsNotValidValue, rounding_increment, "roundingIncrement");
// 26. If zonedRelativeTo is not undefined, then
if (zoned_relative_to) {
// a. Let internalDuration be ToInternalDurationRecord(duration).
auto internal_duration = to_internal_duration_record(vm, duration);
// FIXME: b. Let timeZone be zonedRelativeTo.[[TimeZone]].
// FIXME: c. Let calendar be zonedRelativeTo.[[Calendar]].
// FIXME: d. Let relativeEpochNs be zonedRelativeTo.[[EpochNanoseconds]].
// FIXME: e. Let targetEpochNs be ? AddZonedDateTime(relativeEpochNs, timeZone, calendar, internalDuration, constrain).
// FIXME: f. Set internalDuration to ? DifferenceZonedDateTimeWithRounding(relativeEpochNs, targetEpochNs, timeZone, calendar, largestUnit, roundingIncrement, smallestUnit, roundingMode).
// g. If TemporalUnitCategory(largestUnit) is date, set largestUnit to hour.
if (temporal_unit_category(largest_unit_value) == UnitCategory::Date)
largest_unit_value = Unit::Hour;
// h. Return ? TemporalDurationFromInternal(internalDuration, largestUnit).
return TRY(temporal_duration_from_internal(vm, internal_duration, largest_unit_value));
} }
// 21. Let relativeTo be ? ToRelativeTemporalObject(roundTo). // 27. If plainRelativeTo is not undefined, then
auto relative_to = TRY(to_relative_temporal_object(vm, *round_to)); if (plain_relative_to) {
auto relative_to_value = relative_to_converted_to_value(relative_to); // a. Let internalDuration be ToInternalDurationRecordWith24HourDays(duration).
auto internal_duration = to_internal_duration_record_with_24_hour_days(vm, duration);
// 22. Let unbalanceResult be ? UnbalanceDurationRelative(duration.[[Years]], duration.[[Months]], duration.[[Weeks]], duration.[[Days]], largestUnit, relativeTo). // FIXME: b. Let targetTime be AddTime(MidnightTimeRecord(), internalDuration.[[Time]]).
auto unbalance_result = TRY(unbalance_duration_relative(vm, duration->years(), duration->months(), duration->weeks(), duration->days(), *largest_unit, relative_to_value)); // FIXME: c. Let calendar be plainRelativeTo.[[Calendar]].
// FIXME: d. Let dateDuration be ! AdjustDateDurationRecord(internalDuration.[[Date]], targetTime.[[Days]]).
// FIXME: e. Let targetDate be ? CalendarDateAdd(calendar, plainRelativeTo.[[ISODate]], dateDuration, constrain).
// FIXME: f. Let isoDateTime be CombineISODateAndTimeRecord(plainRelativeTo.[[ISODate]], MidnightTimeRecord()).
// FIXME: g. Let targetDateTime be CombineISODateAndTimeRecord(targetDate, targetTime).
// FIXME: h. Set internalDuration to ? DifferencePlainDateTimeWithRounding(isoDateTime, targetDateTime, calendar, largestUnit, roundingIncrement, smallestUnit, roundingMode).
auto calendar_record = TRY(create_calendar_methods_record_from_relative_to(vm, relative_to.plain_relative_to, relative_to.zoned_relative_to, { { CalendarMethod::DateAdd, CalendarMethod::DateUntil } })); // i. Return ? TemporalDurationFromInternal(internalDuration, largestUnit).
// 23. Let roundResult be (? RoundDuration(unbalanceResult.[[Years]], unbalanceResult.[[Months]], unbalanceResult.[[Weeks]], unbalanceResult.[[Days]], duration.[[Hours]], duration.[[Minutes]], duration.[[Seconds]], duration.[[Milliseconds]], duration.[[Microseconds]], duration.[[Nanoseconds]], roundingIncrement, smallestUnit, roundingMode, relativeTo)).[[DurationRecord]]. return TRY(temporal_duration_from_internal(vm, internal_duration, largest_unit_value));
auto round_result = TRY(round_duration(vm, unbalance_result.years, unbalance_result.months, unbalance_result.weeks, unbalance_result.days, duration->hours(), duration->minutes(), duration->seconds(), duration->milliseconds(), duration->microseconds(), duration->nanoseconds(), rounding_increment, *smallest_unit, rounding_mode, relative_to_value.is_object() ? &relative_to_value.as_object() : nullptr, calendar_record)).duration_record;
// 24. Let adjustResult be ? AdjustRoundedDurationDays(roundResult.[[Years]], roundResult.[[Months]], roundResult.[[Weeks]], roundResult.[[Days]], roundResult.[[Hours]], roundResult.[[Minutes]], roundResult.[[Seconds]], roundResult.[[Milliseconds]], roundResult.[[Microseconds]], roundResult.[[Nanoseconds]], roundingIncrement, smallestUnit, roundingMode, relativeTo).
auto adjust_result = TRY(adjust_rounded_duration_days(vm, round_result.years, round_result.months, round_result.weeks, round_result.days, round_result.hours, round_result.minutes, round_result.seconds, round_result.milliseconds, round_result.microseconds, round_result.nanoseconds, rounding_increment, *smallest_unit, rounding_mode, relative_to_value.is_object() ? &relative_to_value.as_object() : nullptr));
// 25. Let balanceResult be ? BalanceDurationRelative(adjustResult.[[Years]], adjustResult.[[Months]], adjustResult.[[Weeks]], adjustResult.[[Days]], largestUnit, relativeTo).
auto balance_result = TRY(balance_duration_relative(vm, adjust_result.years, adjust_result.months, adjust_result.weeks, adjust_result.days, *largest_unit, relative_to_value));
// 26. If Type(relativeTo) is Object and relativeTo has an [[InitializedTemporalZonedDateTime]] internal slot, then
if (relative_to.zoned_relative_to) {
// a. Set relativeTo to ? MoveRelativeZonedDateTime(relativeTo, balanceResult.[[Years]], balanceResult.[[Months]], balanceResult.[[Weeks]], 0).
relative_to_value = TRY(move_relative_zoned_date_time(vm, *relative_to.zoned_relative_to, balance_result.years, balance_result.months, balance_result.weeks, 0));
} }
// 27. Let result be ? BalanceDuration(balanceResult.[[Days]], adjustResult.[[Hours]], adjustResult.[[Minutes]], adjustResult.[[Seconds]], adjustResult.[[Milliseconds]], adjustResult.[[Microseconds]], adjustResult.[[Nanoseconds]], largestUnit, relativeTo). // 28. If IsCalendarUnit(existingLargestUnit) is true, or IsCalendarUnit(largestUnit) is true, throw a RangeError exception.
auto result = TRY(balance_duration(vm, balance_result.days, adjust_result.hours, adjust_result.minutes, adjust_result.seconds, adjust_result.milliseconds, adjust_result.microseconds, Crypto::SignedBigInteger { adjust_result.nanoseconds }, *largest_unit, relative_to_value.is_object() ? &relative_to_value.as_object() : nullptr)); if (is_calendar_unit(existing_largest_unit))
return vm.throw_completion<RangeError>(ErrorType::TemporalInvalidLargestUnit, temporal_unit_to_string(existing_largest_unit));
if (is_calendar_unit(largest_unit_value))
return vm.throw_completion<RangeError>(ErrorType::TemporalInvalidLargestUnit, temporal_unit_to_string(largest_unit_value));
// 28. Return ! CreateTemporalDuration(balanceResult.[[Years]], balanceResult.[[Months]], balanceResult.[[Weeks]], result.[[Days]], result.[[Hours]], result.[[Minutes]], result.[[Seconds]], result.[[Milliseconds]], result.[[Microseconds]], result.[[Nanoseconds]]). // 29. Assert: IsCalendarUnit(smallestUnit) is false.
return MUST(create_temporal_duration(vm, balance_result.years, balance_result.months, balance_result.weeks, result.days, result.hours, result.minutes, result.seconds, result.milliseconds, result.microseconds, result.nanoseconds)); VERIFY(!is_calendar_unit(smallest_unit_value));
// 30. Let internalDuration be ToInternalDurationRecordWith24HourDays(duration).
auto internal_duration = to_internal_duration_record_with_24_hour_days(vm, duration);
// 31. If smallestUnit is DAY, then
if (smallest_unit_value == Unit::Day) {
// a. Let fractionalDays be TotalTimeDuration(internalDuration.[[Time]], DAY).
auto fractional_days = total_time_duration(internal_duration.time, Unit::Day);
// b. Let days be RoundNumberToIncrement(fractionalDays, roundingIncrement, roundingMode).
auto days = round_number_to_increment(fractional_days, rounding_increment, rounding_mode);
// c. Let dateDuration be ? CreateDateDurationRecord(0, 0, 0, days).
auto date_duration = TRY(create_date_duration_record(vm, 0, 0, 0, days));
// d. Set internalDuration to ! CombineDateAndTimeDuration(dateDuration, 0).
internal_duration = MUST(combine_date_and_time_duration(vm, date_duration, TimeDuration { 0 }));
}
// 32. Else,
else {
// a. Let timeDuration be ? RoundTimeDuration(internalDuration.[[Time]], roundingIncrement, smallestUnit, roundingMode).
auto time_duration = TRY(round_time_duration(vm, internal_duration.time, Crypto::UnsignedBigInteger { rounding_increment }, smallest_unit_value, rounding_mode));
// b. Set internalDuration to ! CombineDateAndTimeDuration(ZeroDateDuration(), timeDuration).
internal_duration = MUST(combine_date_and_time_duration(vm, zero_date_duration(vm), move(time_duration)));
}
// 33. Return ? TemporalDurationFromInternal(internalDuration, largestUnit).
return TRY(temporal_duration_from_internal(vm, internal_duration, largest_unit_value));
} }
// 7.3.21 Temporal.Duration.prototype.total ( totalOf ), https://tc39.es/proposal-temporal/#sec-temporal.duration.prototype.total // 7.3.21 Temporal.Duration.prototype.total ( totalOf ), https://tc39.es/proposal-temporal/#sec-temporal.duration.prototype.total
// FIXME: This is well out of date with the spec.
JS_DEFINE_NATIVE_FUNCTION(DurationPrototype::total) JS_DEFINE_NATIVE_FUNCTION(DurationPrototype::total)
{ {
auto& realm = *vm.current_realm(); auto& realm = *vm.current_realm();
auto total_of_value = vm.argument(0);
// 1. Let duration be the this value. // 1. Let duration be the this value.
// 2. Perform ? RequireInternalSlot(duration, [[InitializedTemporalDuration]]). // 2. Perform ? RequireInternalSlot(duration, [[InitializedTemporalDuration]]).
auto duration = TRY(typed_this_object(vm)); auto duration = TRY(typed_this_object(vm));
// 3. If totalOf is undefined, throw a TypeError exception. // 3. If totalOf is undefined, throw a TypeError exception.
if (vm.argument(0).is_undefined()) if (total_of_value.is_undefined())
return vm.throw_completion<TypeError>(ErrorType::TemporalMissingOptionsObject); return vm.throw_completion<TypeError>(ErrorType::IsUndefined, "totalOf"sv);
Object* total_of; GC::Ptr<Object> total_of;
// 4. If Type(totalOf) is String, then // 4. If totalOf is a String, then
if (vm.argument(0).is_string()) { if (total_of_value.is_string()) {
// a. Let paramString be totalOf. // a. Let paramString be totalOf.
auto param_string = total_of_value;
// b. Set totalOf to OrdinaryObjectCreate(null). // b. Set totalOf to OrdinaryObjectCreate(null).
total_of = Object::create(realm, nullptr); total_of = Object::create(realm, nullptr);
// c. Perform ! CreateDataPropertyOrThrow(totalOf, "unit", paramString). // c. Perform ! CreateDataPropertyOrThrow(totalOf, "unit", paramString).
MUST(total_of->create_data_property_or_throw(vm.names.unit, vm.argument(0))); MUST(total_of->create_data_property_or_throw(vm.names.unit, param_string));
} }
// 5. Else, // 5. Else,
else { else {
// a. Set totalOf to ? GetOptionsObject(totalOf). // a. Set totalOf to ? GetOptionsObject(totalOf).
total_of = TRY(get_options_object(vm, vm.argument(0))); total_of = TRY(get_options_object(vm, total_of_value));
} }
// 6. Let relativeTo be ? ToRelativeTemporalObject(totalOf). // 6. NOTE: The following steps read options and perform independent validation in alphabetical order
auto relative_to = TRY(to_relative_temporal_object(vm, *total_of)); // (GetTemporalRelativeToOption reads "relativeTo").
auto relative_to_value = relative_to_converted_to_value(relative_to);
// 7. Let unit be ? GetTemporalUnit(totalOf, "unit", datetime, required). // 7. Let relativeToRecord be ? GetTemporalRelativeToOption(totalOf).
auto unit = TRY(get_temporal_unit(vm, *total_of, vm.names.unit, UnitGroup::DateTime, TemporalUnitRequired {})); // 8. Let zonedRelativeTo be relativeToRecord.[[ZonedRelativeTo]].
// 9. Let plainRelativeTo be relativeToRecord.[[PlainRelativeTo]].
auto [zoned_relative_to, plain_relative_to] = TRY(get_temporal_relative_to_option(vm, *total_of));
// 8. Let unbalanceResult be ? UnbalanceDurationRelative(duration.[[Years]], duration.[[Months]], duration.[[Weeks]], duration.[[Days]], unit, relativeTo). // 10. Let unit be ? GetTemporalUnitValuedOption(totalOf, "unit", DATETIME, REQUIRED).
auto unbalance_result = TRY(unbalance_duration_relative(vm, duration->years(), duration->months(), duration->weeks(), duration->days(), *unit, relative_to_value)); auto unit = TRY(get_temporal_unit_valued_option(vm, *total_of, vm.names.unit, UnitGroup::DateTime, Required {})).get<Unit>();
// 9. Let intermediate be undefined. double total = 0;
ZonedDateTime* intermediate = nullptr;
// 10. If Type(relativeTo) is Object and relativeTo has an [[InitializedTemporalZonedDateTime]] internal slot, then // 11. If zonedRelativeTo is not undefined, then
if (relative_to.zoned_relative_to) { if (zoned_relative_to) {
// a. Set intermediate to ? MoveRelativeZonedDateTime(relativeTo, unbalanceResult.[[Years]], unbalanceResult.[[Months]], unbalanceResult.[[Weeks]], 0). // FIXME: a. Let internalDuration be ToInternalDurationRecord(duration).
intermediate = TRY(move_relative_zoned_date_time(vm, *relative_to.zoned_relative_to, unbalance_result.years, unbalance_result.months, unbalance_result.weeks, 0)); // FIXME: b. Let timeZone be zonedRelativeTo.[[TimeZone]].
// FIXME: c. Let calendar be zonedRelativeTo.[[Calendar]].
// FIXME: d. Let relativeEpochNs be zonedRelativeTo.[[EpochNanoseconds]].
// FIXME: e. Let targetEpochNs be ? AddZonedDateTime(relativeEpochNs, timeZone, calendar, internalDuration, constrain).
// FIXME: f. Let total be ? DifferenceZonedDateTimeWithTotal(relativeEpochNs, targetEpochNs, timeZone, calendar, unit).
}
// 12. Else if plainRelativeTo is not undefined, then
else if (plain_relative_to) {
// FIXME: a. Let internalDuration be ToInternalDurationRecordWith24HourDays(duration).
// FIXME: b. Let targetTime be AddTime(MidnightTimeRecord(), internalDuration.[[Time]]).
// FIXME: c. Let calendar be plainRelativeTo.[[Calendar]].
// FIXME: d. Let dateDuration be ! AdjustDateDurationRecord(internalDuration.[[Date]], targetTime.[[Days]]).
// FIXME: e. Let targetDate be ? CalendarDateAdd(calendar, plainRelativeTo.[[ISODate]], dateDuration, constrain).
// FIXME: f. Let isoDateTime be CombineISODateAndTimeRecord(plainRelativeTo.[[ISODate]], MidnightTimeRecord()).
// FIXME: g. Let targetDateTime be CombineISODateAndTimeRecord(targetDate, targetTime).
// FIXME: h. Let total be ? DifferencePlainDateTimeWithTotal(isoDateTime, targetDateTime, calendar, unit).
}
// 13. Else,
else {
// a. Let largestUnit be DefaultTemporalLargestUnit(duration).
auto largest_unit = default_temporal_largest_unit(duration);
// b. If IsCalendarUnit(largestUnit) is true, or IsCalendarUnit(unit) is true, throw a RangeError exception.
if (is_calendar_unit(largest_unit))
return vm.throw_completion<RangeError>(ErrorType::TemporalInvalidLargestUnit, temporal_unit_to_string(largest_unit));
if (is_calendar_unit(unit))
return vm.throw_completion<RangeError>(ErrorType::TemporalInvalidLargestUnit, temporal_unit_to_string(unit));
// c. Let internalDuration be ToInternalDurationRecordWith24HourDays(duration).
auto internal_duration = to_internal_duration_record_with_24_hour_days(vm, duration);
// d. Let total be TotalTimeDuration(internalDuration.[[Time]], unit).
total = total_time_duration(internal_duration.time, unit);
} }
// 11. Let balanceResult be ? BalanceDuration(unbalanceResult.[[Days]], duration.[[Hours]], duration.[[Minutes]], duration.[[Seconds]], duration.[[Milliseconds]], duration.[[Microseconds]], duration.[[Nanoseconds]], unit, intermediate). // 14. Return 𝔽(total).
auto balance_result = TRY(balance_duration(vm, unbalance_result.days, duration->hours(), duration->minutes(), duration->seconds(), duration->milliseconds(), duration->microseconds(), Crypto::SignedBigInteger { duration->nanoseconds() }, *unit, intermediate)); return total;
// 12. Let roundRecord be ? RoundDuration(unbalanceResult.[[Years]], unbalanceResult.[[Months]], unbalanceResult.[[Weeks]], balanceResult.[[Days]], balanceResult.[[Hours]], balanceResult.[[Minutes]], balanceResult.[[Seconds]], balanceResult.[[Milliseconds]], balanceResult.[[Microseconds]], balanceResult.[[Nanoseconds]], 1, unit, "trunc", relativeTo).
auto calendar_record = TRY(create_calendar_methods_record_from_relative_to(vm, relative_to.plain_relative_to, relative_to.zoned_relative_to, { { CalendarMethod::DateAdd, CalendarMethod::DateUntil } }));
auto round_record = TRY(round_duration(vm, unbalance_result.years, unbalance_result.months, unbalance_result.weeks, balance_result.days, balance_result.hours, balance_result.minutes, balance_result.seconds, balance_result.milliseconds, balance_result.microseconds, balance_result.nanoseconds, 1, *unit, "trunc"sv, relative_to_value.is_object() ? &relative_to_value.as_object() : nullptr, calendar_record));
// 13. Return 𝔽(roundRecord.[[Total]]).
return Value(round_record.total);
} }
// 7.3.22 Temporal.Duration.prototype.toString ( [ options ] ), https://tc39.es/proposal-temporal/#sec-temporal.duration.prototype.tostring // 7.3.22 Temporal.Duration.prototype.toString ( [ options ] ), https://tc39.es/proposal-temporal/#sec-temporal.duration.prototype.tostring
@ -521,24 +509,55 @@ JS_DEFINE_NATIVE_FUNCTION(DurationPrototype::to_string)
// 2. Perform ? RequireInternalSlot(duration, [[InitializedTemporalDuration]]). // 2. Perform ? RequireInternalSlot(duration, [[InitializedTemporalDuration]]).
auto duration = TRY(typed_this_object(vm)); auto duration = TRY(typed_this_object(vm));
// 3. Set options to ? GetOptionsObject(options). // 3. Let resolvedOptions be ? GetOptionsObject(options).
auto const* options = TRY(get_options_object(vm, vm.argument(0))); auto resolved_options = TRY(get_options_object(vm, vm.argument(0)));
// 4. Let precision be ? ToSecondsStringPrecisionRecord(options). // 4. NOTE: The following steps read options and perform independent validation in alphabetical order
auto precision = TRY(to_seconds_string_precision_record(vm, *options)); // (GetTemporalFractionalSecondDigitsOption reads "fractionalSecondDigits" and GetRoundingModeOption reads
// "roundingMode").
// 5. If precision.[[Unit]] is "minute", throw a RangeError exception. // 5. Let digits be ? GetTemporalFractionalSecondDigitsOption(resolvedOptions).
if (precision.unit == "minute"sv) auto digits = TRY(get_temporal_fractional_second_digits_option(vm, resolved_options));
return vm.throw_completion<RangeError>(ErrorType::OptionIsNotValidValue, "minute"sv, "smallestUnit"sv);
// 6. Let roundingMode be ? ToTemporalRoundingMode(options, "trunc"). // 6. Let roundingMode be ? GetRoundingModeOption(resolvedOptions, TRUNC).
auto rounding_mode = TRY(to_temporal_rounding_mode(vm, *options, "trunc"sv)); auto rounding_mode = TRY(get_rounding_mode_option(vm, resolved_options, RoundingMode::Trunc));
// 7. Let result be (? RoundDuration(duration.[[Years]], duration.[[Months]], duration.[[Weeks]], duration.[[Days]], duration.[[Hours]], duration.[[Minutes]], duration.[[Seconds]], duration.[[Milliseconds]], duration.[[Microseconds]], duration.[[Nanoseconds]], precision.[[Increment]], precision.[[Unit]], roundingMode)).[[DurationRecord]]. // 7. Let smallestUnit be ? GetTemporalUnitValuedOption(resolvedOptions, "smallestUnit", TIME, UNSET).
auto result = TRY(round_duration(vm, duration->years(), duration->months(), duration->weeks(), duration->days(), duration->hours(), duration->minutes(), duration->seconds(), duration->milliseconds(), duration->microseconds(), duration->nanoseconds(), precision.increment, precision.unit, rounding_mode)).duration_record; auto smallest_unit = TRY(get_temporal_unit_valued_option(vm, resolved_options, vm.names.smallestUnit, UnitGroup::Time, Unset {}));
// 8. Return ! TemporalDurationToString(result.[[Years]], result.[[Months]], result.[[Weeks]], result.[[Days]], result.[[Hours]], result.[[Minutes]], result.[[Seconds]], result.[[Milliseconds]], result.[[Microseconds]], result.[[Nanoseconds]], precision.[[Precision]]). // 8. If smallestUnit is HOUR or MINUTE, throw a RangeError exception.
return PrimitiveString::create(vm, MUST(temporal_duration_to_string(vm, result.years, result.months, result.weeks, result.days, result.hours, result.minutes, result.seconds, result.milliseconds, result.microseconds, result.nanoseconds, precision.precision))); if (auto const* unit = smallest_unit.get_pointer<Unit>(); unit && (*unit == Unit::Hour || *unit == Unit::Minute))
return vm.throw_completion<RangeError>(ErrorType::OptionIsNotValidValue, temporal_unit_to_string(*unit), vm.names.smallestUnit);
// 9. Let precision be ToSecondsStringPrecisionRecord(smallestUnit, digits).
auto precision = to_seconds_string_precision_record(smallest_unit, digits);
// 10. If precision.[[Unit]] is NANOSECOND and precision.[[Increment]] = 1, then
if (precision.unit == Unit::Nanosecond && precision.increment == 1) {
// a. Return TemporalDurationToString(duration, precision.[[Precision]]).
return PrimitiveString::create(vm, temporal_duration_to_string(duration, precision.precision.downcast<Auto, u8>()));
}
// 11. Let largestUnit be DefaultTemporalLargestUnit(duration).
auto largest_unit = default_temporal_largest_unit(duration);
// 12. Let internalDuration be ToInternalDurationRecord(duration).
auto internal_duration = to_internal_duration_record(vm, duration);
// 13. Let timeDuration be ? RoundTimeDuration(internalDuration.[[Time]], precision.[[Increment]], precision.[[Unit]], roundingMode).
auto time_duration = TRY(round_time_duration(vm, internal_duration.time, precision.increment, precision.unit, rounding_mode));
// 14. Set internalDuration to ! CombineDateAndTimeDuration(internalDuration.[[Date]], timeDuration).
internal_duration = MUST(combine_date_and_time_duration(vm, internal_duration.date, move(time_duration)));
// 15. Let roundedLargestUnit be LargerOfTwoTemporalUnits(largestUnit, SECOND).
auto rounded_largest_unit = larger_of_two_temporal_units(largest_unit, Unit::Second);
// 16. Let roundedDuration be ? TemporalDurationFromInternal(internalDuration, roundedLargestUnit).
auto rounded_duration = TRY(temporal_duration_from_internal(vm, internal_duration, rounded_largest_unit));
// 17. Return TemporalDurationToString(roundedDuration, precision.[[Precision]]).
return PrimitiveString::create(vm, temporal_duration_to_string(rounded_duration, precision.precision.downcast<Auto, u8>()));
} }
// 7.3.23 Temporal.Duration.prototype.toJSON ( ), https://tc39.es/proposal-temporal/#sec-temporal.duration.prototype.tojson // 7.3.23 Temporal.Duration.prototype.toJSON ( ), https://tc39.es/proposal-temporal/#sec-temporal.duration.prototype.tojson
@ -548,8 +567,8 @@ JS_DEFINE_NATIVE_FUNCTION(DurationPrototype::to_json)
// 2. Perform ? RequireInternalSlot(duration, [[InitializedTemporalDuration]]). // 2. Perform ? RequireInternalSlot(duration, [[InitializedTemporalDuration]]).
auto duration = TRY(typed_this_object(vm)); auto duration = TRY(typed_this_object(vm));
// 3. Return ! TemporalDurationToString(duration.[[Years]], duration.[[Months]], duration.[[Weeks]], duration.[[Days]], duration.[[Hours]], duration.[[Minutes]], duration.[[Seconds]], duration.[[Milliseconds]], duration.[[Microseconds]], duration.[[Nanoseconds]], "auto"). // 3. Return TemporalDurationToString(duration, AUTO).
return PrimitiveString::create(vm, MUST(temporal_duration_to_string(vm, duration->years(), duration->months(), duration->weeks(), duration->days(), duration->hours(), duration->minutes(), duration->seconds(), duration->milliseconds(), duration->microseconds(), duration->nanoseconds(), "auto"sv))); return PrimitiveString::create(vm, temporal_duration_to_string(duration, Auto {}));
} }
// 7.3.24 Temporal.Duration.prototype.toLocaleString ( [ locales [ , options ] ] ), https://tc39.es/proposal-temporal/#sec-temporal.duration.prototype.tolocalestring // 7.3.24 Temporal.Duration.prototype.toLocaleString ( [ locales [ , options ] ] ), https://tc39.es/proposal-temporal/#sec-temporal.duration.prototype.tolocalestring
@ -560,8 +579,8 @@ JS_DEFINE_NATIVE_FUNCTION(DurationPrototype::to_locale_string)
// 2. Perform ? RequireInternalSlot(duration, [[InitializedTemporalDuration]]). // 2. Perform ? RequireInternalSlot(duration, [[InitializedTemporalDuration]]).
auto duration = TRY(typed_this_object(vm)); auto duration = TRY(typed_this_object(vm));
// 3. Return ! TemporalDurationToString(duration.[[Years]], duration.[[Months]], duration.[[Weeks]], duration.[[Days]], duration.[[Hours]], duration.[[Minutes]], duration.[[Seconds]], duration.[[Milliseconds]], duration.[[Microseconds]], duration.[[Nanoseconds]], "auto"). // 3. Return TemporalDurationToString(duration, AUTO).
return PrimitiveString::create(vm, MUST(temporal_duration_to_string(vm, duration->years(), duration->months(), duration->weeks(), duration->days(), duration->hours(), duration->minutes(), duration->seconds(), duration->milliseconds(), duration->microseconds(), duration->nanoseconds(), "auto"sv))); return PrimitiveString::create(vm, temporal_duration_to_string(duration, Auto {}));
} }
// 7.3.25 Temporal.Duration.prototype.valueOf ( ), https://tc39.es/proposal-temporal/#sec-temporal.duration.prototype.valueof // 7.3.25 Temporal.Duration.prototype.valueOf ( ), https://tc39.es/proposal-temporal/#sec-temporal.duration.prototype.valueof

View file

@ -1,5 +1,6 @@
/* /*
* Copyright (c) 2021, Linus Groh <linusg@serenityos.org> * Copyright (c) 2021, Linus Groh <linusg@serenityos.org>
* Copyright (c) 2024, Tim Flynn <trflynn89@ladybird.org>
* *
* SPDX-License-Identifier: BSD-2-Clause * SPDX-License-Identifier: BSD-2-Clause
*/ */
@ -22,16 +23,11 @@ public:
private: private:
explicit DurationPrototype(Realm&); explicit DurationPrototype(Realm&);
JS_DECLARE_NATIVE_FUNCTION(years_getter); #define __JS_ENUMERATE(unit) \
JS_DECLARE_NATIVE_FUNCTION(months_getter); JS_DECLARE_NATIVE_FUNCTION(unit##_getter);
JS_DECLARE_NATIVE_FUNCTION(weeks_getter); JS_ENUMERATE_DURATION_UNITS
JS_DECLARE_NATIVE_FUNCTION(days_getter); #undef __JS_ENUMERATE
JS_DECLARE_NATIVE_FUNCTION(hours_getter);
JS_DECLARE_NATIVE_FUNCTION(minutes_getter);
JS_DECLARE_NATIVE_FUNCTION(seconds_getter);
JS_DECLARE_NATIVE_FUNCTION(milliseconds_getter);
JS_DECLARE_NATIVE_FUNCTION(microseconds_getter);
JS_DECLARE_NATIVE_FUNCTION(nanoseconds_getter);
JS_DECLARE_NATIVE_FUNCTION(sign_getter); JS_DECLARE_NATIVE_FUNCTION(sign_getter);
JS_DECLARE_NATIVE_FUNCTION(blank_getter); JS_DECLARE_NATIVE_FUNCTION(blank_getter);
JS_DECLARE_NATIVE_FUNCTION(with); JS_DECLARE_NATIVE_FUNCTION(with);

File diff suppressed because it is too large Load diff

View file

@ -1,225 +1,35 @@
/* /*
* Copyright (c) 2021-2022, Linus Groh <linusg@serenityos.org> * Copyright (c) 2021-2022, Linus Groh <linusg@serenityos.org>
* Copyright (c) 2024, Tim Flynn <trflynn89@ladybird.org>
* *
* SPDX-License-Identifier: BSD-2-Clause * SPDX-License-Identifier: BSD-2-Clause
*/ */
#pragma once #pragma once
#include <AK/GenericLexer.h>
#include <AK/Optional.h> #include <AK/Optional.h>
#include <AK/StringView.h> #include <AK/StringView.h>
#include <AK/Vector.h>
namespace JS::Temporal { namespace JS::Temporal {
struct Annotation {
bool critical { false };
StringView key;
StringView value;
};
struct ParseResult { struct ParseResult {
Optional<StringView> sign; Optional<char> sign;
Optional<StringView> date_year;
Optional<StringView> date_month;
Optional<StringView> date_day;
Optional<StringView> time_hour;
Optional<StringView> time_minute;
Optional<StringView> time_second;
Optional<StringView> time_fraction;
Optional<StringView> utc_designator;
Optional<StringView> time_zone_annotation;
Optional<StringView> time_zone_numeric_utc_offset;
Optional<StringView> time_zone_utc_offset_sign;
Optional<StringView> time_zone_utc_offset_hour;
Optional<StringView> time_zone_utc_offset_minute;
Optional<StringView> time_zone_utc_offset_second;
Optional<StringView> time_zone_utc_offset_fraction;
Optional<StringView> time_zone_identifier;
Optional<StringView> duration_years; Optional<StringView> duration_years;
Optional<StringView> duration_months; Optional<StringView> duration_months;
Optional<StringView> duration_weeks; Optional<StringView> duration_weeks;
Optional<StringView> duration_days; Optional<StringView> duration_days;
Optional<StringView> duration_whole_hours; Optional<StringView> duration_hours;
Optional<StringView> duration_hours_fraction; Optional<StringView> duration_hours_fraction;
Optional<StringView> duration_whole_minutes; Optional<StringView> duration_minutes;
Optional<StringView> duration_minutes_fraction; Optional<StringView> duration_minutes_fraction;
Optional<StringView> duration_whole_seconds; Optional<StringView> duration_seconds;
Optional<StringView> duration_seconds_fraction; Optional<StringView> duration_seconds_fraction;
Optional<StringView> annotation_key;
Optional<StringView> annotation_value;
Vector<Annotation> annotations;
}; };
enum class Production { enum class Production {
TemporalInstantString,
TemporalDateTimeString,
TemporalDurationString, TemporalDurationString,
TemporalMonthDayString,
TemporalTimeString,
TemporalYearMonthString,
TemporalZonedDateTimeString,
TimeZoneIdentifier,
TimeZoneNumericUTCOffset,
AnnotationValue,
DateMonth,
}; };
Optional<ParseResult> parse_iso8601(Production, StringView); Optional<ParseResult> parse_iso8601(Production, StringView);
namespace Detail {
// 13.33 ISO 8601 grammar, https://tc39.es/proposal-temporal/#sec-temporal-iso8601grammar
class ISO8601Parser {
public:
explicit ISO8601Parser(StringView input)
: m_input(input)
, m_state({
.lexer = GenericLexer { input },
.parse_result = {},
})
{
}
[[nodiscard]] GenericLexer const& lexer() const { return m_state.lexer; }
[[nodiscard]] ParseResult const& parse_result() const { return m_state.parse_result; }
[[nodiscard]] bool parse_decimal_digits();
[[nodiscard]] bool parse_decimal_digit();
[[nodiscard]] bool parse_non_zero_digit();
[[nodiscard]] bool parse_ascii_sign();
[[nodiscard]] bool parse_sign();
[[nodiscard]] bool parse_unpadded_hour();
[[nodiscard]] bool parse_hour();
[[nodiscard]] bool parse_minute_second();
[[nodiscard]] bool parse_decimal_separator();
[[nodiscard]] bool parse_days_designator();
[[nodiscard]] bool parse_hours_designator();
[[nodiscard]] bool parse_minutes_designator();
[[nodiscard]] bool parse_months_designator();
[[nodiscard]] bool parse_duration_designator();
[[nodiscard]] bool parse_seconds_designator();
[[nodiscard]] bool parse_date_time_separator();
[[nodiscard]] bool parse_time_designator();
[[nodiscard]] bool parse_weeks_designator();
[[nodiscard]] bool parse_years_designator();
[[nodiscard]] bool parse_utc_designator();
[[nodiscard]] bool parse_annotation_critical_flag();
[[nodiscard]] bool parse_date_year();
[[nodiscard]] bool parse_date_month();
[[nodiscard]] bool parse_date_month_with_thirty_days();
[[nodiscard]] bool parse_date_day();
[[nodiscard]] bool parse_date_spec_year_month();
[[nodiscard]] bool parse_date_spec_month_day();
[[nodiscard]] bool parse_valid_month_day();
[[nodiscard]] bool parse_date();
[[nodiscard]] bool parse_time_hour();
[[nodiscard]] bool parse_time_minute();
[[nodiscard]] bool parse_time_second();
[[nodiscard]] bool parse_fractional_part();
[[nodiscard]] bool parse_fraction();
[[nodiscard]] bool parse_time_fraction();
[[nodiscard]] bool parse_time_zone_utc_offset_sign();
[[nodiscard]] bool parse_time_zone_utc_offset_hour();
[[nodiscard]] bool parse_time_zone_utc_offset_minute();
[[nodiscard]] bool parse_time_zone_utc_offset_second();
[[nodiscard]] bool parse_time_zone_utc_offset_fractional_part();
[[nodiscard]] bool parse_time_zone_utc_offset_fraction();
[[nodiscard]] bool parse_time_zone_numeric_utc_offset();
[[nodiscard]] bool parse_time_zone_utc_offset();
[[nodiscard]] bool parse_time_zone_utc_offset_name();
[[nodiscard]] bool parse_tz_leading_char();
[[nodiscard]] bool parse_tz_char();
[[nodiscard]] bool parse_time_zone_iana_component();
[[nodiscard]] bool parse_time_zone_iana_name_tail();
[[nodiscard]] bool parse_time_zone_iana_legacy_name();
[[nodiscard]] bool parse_time_zone_iana_name();
[[nodiscard]] bool parse_time_zone_identifier();
[[nodiscard]] bool parse_time_zone_annotation();
[[nodiscard]] bool parse_a_key_leading_char();
[[nodiscard]] bool parse_a_key_char();
[[nodiscard]] bool parse_a_val_char();
[[nodiscard]] bool parse_annotation_key_tail();
[[nodiscard]] bool parse_annotation_key();
[[nodiscard]] bool parse_annotation_value_component();
[[nodiscard]] bool parse_annotation_value_tail();
[[nodiscard]] bool parse_annotation_value();
[[nodiscard]] bool parse_annotation();
[[nodiscard]] bool parse_annotations();
[[nodiscard]] bool parse_time_spec();
[[nodiscard]] bool parse_time_spec_with_optional_offset_not_ambiguous();
[[nodiscard]] bool parse_date_time();
[[nodiscard]] bool parse_annotated_time();
[[nodiscard]] bool parse_annotated_date_time();
[[nodiscard]] bool parse_annotated_date_time_time_required();
[[nodiscard]] bool parse_annotated_year_month();
[[nodiscard]] bool parse_annotated_month_day();
[[nodiscard]] bool parse_duration_whole_seconds();
[[nodiscard]] bool parse_duration_seconds_fraction();
[[nodiscard]] bool parse_duration_seconds_part();
[[nodiscard]] bool parse_duration_whole_minutes();
[[nodiscard]] bool parse_duration_minutes_fraction();
[[nodiscard]] bool parse_duration_minutes_part();
[[nodiscard]] bool parse_duration_whole_hours();
[[nodiscard]] bool parse_duration_hours_fraction();
[[nodiscard]] bool parse_duration_hours_part();
[[nodiscard]] bool parse_duration_time();
[[nodiscard]] bool parse_duration_days();
[[nodiscard]] bool parse_duration_days_part();
[[nodiscard]] bool parse_duration_weeks();
[[nodiscard]] bool parse_duration_weeks_part();
[[nodiscard]] bool parse_duration_months();
[[nodiscard]] bool parse_duration_months_part();
[[nodiscard]] bool parse_duration_years();
[[nodiscard]] bool parse_duration_years_part();
[[nodiscard]] bool parse_duration_date();
[[nodiscard]] bool parse_duration();
[[nodiscard]] bool parse_temporal_instant_string();
[[nodiscard]] bool parse_temporal_date_time_string();
[[nodiscard]] bool parse_temporal_duration_string();
[[nodiscard]] bool parse_temporal_month_day_string();
[[nodiscard]] bool parse_temporal_time_string();
[[nodiscard]] bool parse_temporal_year_month_string();
[[nodiscard]] bool parse_temporal_zoned_date_time_string();
private:
struct State {
GenericLexer lexer;
ParseResult parse_result;
};
struct StateTransaction {
explicit StateTransaction(ISO8601Parser& parser)
: m_parser(parser)
, m_saved_state(parser.m_state)
, m_start_index(parser.m_state.lexer.tell())
{
}
~StateTransaction()
{
if (!m_commit)
m_parser.m_state = move(m_saved_state);
}
void commit() { m_commit = true; }
StringView parsed_string_view() const
{
return m_parser.m_input.substring_view(m_start_index, m_parser.m_state.lexer.tell() - m_start_index);
}
private:
ISO8601Parser& m_parser;
State m_saved_state;
size_t m_start_index { 0 };
bool m_commit { false };
};
StringView m_input;
State m_state;
};
}
} }

View file

@ -2,369 +2,36 @@
* Copyright (c) 2021-2023, Linus Groh <linusg@serenityos.org> * Copyright (c) 2021-2023, Linus Groh <linusg@serenityos.org>
* Copyright (c) 2021, Idan Horowitz <idan.horowitz@serenityos.org> * Copyright (c) 2021, Idan Horowitz <idan.horowitz@serenityos.org>
* Copyright (c) 2023, Shannon Booth <shannon@serenityos.org> * Copyright (c) 2023, Shannon Booth <shannon@serenityos.org>
* Copyright (c) 2024, Tim Flynn <trflynn89@ladybird.org>
* *
* SPDX-License-Identifier: BSD-2-Clause * SPDX-License-Identifier: BSD-2-Clause
*/ */
#include <AK/TypeCasts.h>
#include <AK/Variant.h>
#include <LibCrypto/BigInt/SignedBigInteger.h>
#include <LibJS/Runtime/AbstractOperations.h>
#include <LibJS/Runtime/Completion.h>
#include <LibJS/Runtime/Date.h>
#include <LibJS/Runtime/GlobalObject.h>
#include <LibJS/Runtime/Temporal/AbstractOperations.h>
#include <LibJS/Runtime/Temporal/Calendar.h>
#include <LibJS/Runtime/Temporal/Instant.h> #include <LibJS/Runtime/Temporal/Instant.h>
#include <LibJS/Runtime/Temporal/InstantConstructor.h>
#include <LibJS/Runtime/Temporal/PlainDateTime.h>
#include <LibJS/Runtime/Temporal/TimeZone.h>
#include <LibJS/Runtime/Temporal/ZonedDateTime.h>
namespace JS::Temporal { namespace JS::Temporal {
GC_DEFINE_ALLOCATOR(Instant); // nsMaxInstant = 10**8 × nsPerDay = 8.64 × 10**21
Crypto::SignedBigInteger const NANOSECONDS_MAX_INSTANT = "8640000000000000000000"_sbigint;
// 8 Temporal.Instant Objects, https://tc39.es/proposal-temporal/#sec-temporal-instant-objects
Instant::Instant(BigInt const& nanoseconds, Object& prototype) // nsMinInstant = -nsMaxInstant = -8.64 × 10**21
: Object(ConstructWithPrototypeTag::Tag, prototype) Crypto::SignedBigInteger const NANOSECONDS_MIN_INSTANT = "-8640000000000000000000"_sbigint;
, m_nanoseconds(nanoseconds)
{ // nsPerDay = 10**6 × (msPerDay) = 8.64 × 10**13
} Crypto::UnsignedBigInteger const NANOSECONDS_PER_DAY = 86400000000000_bigint;
void Instant::visit_edges(Cell::Visitor& visitor) // Non-standard:
{ Crypto::UnsignedBigInteger const NANOSECONDS_PER_HOUR = 3600000000000_bigint;
Base::visit_edges(visitor); Crypto::UnsignedBigInteger const NANOSECONDS_PER_MINUTE = 60000000000_bigint;
Crypto::UnsignedBigInteger const NANOSECONDS_PER_SECOND = 1000000000_bigint;
visitor.visit(m_nanoseconds); Crypto::UnsignedBigInteger const NANOSECONDS_PER_MILLISECOND = 1000000_bigint;
} Crypto::UnsignedBigInteger const NANOSECONDS_PER_MICROSECOND = 1000_bigint;
Crypto::UnsignedBigInteger const NANOSECONDS_PER_NANOSECOND = 1_bigint;
// 8.5.1 IsValidEpochNanoseconds ( epochNanoseconds ), https://tc39.es/proposal-temporal/#sec-temporal-isvalidepochnanoseconds
bool is_valid_epoch_nanoseconds(BigInt const& epoch_nanoseconds) Crypto::UnsignedBigInteger const MICROSECONDS_PER_MILLISECOND = 1000_bigint;
{ Crypto::UnsignedBigInteger const MILLISECONDS_PER_SECOND = 1000_bigint;
return is_valid_epoch_nanoseconds(epoch_nanoseconds.big_integer()); Crypto::UnsignedBigInteger const SECONDS_PER_MINUTE = 60_bigint;
} Crypto::UnsignedBigInteger const MINUTES_PER_HOUR = 60_bigint;
Crypto::UnsignedBigInteger const HOURS_PER_DAY = 24_bigint;
// 8.5.1 IsValidEpochNanoseconds ( epochNanoseconds ), https://tc39.es/proposal-temporal/#sec-temporal-isvalidepochnanoseconds
bool is_valid_epoch_nanoseconds(Crypto::SignedBigInteger const& epoch_nanoseconds)
{
// 1. Assert: Type(epochNanoseconds) is BigInt.
// 2. If (epochNanoseconds) < nsMinInstant or (epochNanoseconds) > nsMaxInstant, then
if (epoch_nanoseconds < ns_min_instant || epoch_nanoseconds > ns_max_instant) {
// a. Return false.
return false;
}
// 3. Return true.
return true;
}
// 8.5.2 CreateTemporalInstant ( epochNanoseconds [ , newTarget ] ), https://tc39.es/proposal-temporal/#sec-temporal-createtemporalinstant
ThrowCompletionOr<Instant*> create_temporal_instant(VM& vm, BigInt const& epoch_nanoseconds, FunctionObject const* new_target)
{
auto& realm = *vm.current_realm();
// 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 newTarget to %Temporal.Instant%.
if (!new_target)
new_target = realm.intrinsics().temporal_instant_constructor();
// 4. Let object be ? OrdinaryCreateFromConstructor(newTarget, "%Temporal.Instant.prototype%", « [[InitializedTemporalInstant]], [[Nanoseconds]] »).
// 5. Set object.[[Nanoseconds]] to epochNanoseconds.
auto object = TRY(ordinary_create_from_constructor<Instant>(vm, *new_target, &Intrinsics::temporal_instant_prototype, epoch_nanoseconds));
// 6. Return object.
return object.ptr();
}
// 8.5.3 ToTemporalInstant ( item ), https://tc39.es/proposal-temporal/#sec-temporal-totemporalinstant
ThrowCompletionOr<Instant*> to_temporal_instant(VM& vm, Value item)
{
// 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());
}
// b. If item has an [[InitializedTemporalZonedDateTime]] internal slot, then
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(vm, zoned_date_time.nanoseconds());
}
}
// 2. Let string be ? ToString(item).
auto string = TRY(item.to_string(vm));
// 3. Let epochNanoseconds be ? ParseTemporalInstant(string).
auto* epoch_nanoseconds = TRY(parse_temporal_instant(vm, string));
// 4. Return ! CreateTemporalInstant((epochNanoseconds)).
return create_temporal_instant(vm, *epoch_nanoseconds);
}
// 8.5.4 ParseTemporalInstant ( isoString ), https://tc39.es/proposal-temporal/#sec-temporal-parsetemporalinstant
ThrowCompletionOr<BigInt*> parse_temporal_instant(VM& vm, StringView iso_string)
{
// 1. Assert: Type(isoString) is String.
// 2. Let result be ? ParseTemporalInstantString(isoString).
auto result = TRY(parse_temporal_instant_string(vm, iso_string));
// 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 GetUTCEpochNanoseconds(result.[[Year]], result.[[Month]], result.[[Day]], result.[[Hour]], result.[[Minute]], result.[[Second]], result.[[Millisecond]], result.[[Microsecond]], result.[[Nanosecond]]).
auto utc = get_utc_epoch_nanoseconds(result.year, result.month, result.day, result.hour, result.minute, result.second, result.millisecond, result.microsecond, result.nanosecond);
// 6. If IsTimeZoneOffsetString(offsetString) is false, throw a RangeError exception.
if (!is_time_zone_offset_string(*offset_string))
return vm.throw_completion<RangeError>(ErrorType::TemporalInvalidTimeZoneName, *offset_string);
// 7. Let offsetNanoseconds be ParseTimeZoneOffsetString(offsetString).
auto offset_nanoseconds = parse_time_zone_offset_string(*offset_string);
// 7. Let result be utc - (offsetNanoseconds).
auto result_ns = utc.minus(Crypto::SignedBigInteger { offset_nanoseconds });
// 8. If ! IsValidEpochNanoseconds(result) is false, then
if (!is_valid_epoch_nanoseconds(result_ns)) {
// a. Throw a RangeError exception.
return vm.throw_completion<RangeError>(ErrorType::TemporalInvalidEpochNanoseconds);
}
// 9. Return result.
return BigInt::create(vm, move(result_ns)).ptr();
}
// 8.5.5 CompareEpochNanoseconds ( epochNanosecondsOne, epochNanosecondsTwo ), https://tc39.es/proposal-temporal/#sec-temporal-compareepochnanoseconds
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;
}
// 8.5.6 AddInstant ( epochNanoseconds, hours, minutes, seconds, milliseconds, microseconds, nanoseconds ), https://tc39.es/proposal-temporal/#sec-temporal-addinstant
ThrowCompletionOr<BigInt*> add_instant(VM& vm, BigInt const& epoch_nanoseconds, double hours, double minutes, double seconds, double milliseconds, double microseconds, double nanoseconds)
{
VERIFY(hours == trunc(hours) && minutes == trunc(minutes) && seconds == trunc(seconds) && milliseconds == trunc(milliseconds) && microseconds == trunc(microseconds) && nanoseconds == trunc(nanoseconds));
// 1. Let result be epochNanoseconds + (nanoseconds) + (microseconds) × 1000 + (milliseconds) × 10^6 + (seconds) × 10^9 + (minutes) × 60 × 10^9 + (hours) × 3600 × 10^9.
auto result = BigInt::create(vm,
epoch_nanoseconds.big_integer()
.plus(Crypto::SignedBigInteger { nanoseconds })
.plus(Crypto::SignedBigInteger { microseconds }.multiplied_by(Crypto::SignedBigInteger { 1'000 }))
.plus(Crypto::SignedBigInteger { milliseconds }.multiplied_by(Crypto::SignedBigInteger { 1'000'000 }))
.plus(Crypto::SignedBigInteger { seconds }.multiplied_by(Crypto::SignedBigInteger { 1'000'000'000 }))
.plus(Crypto::SignedBigInteger { minutes }.multiplied_by(Crypto::SignedBigInteger { 60 }).multiplied_by(Crypto::SignedBigInteger { 1'000'000'000 }))
.plus(Crypto::SignedBigInteger { hours }.multiplied_by(Crypto::SignedBigInteger { 3600 }).multiplied_by(Crypto::SignedBigInteger { 1'000'000'000 })));
// 2. If ! IsValidEpochNanoseconds(result) is false, throw a RangeError exception.
if (!is_valid_epoch_nanoseconds(*result))
return vm.throw_completion<RangeError>(ErrorType::TemporalInvalidEpochNanoseconds);
// 3. Return result.
return result.ptr();
}
// 8.5.6 DifferenceInstant ( ns1, ns2, roundingIncrement, smallestUnit, largestUnit, roundingMode ), https://tc39.es/proposal-temporal/#sec-temporal-differenceinstant
TimeDurationRecord difference_instant(VM& vm, BigInt const& nanoseconds1, BigInt const& nanoseconds2, u64 rounding_increment, StringView smallest_unit, StringView largest_unit, StringView rounding_mode)
{
static Crypto::UnsignedBigInteger const BIGINT_ONE_THOUSAND { 1'000 };
// 1. Let difference be (ns2) - (ns1).
auto difference = nanoseconds2.big_integer().minus(nanoseconds1.big_integer());
// 2. Let nanoseconds be remainder(difference, 1000).
auto nanoseconds = remainder(difference, BIGINT_ONE_THOUSAND);
// 3. Let microseconds be remainder(truncate(difference / 1000), 1000).
auto microseconds = remainder(difference.divided_by(BIGINT_ONE_THOUSAND).quotient, BIGINT_ONE_THOUSAND);
// 4. Let milliseconds be remainder(truncate(difference / 10^6), 1000).
auto milliseconds = remainder(difference.divided_by(Crypto::UnsignedBigInteger { 1'000'000 }).quotient, BIGINT_ONE_THOUSAND);
// 5. Let seconds be truncate(difference / 10^9).
auto seconds = difference.divided_by(Crypto::UnsignedBigInteger { 1'000'000'000 }).quotient;
// 6. If smallestUnit is "nanosecond" and roundingIncrement is 1, then
if (smallest_unit == "nanosecond"sv && rounding_increment == 1) {
// a. Return ! BalanceTimeDuration(0, 0, 0, seconds, milliseconds, microseconds, nanoseconds, largestUnit).
return MUST(balance_time_duration(vm, 0, 0, 0, seconds.to_double(), milliseconds.to_double(), microseconds.to_double(), nanoseconds, largest_unit));
}
// 7. Let roundResult be ! RoundDuration(0, 0, 0, 0, 0, 0, seconds, milliseconds, microseconds, nanoseconds, roundingIncrement, smallestUnit, roundingMode).
auto round_result = MUST(round_duration(vm, 0, 0, 0, 0, 0, 0, seconds.to_double(), milliseconds.to_double(), microseconds.to_double(), nanoseconds.to_double(), rounding_increment, smallest_unit, rounding_mode)).duration_record;
// 8. Assert: roundResult.[[Days]] is 0.
VERIFY(round_result.days == 0);
// 9. Return ! BalanceTimeDuration(0, roundResult.[[Hours]], roundResult.[[Minutes]], roundResult.[[Seconds]], roundResult.[[Milliseconds]], roundResult.[[Microseconds]], roundResult.[[Nanoseconds]], largestUnit).
return MUST(balance_time_duration(vm, 0, round_result.hours, round_result.minutes, round_result.seconds, round_result.milliseconds, round_result.microseconds, Crypto::SignedBigInteger { round_result.nanoseconds }, largest_unit));
}
// 8.5.8 RoundTemporalInstant ( ns, increment, unit, roundingMode ), https://tc39.es/proposal-temporal/#sec-temporal-roundtemporalinstant
BigInt* round_temporal_instant(VM& vm, BigInt const& nanoseconds, u64 increment, StringView unit, StringView rounding_mode)
{
// 1. Assert: Type(ns) is BigInt.
u64 increment_nanoseconds;
// 2. If unit is "hour", then
if (unit == "hour"sv) {
// a. Let incrementNs be increment × 3.6 × 10^12.
increment_nanoseconds = increment * 3600000000000;
}
// 3. Else if unit is "minute", then
else if (unit == "minute"sv) {
// a. Let incrementNs be increment × 6 × 10^10.
increment_nanoseconds = increment * 60000000000;
}
// 4. Else if unit is "second", then
else if (unit == "second"sv) {
// a. Let incrementNs be increment × 10^9.
increment_nanoseconds = increment * 1000000000;
}
// 5. Else if unit is "millisecond", then
else if (unit == "millisecond"sv) {
// a. Let incrementNs be increment × 10^6.
increment_nanoseconds = increment * 1000000;
}
// 6. Else if unit is "microsecond", then
else if (unit == "microsecond"sv) {
// a. Let incrementNs be increment × 10^3.
increment_nanoseconds = increment * 1000;
}
// 7. Else,
else {
// a. Assert: unit is "nanosecond".
VERIFY(unit == "nanosecond"sv);
// b. Let incrementNs be increment.
increment_nanoseconds = increment;
}
// 8. Return RoundNumberToIncrementAsIfPositive((ns), incrementNs, roundingMode).
return BigInt::create(vm, round_number_to_increment_as_if_positive(nanoseconds.big_integer(), increment_nanoseconds, rounding_mode));
}
// 8.5.9 TemporalInstantToString ( instant, timeZone, precision ), https://tc39.es/proposal-temporal/#sec-temporal-temporalinstanttostring
ThrowCompletionOr<String> temporal_instant_to_string(VM& vm, Instant& instant, Value time_zone, Variant<StringView, u8> const& precision)
{
// 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 = MUST_OR_THROW_OOM(create_temporal_time_zone(vm, "UTC"_string));
}
// 5. Let isoCalendar be ! GetISO8601Calendar().
auto* iso_calendar = get_iso8601_calendar(vm);
// 6. Let dateTime be ? BuiltinTimeZoneGetPlainDateTimeFor(outputTimeZone, instant, isoCalendar).
auto* date_time = TRY(builtin_time_zone_get_plain_date_time_for(vm, output_time_zone, instant, *iso_calendar));
// 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 = MUST_OR_THROW_OOM(temporal_date_time_to_string(vm, 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(), nullptr, precision, "never"sv));
String time_zone_string;
// 8. If timeZone is undefined, then
if (time_zone.is_undefined()) {
// a. Let timeZoneString be "Z".
time_zone_string = "Z"_string;
}
// 9. Else,
else {
auto time_zone_record = TRY(create_time_zone_methods_record(vm, GC::Ref<Object> { time_zone.as_object() }, { { TimeZoneMethod::GetOffsetNanosecondsFor } }));
// a. Let offsetNs be ? GetOffsetNanosecondsFor(timeZone, instant).
auto offset_ns = TRY(get_offset_nanoseconds_for(vm, time_zone_record, instant));
// b. Let timeZoneString be ! FormatISOTimeZoneOffsetString(offsetNs).
time_zone_string = MUST_OR_THROW_OOM(format_iso_time_zone_offset_string(vm, offset_ns));
}
// 10. Return the string-concatenation of dateTimeString and timeZoneString.
return TRY_OR_THROW_OOM(vm, String::formatted("{}{}", date_time_string, time_zone_string));
}
// 8.5.9 DifferenceTemporalInstant ( operation, instant, other, options ), https://tc39.es/proposal-temporal/#sec-temporal-differencetemporalinstant
ThrowCompletionOr<GC::Ref<Duration>> difference_temporal_instant(VM& vm, DifferenceOperation operation, Instant const& instant, Value other_value, Value options)
{
// 1. If operation is since, let sign be -1. Otherwise, let sign be 1.
i8 sign = operation == DifferenceOperation::Since ? -1 : 1;
// 2. Set other to ? ToTemporalInstant(other).
auto* other = TRY(to_temporal_instant(vm, other_value));
// 3. Let resolvedOptions be ? SnapshotOwnProperties(? GetOptionsObject(options), null).
auto resolved_options = TRY(TRY(get_options_object(vm, options))->snapshot_own_properties(vm, nullptr));
// 4. Let settings be ? GetDifferenceSettings(operation, resolvedOptions, time, « », "nanosecond", "second").
auto settings = TRY(get_difference_settings(vm, operation, resolved_options, UnitGroup::Time, {}, { "nanosecond"sv }, "second"sv));
// 5. Let result be DifferenceInstant(instant.[[Nanoseconds]], other.[[Nanoseconds]], settings.[[RoundingIncrement]], settings.[[SmallestUnit]], settings.[[LargestUnit]], settings.[[RoundingMode]]).
auto result = difference_instant(vm, instant.nanoseconds(), other->nanoseconds(), settings.rounding_increment, settings.smallest_unit, settings.largest_unit, settings.rounding_mode);
// 6. Return ! CreateTemporalDuration(0, 0, 0, 0, sign × result.[[Hours]], sign × result.[[Minutes]], sign × result.[[Seconds]], sign × result.[[Milliseconds]], sign × result.[[Microseconds]], sign × result.[[Nanoseconds]]).
return MUST(create_temporal_duration(vm, 0, 0, 0, 0, sign * result.hours, sign * result.minutes, sign * result.seconds, sign * result.milliseconds, sign * result.microseconds, sign * result.nanoseconds));
}
// 8.5.11 AddDurationToOrSubtractDurationFromInstant ( operation, instant, temporalDurationLike ), https://tc39.es/proposal-temporal/#sec-temporal-adddurationtoorsubtractdurationfrominstant
ThrowCompletionOr<Instant*> add_duration_to_or_subtract_duration_from_instant(VM& vm, ArithmeticOperation operation, Instant const& instant, Value temporal_duration_like)
{
// 1. If operation is subtract, let sign be -1. Otherwise, let sign be 1.
i8 sign = operation == ArithmeticOperation::Subtract ? -1 : 1;
// 2. Let duration be ? ToTemporalDurationRecord(temporalDurationLike).
auto duration = TRY(to_temporal_duration_record(vm, temporal_duration_like));
// 3. If duration.[[Days]] is not 0, throw a RangeError exception.
if (duration.days != 0)
return vm.throw_completion<RangeError>(ErrorType::TemporalInvalidDurationPropertyValueNonZero, "days", duration.days);
// 4. If duration.[[Months]] is not 0, throw a RangeError exception.
if (duration.months != 0)
return vm.throw_completion<RangeError>(ErrorType::TemporalInvalidDurationPropertyValueNonZero, "months", duration.months);
// 5. If duration.[[Weeks]] is not 0, throw a RangeError exception.
if (duration.weeks != 0)
return vm.throw_completion<RangeError>(ErrorType::TemporalInvalidDurationPropertyValueNonZero, "weeks", duration.weeks);
// 6. If duration.[[Years]] is not 0, throw a RangeError exception.
if (duration.years != 0)
return vm.throw_completion<RangeError>(ErrorType::TemporalInvalidDurationPropertyValueNonZero, "years", duration.years);
// 7. Let ns be ? AddInstant(instant.[[Nanoseconds]], sign × duration.[[Hours]], sign × duration.[[Minutes]], sign × duration.[[Seconds]], sign × duration.[[Milliseconds]], sign × duration.[[Microseconds]], sign × duration.[[Nanoseconds]]).
auto* ns = TRY(add_instant(vm, instant.nanoseconds(), sign * duration.hours, sign * duration.minutes, sign * duration.seconds, sign * duration.milliseconds, sign * duration.microseconds, sign * duration.nanoseconds));
// 8. Return ! CreateTemporalInstant(ns).
return MUST(create_temporal_instant(vm, *ns));
}
} }

View file

@ -1,58 +1,39 @@
/* /*
* Copyright (c) 2021-2023, Linus Groh <linusg@serenityos.org> * Copyright (c) 2021-2023, Linus Groh <linusg@serenityos.org>
* Copyright (c) 2021, Idan Horowitz <idan.horowitz@serenityos.org> * Copyright (c) 2021, Idan Horowitz <idan.horowitz@serenityos.org>
* Copyright (c) 2024, Tim Flynn <trflynn89@ladybird.org>
* *
* SPDX-License-Identifier: BSD-2-Clause * SPDX-License-Identifier: BSD-2-Clause
*/ */
#pragma once #pragma once
#include <AK/Optional.h> #include <LibCrypto/BigInt/SignedBigInteger.h>
#include <AK/Variant.h> #include <LibCrypto/BigInt/UnsignedBigInteger.h>
#include <LibJS/Runtime/BigInt.h>
#include <LibJS/Runtime/Completion.h>
#include <LibJS/Runtime/Object.h>
#include <LibJS/Runtime/Temporal/AbstractOperations.h>
namespace JS::Temporal { namespace JS::Temporal {
class Instant final : public Object {
JS_OBJECT(Instant, Object);
GC_DECLARE_ALLOCATOR(Instant);
public:
virtual ~Instant() override = default;
[[nodiscard]] BigInt const& nanoseconds() const { return m_nanoseconds; }
private:
Instant(BigInt const& nanoseconds, Object& prototype);
virtual void visit_edges(Visitor&) override;
// 8.4 Properties of Temporal.Instant Instances, https://tc39.es/proposal-temporal/#sec-properties-of-temporal-instant-instances
GC::Ref<BigInt const> m_nanoseconds; // [[Nanoseconds]]
};
// https://tc39.es/proposal-temporal/#eqn-nsMaxInstant // https://tc39.es/proposal-temporal/#eqn-nsMaxInstant
// nsMaxInstant = 10^8 × nsPerDay = 8.64 × 10^21 extern Crypto::SignedBigInteger const NANOSECONDS_MAX_INSTANT;
static auto const ns_max_instant = "8640000000000000000000"_sbigint;
// https://tc39.es/proposal-temporal/#eqn-nsMinInstant // https://tc39.es/proposal-temporal/#eqn-nsMinInstant
// nsMinInstant = -nsMaxInstant = -8.64 × 10^21 extern Crypto::SignedBigInteger const NANOSECONDS_MIN_INSTANT;
static auto const ns_min_instant = "-8640000000000000000000"_sbigint;
bool is_valid_epoch_nanoseconds(BigInt const& epoch_nanoseconds); // https://tc39.es/proposal-temporal/#eqn-nsPerDay
bool is_valid_epoch_nanoseconds(Crypto::SignedBigInteger const& epoch_nanoseconds); extern Crypto::UnsignedBigInteger const NANOSECONDS_PER_DAY;
ThrowCompletionOr<Instant*> create_temporal_instant(VM&, BigInt const& nanoseconds, FunctionObject const* new_target = nullptr);
ThrowCompletionOr<Instant*> to_temporal_instant(VM&, Value item); // Non-standard:
ThrowCompletionOr<BigInt*> parse_temporal_instant(VM&, StringView iso_string); extern Crypto::UnsignedBigInteger const NANOSECONDS_PER_HOUR;
i32 compare_epoch_nanoseconds(BigInt const&, BigInt const&); extern Crypto::UnsignedBigInteger const NANOSECONDS_PER_MINUTE;
ThrowCompletionOr<BigInt*> add_instant(VM&, BigInt const& epoch_nanoseconds, double hours, double minutes, double seconds, double milliseconds, double microseconds, double nanoseconds); extern Crypto::UnsignedBigInteger const NANOSECONDS_PER_SECOND;
TimeDurationRecord difference_instant(VM&, BigInt const& nanoseconds1, BigInt const& nanoseconds2, u64 rounding_increment, StringView smallest_unit, StringView largest_unit, StringView rounding_mode); extern Crypto::UnsignedBigInteger const NANOSECONDS_PER_MILLISECOND;
BigInt* round_temporal_instant(VM&, BigInt const& nanoseconds, u64 increment, StringView unit, StringView rounding_mode); extern Crypto::UnsignedBigInteger const NANOSECONDS_PER_MICROSECOND;
ThrowCompletionOr<String> temporal_instant_to_string(VM&, Instant&, Value time_zone, Variant<StringView, u8> const& precision); extern Crypto::UnsignedBigInteger const NANOSECONDS_PER_NANOSECOND;
ThrowCompletionOr<GC::Ref<Duration>> difference_temporal_instant(VM&, DifferenceOperation, Instant const&, Value other, Value options);
ThrowCompletionOr<Instant*> add_duration_to_or_subtract_duration_from_instant(VM&, ArithmeticOperation, Instant const&, Value temporal_duration_like); extern Crypto::UnsignedBigInteger const MICROSECONDS_PER_MILLISECOND;
extern Crypto::UnsignedBigInteger const MILLISECONDS_PER_SECOND;
extern Crypto::UnsignedBigInteger const SECONDS_PER_MINUTE;
extern Crypto::UnsignedBigInteger const MINUTES_PER_HOUR;
extern Crypto::UnsignedBigInteger const HOURS_PER_DAY;
} }

View file

@ -1,169 +0,0 @@
/*
* Copyright (c) 2021-2023, Linus Groh <linusg@serenityos.org>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#include <AK/TypeCasts.h>
#include <LibCrypto/BigInt/UnsignedBigInteger.h>
#include <LibJS/Runtime/GlobalObject.h>
#include <LibJS/Runtime/Temporal/Instant.h>
#include <LibJS/Runtime/Temporal/InstantConstructor.h>
#include <LibJS/Runtime/ValueInlines.h>
namespace JS::Temporal {
GC_DEFINE_ALLOCATOR(InstantConstructor);
// 8.1 The Temporal.Instant Constructor, https://tc39.es/proposal-temporal/#sec-temporal-instant-constructor
InstantConstructor::InstantConstructor(Realm& realm)
: NativeFunction(realm.vm().names.Instant.as_string(), realm.intrinsics().function_prototype())
{
}
void InstantConstructor::initialize(Realm& realm)
{
Base::initialize(realm);
auto& vm = this->vm();
// 8.2.1 Temporal.Instant.prototype, https://tc39.es/proposal-temporal/#sec-temporal.instant.prototype
define_direct_property(vm.names.prototype, realm.intrinsics().temporal_instant_prototype(), 0);
u8 attr = Attribute::Writable | Attribute::Configurable;
define_native_function(realm, vm.names.from, from, 1, attr);
define_native_function(realm, vm.names.fromEpochSeconds, from_epoch_seconds, 1, attr);
define_native_function(realm, vm.names.fromEpochMilliseconds, from_epoch_milliseconds, 1, attr);
define_native_function(realm, vm.names.fromEpochMicroseconds, from_epoch_microseconds, 1, attr);
define_native_function(realm, vm.names.fromEpochNanoseconds, from_epoch_nanoseconds, 1, attr);
define_native_function(realm, vm.names.compare, compare, 2, attr);
define_direct_property(vm.names.length, Value(1), Attribute::Configurable);
}
// 8.1.1 Temporal.Instant ( epochNanoseconds ), https://tc39.es/proposal-temporal/#sec-temporal.instant
ThrowCompletionOr<Value> InstantConstructor::call()
{
auto& vm = this->vm();
// 1. If NewTarget is undefined, then
// a. Throw a TypeError exception.
return vm.throw_completion<TypeError>(ErrorType::ConstructorWithoutNew, "Temporal.Instant");
}
// 8.1.1 Temporal.Instant ( epochNanoseconds ), https://tc39.es/proposal-temporal/#sec-temporal.instant
ThrowCompletionOr<GC::Ref<Object>> InstantConstructor::construct(FunctionObject& new_target)
{
auto& vm = this->vm();
// 2. Let epochNanoseconds be ? ToBigInt(epochNanoseconds).
auto epoch_nanoseconds = TRY(vm.argument(0).to_bigint(vm));
// 3. If ! IsValidEpochNanoseconds(epochNanoseconds) is false, throw a RangeError exception.
if (!is_valid_epoch_nanoseconds(epoch_nanoseconds))
return vm.throw_completion<RangeError>(ErrorType::TemporalInvalidEpochNanoseconds);
// 4. Return ? CreateTemporalInstant(epochNanoseconds, NewTarget).
return *TRY(create_temporal_instant(vm, epoch_nanoseconds, &new_target));
}
// 8.2.2 Temporal.Instant.from ( item ), https://tc39.es/proposal-temporal/#sec-temporal.instant.from
JS_DEFINE_NATIVE_FUNCTION(InstantConstructor::from)
{
auto item = vm.argument(0);
// 1. If Type(item) is Object and item has an [[InitializedTemporalInstant]] internal slot, then
if (item.is_object() && is<Instant>(item.as_object())) {
// a. Return ! CreateTemporalInstant(item.[[Nanoseconds]]).
return MUST(create_temporal_instant(vm, BigInt::create(vm, static_cast<Instant&>(item.as_object()).nanoseconds().big_integer())));
}
// 2. Return ? ToTemporalInstant(item).
return TRY(to_temporal_instant(vm, item));
}
// 8.2.3 Temporal.Instant.fromEpochSeconds ( epochSeconds ), https://tc39.es/proposal-temporal/#sec-temporal.instant.fromepochseconds
JS_DEFINE_NATIVE_FUNCTION(InstantConstructor::from_epoch_seconds)
{
// 1. Set epochSeconds to ? ToNumber(epochSeconds).
auto epoch_seconds_value = TRY(vm.argument(0).to_number(vm));
// 2. Set epochSeconds to ? NumberToBigInt(epochSeconds).
auto* epoch_seconds = TRY(number_to_bigint(vm, epoch_seconds_value));
// 3. Let epochNanoseconds be epochSeconds × 10^9.
auto epoch_nanoseconds = BigInt::create(vm, epoch_seconds->big_integer().multiplied_by(Crypto::UnsignedBigInteger { 1'000'000'000 }));
// 4. If ! IsValidEpochNanoseconds(epochNanoseconds) is false, throw a RangeError exception.
if (!is_valid_epoch_nanoseconds(epoch_nanoseconds))
return vm.throw_completion<RangeError>(ErrorType::TemporalInvalidEpochNanoseconds);
// 5. Return ! CreateTemporalInstant(epochNanoseconds).
return MUST(create_temporal_instant(vm, epoch_nanoseconds));
}
// 8.2.4 Temporal.Instant.fromEpochMilliseconds ( epochMilliseconds ), https://tc39.es/proposal-temporal/#sec-temporal.instant.fromepochmilliseconds
JS_DEFINE_NATIVE_FUNCTION(InstantConstructor::from_epoch_milliseconds)
{
// 1. Set epochMilliseconds to ? ToNumber(epochMilliseconds).
auto epoch_milliseconds_value = TRY(vm.argument(0).to_number(vm));
// 2. Set epochMilliseconds to ? NumberToBigInt(epochMilliseconds).
auto* epoch_milliseconds = TRY(number_to_bigint(vm, epoch_milliseconds_value));
// 3. Let epochNanoseconds be epochMilliseconds × 10^6.
auto epoch_nanoseconds = BigInt::create(vm, epoch_milliseconds->big_integer().multiplied_by(Crypto::UnsignedBigInteger { 1'000'000 }));
// 4. If ! IsValidEpochNanoseconds(epochNanoseconds) is false, throw a RangeError exception.
if (!is_valid_epoch_nanoseconds(epoch_nanoseconds))
return vm.throw_completion<RangeError>(ErrorType::TemporalInvalidEpochNanoseconds);
// 5. Return ! CreateTemporalInstant(epochNanoseconds).
return MUST(create_temporal_instant(vm, epoch_nanoseconds));
}
// 8.2.5 Temporal.Instant.fromEpochMicroseconds ( epochMicroseconds ), https://tc39.es/proposal-temporal/#sec-temporal.instant.fromepochmicroseconds
JS_DEFINE_NATIVE_FUNCTION(InstantConstructor::from_epoch_microseconds)
{
// 1. Set epochMicroseconds to ? ToBigInt(epochMicroseconds).
auto epoch_microseconds = TRY(vm.argument(0).to_bigint(vm));
// 2. Let epochNanoseconds be epochMicroseconds × 1000.
auto epoch_nanoseconds = BigInt::create(vm, epoch_microseconds->big_integer().multiplied_by(Crypto::UnsignedBigInteger { 1'000 }));
// 3. If ! IsValidEpochNanoseconds(epochNanoseconds) is false, throw a RangeError exception.
if (!is_valid_epoch_nanoseconds(epoch_nanoseconds))
return vm.throw_completion<RangeError>(ErrorType::TemporalInvalidEpochNanoseconds);
// 4. Return ! CreateTemporalInstant(epochNanoseconds).
return MUST(create_temporal_instant(vm, epoch_nanoseconds));
}
// 8.2.6 Temporal.Instant.fromEpochNanoseconds ( epochNanoseconds ), https://tc39.es/proposal-temporal/#sec-temporal.instant.fromepochnanoseconds
JS_DEFINE_NATIVE_FUNCTION(InstantConstructor::from_epoch_nanoseconds)
{
// 1. Set epochNanoseconds to ? ToBigInt(epochNanoseconds).
auto epoch_nanoseconds = TRY(vm.argument(0).to_bigint(vm));
// 2. If ! IsValidEpochNanoseconds(epochNanoseconds) is false, throw a RangeError exception.
if (!is_valid_epoch_nanoseconds(epoch_nanoseconds))
return vm.throw_completion<RangeError>(ErrorType::TemporalInvalidEpochNanoseconds);
// 3. Return ! CreateTemporalInstant(epochNanoseconds).
return MUST(create_temporal_instant(vm, epoch_nanoseconds));
}
// 8.2.7 Temporal.Instant.compare ( one, two ), https://tc39.es/proposal-temporal/#sec-temporal.instant.compare
JS_DEFINE_NATIVE_FUNCTION(InstantConstructor::compare)
{
// 1. Set one to ? ToTemporalInstant(one).
auto* one = TRY(to_temporal_instant(vm, vm.argument(0)));
// 2. Set two to ? ToTemporalInstant(two).
auto* two = TRY(to_temporal_instant(vm, vm.argument(1)));
// 3. Return 𝔽(! CompareEpochNanoseconds(one.[[Nanoseconds]], two.[[Nanoseconds]])).
return Value(compare_epoch_nanoseconds(one->nanoseconds(), two->nanoseconds()));
}
}

View file

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

View file

@ -1,413 +0,0 @@
/*
* Copyright (c) 2021-2023, Linus Groh <linusg@serenityos.org>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#include <AK/TypeCasts.h>
#include <LibCrypto/BigInt/UnsignedBigInteger.h>
#include <LibJS/Runtime/Date.h>
#include <LibJS/Runtime/GlobalObject.h>
#include <LibJS/Runtime/Temporal/AbstractOperations.h>
#include <LibJS/Runtime/Temporal/Calendar.h>
#include <LibJS/Runtime/Temporal/Duration.h>
#include <LibJS/Runtime/Temporal/Instant.h>
#include <LibJS/Runtime/Temporal/InstantPrototype.h>
#include <LibJS/Runtime/Temporal/TimeZone.h>
#include <LibJS/Runtime/Temporal/ZonedDateTime.h>
namespace JS::Temporal {
GC_DEFINE_ALLOCATOR(InstantPrototype);
// 8.3 Properties of the Temporal.Instant Prototype Object, https://tc39.es/proposal-temporal/#sec-properties-of-the-temporal-instant-prototype-object
InstantPrototype::InstantPrototype(Realm& realm)
: PrototypeObject(realm.intrinsics().object_prototype())
{
}
void InstantPrototype::initialize(Realm& realm)
{
Base::initialize(realm);
auto& vm = this->vm();
// 8.3.2 Temporal.Instant.prototype[ @@toStringTag ], https://tc39.es/proposal-temporal/#sec-temporal.instant.prototype-@@tostringtag
define_direct_property(vm.well_known_symbol_to_string_tag(), PrimitiveString::create(vm, "Temporal.Instant"_string), Attribute::Configurable);
define_native_accessor(realm, vm.names.epochSeconds, epoch_seconds_getter, {}, Attribute::Configurable);
define_native_accessor(realm, vm.names.epochMilliseconds, epoch_milliseconds_getter, {}, Attribute::Configurable);
define_native_accessor(realm, vm.names.epochMicroseconds, epoch_microseconds_getter, {}, Attribute::Configurable);
define_native_accessor(realm, vm.names.epochNanoseconds, epoch_nanoseconds_getter, {}, Attribute::Configurable);
u8 attr = Attribute::Writable | Attribute::Configurable;
define_native_function(realm, vm.names.add, add, 1, attr);
define_native_function(realm, vm.names.subtract, subtract, 1, attr);
define_native_function(realm, vm.names.until, until, 1, attr);
define_native_function(realm, vm.names.since, since, 1, attr);
define_native_function(realm, vm.names.round, round, 1, attr);
define_native_function(realm, vm.names.equals, equals, 1, attr);
define_native_function(realm, vm.names.toString, to_string, 0, attr);
define_native_function(realm, vm.names.toLocaleString, to_locale_string, 0, attr);
define_native_function(realm, vm.names.toJSON, to_json, 0, attr);
define_native_function(realm, vm.names.valueOf, value_of, 0, attr);
define_native_function(realm, vm.names.toZonedDateTime, to_zoned_date_time, 1, attr);
define_native_function(realm, vm.names.toZonedDateTimeISO, to_zoned_date_time_iso, 1, attr);
}
// 8.3.3 get Temporal.Instant.prototype.epochSeconds, https://tc39.es/proposal-temporal/#sec-get-temporal.instant.prototype.epochseconds
JS_DEFINE_NATIVE_FUNCTION(InstantPrototype::epoch_seconds_getter)
{
// 1. Let instant be the this value.
// 2. Perform ? RequireInternalSlot(instant, [[InitializedTemporalInstant]]).
auto instant = TRY(typed_this_object(vm));
// 3. Let ns be instant.[[Nanoseconds]].
auto& ns = instant->nanoseconds();
// 4. Let s be truncate((ns) / 10^9).
auto [s, _] = ns.big_integer().divided_by(Crypto::UnsignedBigInteger { 1'000'000'000 });
// 5. Return 𝔽(s).
return Value((double)s.to_base_deprecated(10).to_number<i64>().value());
}
// 8.3.4 get Temporal.Instant.prototype.epochMilliseconds, https://tc39.es/proposal-temporal/#sec-get-temporal.instant.prototype.epochmilliseconds
JS_DEFINE_NATIVE_FUNCTION(InstantPrototype::epoch_milliseconds_getter)
{
// 1. Let instant be the this value.
// 2. Perform ? RequireInternalSlot(instant, [[InitializedTemporalInstant]]).
auto instant = TRY(typed_this_object(vm));
// 3. Let ns be instant.[[Nanoseconds]].
auto& ns = instant->nanoseconds();
// 4. Let ms be truncate((ns) / 10^6).
auto [ms, _] = ns.big_integer().divided_by(Crypto::UnsignedBigInteger { 1'000'000 });
// 5. Return 𝔽(ms).
return Value((double)ms.to_base_deprecated(10).to_number<i64>().value());
}
// 8.3.5 get Temporal.Instant.prototype.epochMicroseconds, https://tc39.es/proposal-temporal/#sec-get-temporal.instant.prototype.epochmicroseconds
JS_DEFINE_NATIVE_FUNCTION(InstantPrototype::epoch_microseconds_getter)
{
// 1. Let instant be the this value.
// 2. Perform ? RequireInternalSlot(instant, [[InitializedTemporalInstant]]).
auto instant = TRY(typed_this_object(vm));
// 3. Let ns be instant.[[Nanoseconds]].
auto& ns = instant->nanoseconds();
// 4. Let µs be truncate((ns) / 10^3).
auto [us, _] = ns.big_integer().divided_by(Crypto::UnsignedBigInteger { 1'000 });
// 5. Return (µs).
return BigInt::create(vm, move(us));
}
// 8.3.6 get Temporal.Instant.prototype.epochNanoseconds, https://tc39.es/proposal-temporal/#sec-get-temporal.instant.prototype.epochnanoseconds
JS_DEFINE_NATIVE_FUNCTION(InstantPrototype::epoch_nanoseconds_getter)
{
// 1. Let instant be the this value.
// 2. Perform ? RequireInternalSlot(instant, [[InitializedTemporalInstant]]).
auto instant = TRY(typed_this_object(vm));
// 3. Let ns be instant.[[Nanoseconds]].
auto& ns = instant->nanoseconds();
// 4. Return ns.
return &ns;
}
// 8.3.7 Temporal.Instant.prototype.add ( temporalDurationLike ), https://tc39.es/proposal-temporal/#sec-temporal.instant.prototype.add
JS_DEFINE_NATIVE_FUNCTION(InstantPrototype::add)
{
auto temporal_duration_like = vm.argument(0);
// 1. Let instant be the this value.
// 2. Perform ? RequireInternalSlot(instant, [[InitializedTemporalInstant]]).
auto instant = TRY(typed_this_object(vm));
// 3. Return ? AddDurationToOrSubtractDurationFromInstant(add, instant, temporalDurationLike).
return TRY(add_duration_to_or_subtract_duration_from_instant(vm, ArithmeticOperation::Add, instant, temporal_duration_like));
}
// 8.3.8 Temporal.Instant.prototype.subtract ( temporalDurationLike ), https://tc39.es/proposal-temporal/#sec-temporal.instant.prototype.subtract
JS_DEFINE_NATIVE_FUNCTION(InstantPrototype::subtract)
{
auto temporal_duration_like = vm.argument(0);
// 1. Let instant be the this value.
// 2. Perform ? RequireInternalSlot(instant, [[InitializedTemporalInstant]]).
auto instant = TRY(typed_this_object(vm));
// 3. Return ? AddDurationToOrSubtractDurationFromInstant(subtract, instant, temporalDurationLike).
return TRY(add_duration_to_or_subtract_duration_from_instant(vm, ArithmeticOperation::Subtract, instant, temporal_duration_like));
}
// 8.3.9 Temporal.Instant.prototype.until ( other [ , options ] ), https://tc39.es/proposal-temporal/#sec-temporal.instant.prototype.until
JS_DEFINE_NATIVE_FUNCTION(InstantPrototype::until)
{
auto other = vm.argument(0);
auto options = vm.argument(1);
// 1. Let instant be the this value.
// 2. Perform ? RequireInternalSlot(instant, [[InitializedTemporalInstant]]).
auto instant = TRY(typed_this_object(vm));
// 3. Return ? DifferenceTemporalInstant(until, instant, other, options).
return TRY(difference_temporal_instant(vm, DifferenceOperation::Until, instant, other, options));
}
// 8.3.10 Temporal.Instant.prototype.since ( other [ , options ] ), https://tc39.es/proposal-temporal/#sec-temporal.instant.prototype.since
JS_DEFINE_NATIVE_FUNCTION(InstantPrototype::since)
{
auto other = vm.argument(0);
auto options = vm.argument(1);
// 1. Let instant be the this value.
// 2. Perform ? RequireInternalSlot(instant, [[InitializedTemporalInstant]]).
auto instant = TRY(typed_this_object(vm));
// 3. Return ? DifferenceTemporalInstant(since, instant, other, options).
return TRY(difference_temporal_instant(vm, DifferenceOperation::Since, instant, other, options));
}
// 8.3.11 Temporal.Instant.prototype.round ( roundTo ), https://tc39.es/proposal-temporal/#sec-temporal.instant.prototype.round
JS_DEFINE_NATIVE_FUNCTION(InstantPrototype::round)
{
auto& realm = *vm.current_realm();
// 1. Let instant be the this value.
// 2. Perform ? RequireInternalSlot(instant, [[InitializedTemporalInstant]]).
auto instant = TRY(typed_this_object(vm));
// 3. If roundTo is undefined, then
if (vm.argument(0).is_undefined()) {
// a. Throw a TypeError exception.
return vm.throw_completion<TypeError>(ErrorType::TemporalMissingOptionsObject);
}
Object* round_to;
// 4. If Type(roundTo) is String, then
if (vm.argument(0).is_string()) {
// a. Let paramString be roundTo.
// b. Set roundTo to OrdinaryObjectCreate(null).
round_to = Object::create(realm, nullptr);
// c. Perform ! CreateDataPropertyOrThrow(roundTo, "smallestUnit", paramString).
MUST(round_to->create_data_property_or_throw(vm.names.smallestUnit, vm.argument(0)));
}
// 5. Else,
else {
// a. Set roundTo to ? GetOptionsObject(roundTo).
round_to = TRY(get_options_object(vm, vm.argument(0)));
}
// 6. Let smallestUnit be ? GetTemporalUnit(roundTo, "smallestUnit", time, required).
auto smallest_unit_value = TRY(get_temporal_unit(vm, *round_to, vm.names.smallestUnit, UnitGroup::Time, TemporalUnitRequired {}));
// 6. If smallestUnit is undefined, throw a RangeError exception.
if (!smallest_unit_value.has_value())
return vm.throw_completion<RangeError>(ErrorType::OptionIsNotValidValue, vm.names.undefined.as_string(), "smallestUnit");
// At this point smallest_unit_value can only be a string
auto& smallest_unit = *smallest_unit_value;
// 7. Let roundingMode be ? ToTemporalRoundingMode(roundTo, "halfExpand").
auto rounding_mode = TRY(to_temporal_rounding_mode(vm, *round_to, "halfExpand"sv));
double maximum;
// 8. If smallestUnit is "hour", then
if (smallest_unit == "hour"sv) {
// a. Let maximum be HoursPerDay.
maximum = hours_per_day;
}
// 9. Else if smallestUnit is "minute", then
else if (smallest_unit == "minute"sv) {
// a. Let maximum be MinutesPerHour × HoursPerDay.
maximum = minutes_per_hour * hours_per_day;
}
// 10. Else if smallestUnit is "second", then
else if (smallest_unit == "second"sv) {
// a. Let maximum be SecondsPerMinute × MinutesPerHour × HoursPerDay.
maximum = seconds_per_minute * minutes_per_hour * hours_per_day;
}
// 11. Else if smallestUnit is "millisecond", then
else if (smallest_unit == "millisecond"sv) {
// a. Let maximum be (msPerDay).
maximum = ms_per_day;
}
// 12. Else if smallestUnit is "microsecond", then
else if (smallest_unit == "microsecond"sv) {
// a. Let maximum be 10^3 × (msPerDay).
maximum = 1000 * ms_per_day;
}
// 13. Else,
else {
// a. Assert: smallestUnit is "nanosecond".
VERIFY(smallest_unit == "nanosecond"sv);
// b. Let maximum be nsPerDay.
maximum = ns_per_day;
}
// 14. Let roundingIncrement be ? ToTemporalRoundingIncrement(options).
auto rounding_increment = TRY(to_temporal_rounding_increment(vm, *round_to));
// 15. Perform ? ValidateTemporalRoundingIncrement(roundingIncrement, maximum, true).
TRY(validate_temporal_rounding_increment(vm, rounding_increment, maximum, true));
// 16. Let roundedNs be ! RoundTemporalInstant(instant.[[Nanoseconds]], roundingIncrement, smallestUnit, roundingMode).
auto* rounded_ns = round_temporal_instant(vm, instant->nanoseconds(), rounding_increment, smallest_unit, rounding_mode);
// 17. Return ! CreateTemporalInstant(roundedNs).
return MUST(create_temporal_instant(vm, *rounded_ns));
}
// 8.3.12 Temporal.Instant.prototype.equals ( other ), https://tc39.es/proposal-temporal/#sec-temporal.instant.prototype.equals
JS_DEFINE_NATIVE_FUNCTION(InstantPrototype::equals)
{
// 1. Let instant be the this value.
// 2. Perform ? RequireInternalSlot(instant, [[InitializedTemporalInstant]]).
auto instant = TRY(typed_this_object(vm));
// 3. Set other to ? ToTemporalInstant(other).
auto other = TRY(to_temporal_instant(vm, vm.argument(0)));
// 4. If instant.[[Nanoseconds]] ≠ other.[[Nanoseconds]], return false.
if (instant->nanoseconds().big_integer() != other->nanoseconds().big_integer())
return Value(false);
// 5. Return true.
return Value(true);
}
// 8.3.13 Temporal.Instant.prototype.toString ( [ options ] ), https://tc39.es/proposal-temporal/#sec-temporal.instant.prototype.tostring
JS_DEFINE_NATIVE_FUNCTION(InstantPrototype::to_string)
{
// 1. Let instant be the this value.
// 2. Perform ? RequireInternalSlot(instant, [[InitializedTemporalInstant]]).
auto instant = TRY(typed_this_object(vm));
// 3. Set options to ? GetOptionsObject(options).
auto const* options = TRY(get_options_object(vm, vm.argument(0)));
// 4. Let timeZone be ? Get(options, "timeZone").
auto time_zone = TRY(options->get(vm.names.timeZone));
// 5. If timeZone is not undefined, then
if (!time_zone.is_undefined()) {
// a. Set timeZone to ? ToTemporalTimeZone(timeZone).
time_zone = TRY(to_temporal_time_zone(vm, time_zone));
}
// 6. Let precision be ? ToSecondsStringPrecisionRecord(options).
auto precision = TRY(to_seconds_string_precision_record(vm, *options));
// 7. Let roundingMode be ? ToTemporalRoundingMode(options, "trunc").
auto rounding_mode = TRY(to_temporal_rounding_mode(vm, *options, "trunc"sv));
// 8. Let roundedNs be ! RoundTemporalInstant(instant.[[Nanoseconds]], precision.[[Increment]], precision.[[Unit]], roundingMode).
auto* rounded_ns = round_temporal_instant(vm, instant->nanoseconds(), precision.increment, precision.unit, rounding_mode);
// 9. Let roundedInstant be ! CreateTemporalInstant(roundedNs).
auto* rounded_instant = MUST(create_temporal_instant(vm, *rounded_ns));
// 10. Return ? TemporalInstantToString(roundedInstant, timeZone, precision.[[Precision]]).
return PrimitiveString::create(vm, TRY(temporal_instant_to_string(vm, *rounded_instant, time_zone, precision.precision)));
}
// 8.3.14 Temporal.Instant.prototype.toLocaleString ( [ locales [ , options ] ] ), https://tc39.es/proposal-temporal/#sec-temporal.instant.prototype.tolocalestring
// NOTE: This is the minimum toLocaleString implementation for engines without ECMA-402.
JS_DEFINE_NATIVE_FUNCTION(InstantPrototype::to_locale_string)
{
// 1. Let instant be the this value.
// 2. Perform ? RequireInternalSlot(instant, [[InitializedTemporalInstant]]).
auto instant = TRY(typed_this_object(vm));
// 3. Return ? TemporalInstantToString(instant, undefined, "auto").
return PrimitiveString::create(vm, TRY(temporal_instant_to_string(vm, instant, js_undefined(), "auto"sv)));
}
// 8.3.15 Temporal.Instant.prototype.toJSON ( ), https://tc39.es/proposal-temporal/#sec-temporal.instant.prototype.tojson
JS_DEFINE_NATIVE_FUNCTION(InstantPrototype::to_json)
{
// 1. Let instant be the this value.
// 2. Perform ? RequireInternalSlot(instant, [[InitializedTemporalInstant]]).
auto instant = TRY(typed_this_object(vm));
// 3. Return ? TemporalInstantToString(instant, undefined, "auto").
return PrimitiveString::create(vm, TRY(temporal_instant_to_string(vm, instant, js_undefined(), "auto"sv)));
}
// 8.3.16 Temporal.Instant.prototype.valueOf ( ), https://tc39.es/proposal-temporal/#sec-temporal.instant.prototype.valueof
JS_DEFINE_NATIVE_FUNCTION(InstantPrototype::value_of)
{
// 1. Throw a TypeError exception.
return vm.throw_completion<TypeError>(ErrorType::Convert, "Temporal.Instant", "a primitive value");
}
// 8.3.17 Temporal.Instant.prototype.toZonedDateTime ( item ), https://tc39.es/proposal-temporal/#sec-temporal.instant.prototype.tozoneddatetime
JS_DEFINE_NATIVE_FUNCTION(InstantPrototype::to_zoned_date_time)
{
auto item = vm.argument(0);
// 1. Let instant be the this value.
// 2. Perform ? RequireInternalSlot(instant, [[InitializedTemporalInstant]]).
auto instant = TRY(typed_this_object(vm));
// 3. If Type(item) is not Object, then
if (!item.is_object()) {
// a. Throw a TypeError exception.
return vm.throw_completion<TypeError>(ErrorType::NotAnObject, item);
}
// 4. Let calendarLike be ? Get(item, "calendar").
auto calendar_like = TRY(item.as_object().get(vm.names.calendar));
// 5. If calendarLike is undefined, then
if (calendar_like.is_undefined()) {
// a. Throw a TypeError exception.
return vm.throw_completion<TypeError>(ErrorType::MissingRequiredProperty, vm.names.calendar.as_string());
}
// 6. Let calendar be ? ToTemporalCalendar(calendarLike).
auto* calendar = TRY(to_temporal_calendar(vm, calendar_like));
// 7. Let temporalTimeZoneLike be ? Get(item, "timeZone").
auto temporal_time_zone_like = TRY(item.as_object().get(vm.names.timeZone));
// 8. If temporalTimeZoneLike is undefined, then
if (temporal_time_zone_like.is_undefined()) {
// a. Throw a TypeError exception.
return vm.throw_completion<TypeError>(ErrorType::MissingRequiredProperty, vm.names.timeZone.as_string());
}
// 9. Let timeZone be ? ToTemporalTimeZone(temporalTimeZoneLike).
auto* time_zone = TRY(to_temporal_time_zone(vm, temporal_time_zone_like));
// 10. Return ? CreateTemporalZonedDateTime(instant.[[Nanoseconds]], timeZone, calendar).
return TRY(create_temporal_zoned_date_time(vm, instant->nanoseconds(), *time_zone, *calendar));
}
// 8.3.18 Temporal.Instant.prototype.toZonedDateTimeISO ( timeZone ), https://tc39.es/proposal-temporal/#sec-temporal.instant.prototype.tozoneddatetimeiso
JS_DEFINE_NATIVE_FUNCTION(InstantPrototype::to_zoned_date_time_iso)
{
// 1. Let instant be the this value.
// 2. Perform ? RequireInternalSlot(instant, [[InitializedTemporalInstant]]).
auto instant = TRY(typed_this_object(vm));
// 3. Set timeZone to ? ToTemporalTimeZone(timeZone).
auto* time_zone = TRY(to_temporal_time_zone(vm, vm.argument(0)));
// 4. Let calendar be ! GetISO8601Calendar().
auto* calendar = get_iso8601_calendar(vm);
// 5. Return ? CreateTemporalZonedDateTime(instant.[[Nanoseconds]], timeZone, calendar).
return TRY(create_temporal_zoned_date_time(vm, instant->nanoseconds(), *time_zone, *calendar));
}
}

View file

@ -1,43 +0,0 @@
/*
* Copyright (c) 2021, Linus Groh <linusg@serenityos.org>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#pragma once
#include <LibJS/Runtime/PrototypeObject.h>
#include <LibJS/Runtime/Temporal/Instant.h>
namespace JS::Temporal {
class InstantPrototype final : public PrototypeObject<InstantPrototype, Instant> {
JS_PROTOTYPE_OBJECT(InstantPrototype, Instant, Temporal.Instant);
GC_DECLARE_ALLOCATOR(InstantPrototype);
public:
virtual void initialize(Realm&) override;
virtual ~InstantPrototype() override = default;
private:
explicit InstantPrototype(Realm&);
JS_DECLARE_NATIVE_FUNCTION(epoch_seconds_getter);
JS_DECLARE_NATIVE_FUNCTION(epoch_milliseconds_getter);
JS_DECLARE_NATIVE_FUNCTION(epoch_microseconds_getter);
JS_DECLARE_NATIVE_FUNCTION(epoch_nanoseconds_getter);
JS_DECLARE_NATIVE_FUNCTION(add);
JS_DECLARE_NATIVE_FUNCTION(subtract);
JS_DECLARE_NATIVE_FUNCTION(until);
JS_DECLARE_NATIVE_FUNCTION(since);
JS_DECLARE_NATIVE_FUNCTION(round);
JS_DECLARE_NATIVE_FUNCTION(equals);
JS_DECLARE_NATIVE_FUNCTION(to_string);
JS_DECLARE_NATIVE_FUNCTION(to_locale_string);
JS_DECLARE_NATIVE_FUNCTION(to_json);
JS_DECLARE_NATIVE_FUNCTION(value_of);
JS_DECLARE_NATIVE_FUNCTION(to_zoned_date_time);
JS_DECLARE_NATIVE_FUNCTION(to_zoned_date_time_iso);
};
}

View file

@ -1,241 +0,0 @@
/*
* Copyright (c) 2021-2023, Linus Groh <linusg@serenityos.org>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#include <AK/Time.h>
#include <LibCrypto/BigInt/SignedBigInteger.h>
#include <LibJS/Runtime/Completion.h>
#include <LibJS/Runtime/Date.h>
#include <LibJS/Runtime/GlobalObject.h>
#include <LibJS/Runtime/Temporal/Calendar.h>
#include <LibJS/Runtime/Temporal/Instant.h>
#include <LibJS/Runtime/Temporal/Now.h>
#include <LibJS/Runtime/Temporal/PlainDate.h>
#include <LibJS/Runtime/Temporal/PlainDateTime.h>
#include <LibJS/Runtime/Temporal/PlainTime.h>
#include <LibJS/Runtime/Temporal/TimeZone.h>
#include <LibJS/Runtime/Temporal/ZonedDateTime.h>
namespace JS::Temporal {
GC_DEFINE_ALLOCATOR(Now);
// 2 The Temporal.Now Object, https://tc39.es/proposal-temporal/#sec-temporal-now-object
Now::Now(Realm& realm)
: Object(ConstructWithPrototypeTag::Tag, realm.intrinsics().object_prototype())
{
}
void Now::initialize(Realm& realm)
{
Base::initialize(realm);
auto& vm = this->vm();
// 2.1.1 Temporal.Now [ @@toStringTag ], https://tc39.es/proposal-temporal/#sec-temporal-now-@@tostringtag
define_direct_property(vm.well_known_symbol_to_string_tag(), PrimitiveString::create(vm, "Temporal.Now"_string), Attribute::Configurable);
u8 attr = Attribute::Writable | Attribute::Configurable;
define_native_function(realm, vm.names.timeZone, time_zone, 0, attr);
define_native_function(realm, vm.names.instant, instant, 0, attr);
define_native_function(realm, vm.names.plainDateTime, plain_date_time, 1, attr);
define_native_function(realm, vm.names.plainDateTimeISO, plain_date_time_iso, 0, attr);
define_native_function(realm, vm.names.zonedDateTime, zoned_date_time, 1, attr);
define_native_function(realm, vm.names.zonedDateTimeISO, zoned_date_time_iso, 0, attr);
define_native_function(realm, vm.names.plainDate, plain_date, 1, attr);
define_native_function(realm, vm.names.plainDateISO, plain_date_iso, 0, attr);
define_native_function(realm, vm.names.plainTimeISO, plain_time_iso, 0, attr);
}
// 2.2.1 Temporal.Now.timeZone ( ), https://tc39.es/proposal-temporal/#sec-temporal.now.timezone
JS_DEFINE_NATIVE_FUNCTION(Now::time_zone)
{
// 1. Return ! SystemTimeZone().
return system_time_zone(vm);
}
// 2.2.2 Temporal.Now.instant ( ), https://tc39.es/proposal-temporal/#sec-temporal.now.instant
JS_DEFINE_NATIVE_FUNCTION(Now::instant)
{
// 1. Return ! SystemInstant().
return system_instant(vm);
}
// 2.2.3 Temporal.Now.plainDateTime ( calendarLike [ , temporalTimeZoneLike ] ), https://tc39.es/proposal-temporal/#sec-temporal.now.plaindatetime
JS_DEFINE_NATIVE_FUNCTION(Now::plain_date_time)
{
auto calendar_like = vm.argument(0);
auto temporal_time_zone_like = vm.argument(1);
// 1. Return ? SystemDateTime(temporalTimeZoneLike, calendarLike).
return TRY(system_date_time(vm, temporal_time_zone_like, calendar_like));
}
// 2.2.4 Temporal.Now.plainDateTimeISO ( [ temporalTimeZoneLike ] ), https://tc39.es/proposal-temporal/#sec-temporal.now.plaindatetimeiso
JS_DEFINE_NATIVE_FUNCTION(Now::plain_date_time_iso)
{
auto temporal_time_zone_like = vm.argument(0);
// 1, Let calendar be ! GetISO8601Calendar().
auto* calendar = get_iso8601_calendar(vm);
// 2. Return ? SystemDateTime(temporalTimeZoneLike, calendar).
return TRY(system_date_time(vm, temporal_time_zone_like, calendar));
}
// 2.2.5 Temporal.Now.zonedDateTime ( calendarLike [ , temporalTimeZoneLike ] ), https://tc39.es/proposal-temporal/#sec-temporal.now.zoneddatetime
JS_DEFINE_NATIVE_FUNCTION(Now::zoned_date_time)
{
auto calendar_like = vm.argument(0);
auto temporal_time_zone_like = vm.argument(1);
// 1. Return ? SystemZonedDateTime(temporalTimeZoneLike, calendarLike).
return TRY(system_zoned_date_time(vm, temporal_time_zone_like, calendar_like));
}
// 2.2.6 Temporal.Now.zonedDateTimeISO ( [ temporalTimeZoneLike ] ), https://tc39.es/proposal-temporal/#sec-temporal.now.zoneddatetimeiso
JS_DEFINE_NATIVE_FUNCTION(Now::zoned_date_time_iso)
{
auto temporal_time_zone_like = vm.argument(0);
// 1, Let calendar be ! GetISO8601Calendar().
auto* calendar = get_iso8601_calendar(vm);
// 2. Return ? SystemZonedDateTime(temporalTimeZoneLike, calendar).
return TRY(system_zoned_date_time(vm, temporal_time_zone_like, calendar));
}
// 2.2.7 Temporal.Now.plainDate ( calendarLike [ , temporalTimeZoneLike ] ), https://tc39.es/proposal-temporal/#sec-temporal.now.plaindate
JS_DEFINE_NATIVE_FUNCTION(Now::plain_date)
{
auto calendar_like = vm.argument(0);
auto temporal_time_zone_like = vm.argument(1);
// 1. Let dateTime be ? SystemDateTime(temporalTimeZoneLike, calendarLike).
auto* date_time = TRY(system_date_time(vm, temporal_time_zone_like, calendar_like));
// 2. Return ! CreateTemporalDate(dateTime.[[ISOYear]], dateTime.[[ISOMonth]], dateTime.[[ISODay]], dateTime.[[Calendar]]).
return MUST(create_temporal_date(vm, date_time->iso_year(), date_time->iso_month(), date_time->iso_day(), date_time->calendar()));
}
// 2.2.8 Temporal.Now.plainDateISO ( [ temporalTimeZoneLike ] ), https://tc39.es/proposal-temporal/#sec-temporal.now.plaindateiso
JS_DEFINE_NATIVE_FUNCTION(Now::plain_date_iso)
{
auto temporal_time_zone_like = vm.argument(0);
// 1. Let calendar be ! GetISO8601Calendar().
auto* calendar = get_iso8601_calendar(vm);
// 2. Let dateTime be ? SystemDateTime(temporalTimeZoneLike, calendar).
auto* date_time = TRY(system_date_time(vm, temporal_time_zone_like, calendar));
// 3. Return ! CreateTemporalDate(dateTime.[[ISOYear]], dateTime.[[ISOMonth]], dateTime.[[ISODay]], dateTime.[[Calendar]]).
return MUST(create_temporal_date(vm, date_time->iso_year(), date_time->iso_month(), date_time->iso_day(), date_time->calendar()));
}
// 2.2.9 Temporal.Now.plainTimeISO ( [ temporalTimeZoneLike ] ), https://tc39.es/proposal-temporal/#sec-temporal.now.plaintimeiso
JS_DEFINE_NATIVE_FUNCTION(Now::plain_time_iso)
{
auto temporal_time_zone_like = vm.argument(0);
// 1. Let calendar be ! GetISO8601Calendar().
auto* calendar = get_iso8601_calendar(vm);
// 2. Let dateTime be ? SystemDateTime(temporalTimeZoneLike, calendar).
auto* date_time = TRY(system_date_time(vm, temporal_time_zone_like, calendar));
// 3. Return ! CreateTemporalTime(dateTime.[[ISOHour]], dateTime.[[ISOMinute]], dateTime.[[ISOSecond]], dateTime.[[ISOMillisecond]], dateTime.[[ISOMicrosecond]], dateTime.[[ISONanosecond]]).
return MUST(create_temporal_time(vm, date_time->iso_hour(), date_time->iso_minute(), date_time->iso_second(), date_time->iso_millisecond(), date_time->iso_microsecond(), date_time->iso_nanosecond()));
}
// 2.3.1 SystemTimeZone ( ), https://tc39.es/proposal-temporal/#sec-temporal-systemtimezone
TimeZone* system_time_zone(VM& vm)
{
// 1. Let identifier be ! DefaultTimeZone().
auto identifier = system_time_zone_identifier();
// 2. Return ! CreateTemporalTimeZone(identifier).
// FIXME: Propagate possible OOM error
return MUST(create_temporal_time_zone(vm, move(identifier)));
}
// 2.3.2 SystemUTCEpochNanoseconds ( ), https://tc39.es/proposal-temporal/#sec-temporal-systemutcepochnanoseconds
BigInt* system_utc_epoch_nanoseconds(VM& vm)
{
// 1. Let ns be the approximate current UTC date and time, in nanoseconds since the epoch.
auto now = AK::UnixDateTime::now().nanoseconds_since_epoch();
auto ns = Crypto::SignedBigInteger { now };
// 2. Set ns to the result of clamping ns between nsMinInstant and nsMaxInstant.
// NOTE: Duration::to_nanoseconds() already clamps between -(2^63) and 2^63 - 1, the range of an i64,
// if an overflow occurs during seconds -> nanoseconds conversion.
// 3. Return (ns).
return BigInt::create(vm, move(ns));
}
// 2.3.3 SystemInstant ( ), https://tc39.es/proposal-temporal/#sec-temporal-systeminstant
Instant* system_instant(VM& vm)
{
// 1. Let ns be ! SystemUTCEpochNanoseconds().
auto* ns = system_utc_epoch_nanoseconds(vm);
// 2. Return ! CreateTemporalInstant(ns).
return MUST(create_temporal_instant(vm, *ns));
}
// 2.3.4 SystemDateTime ( temporalTimeZoneLike, calendarLike ), https://tc39.es/proposal-temporal/#sec-temporal-systemdatetime
ThrowCompletionOr<PlainDateTime*> system_date_time(VM& vm, Value temporal_time_zone_like, Value calendar_like)
{
Object* time_zone;
// 1. If temporalTimeZoneLike is undefined, then
if (temporal_time_zone_like.is_undefined()) {
// a. Let timeZone be ! SystemTimeZone().
time_zone = system_time_zone(vm);
}
// 2. Else,
else {
// a. Let timeZone be ? ToTemporalTimeZone(temporalTimeZoneLike).
time_zone = TRY(to_temporal_time_zone(vm, temporal_time_zone_like));
}
// 3. Let calendar be ? ToTemporalCalendar(calendarLike).
auto* calendar = TRY(to_temporal_calendar(vm, calendar_like));
// 4. Let instant be ! SystemInstant().
auto* instant = system_instant(vm);
// 5. Return ? BuiltinTimeZoneGetPlainDateTimeFor(timeZone, instant, calendar).
return builtin_time_zone_get_plain_date_time_for(vm, time_zone, *instant, *calendar);
}
// 2.3.5 SystemZonedDateTime ( temporalTimeZoneLike, calendarLike ), https://tc39.es/proposal-temporal/#sec-temporal-systemzoneddatetime
ThrowCompletionOr<ZonedDateTime*> system_zoned_date_time(VM& vm, Value temporal_time_zone_like, Value calendar_like)
{
Object* time_zone;
// 1. If temporalTimeZoneLike is undefined, then
if (temporal_time_zone_like.is_undefined()) {
// a. Let timeZone be ! SystemTimeZone().
time_zone = system_time_zone(vm);
}
// 2. Else,
else {
// a. Let timeZone be ? ToTemporalTimeZone(temporalTimeZoneLike).
time_zone = TRY(to_temporal_time_zone(vm, temporal_time_zone_like));
}
// 3. Let calendar be ? ToTemporalCalendar(calendarLike).
auto* calendar = TRY(to_temporal_calendar(vm, calendar_like));
// 4. Let ns be ! SystemUTCEpochNanoseconds().
auto* ns = system_utc_epoch_nanoseconds(vm);
// 5. Return ? CreateTemporalZonedDateTime(ns, timeZone, calendar).
return create_temporal_zoned_date_time(vm, *ns, *time_zone, *calendar);
}
}

View file

@ -1,42 +0,0 @@
/*
* Copyright (c) 2021-2022, Linus Groh <linusg@serenityos.org>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#pragma once
#include <LibJS/Runtime/Completion.h>
#include <LibJS/Runtime/Object.h>
namespace JS::Temporal {
class Now final : public Object {
JS_OBJECT(Now, Object);
GC_DECLARE_ALLOCATOR(Now);
public:
virtual void initialize(Realm&) override;
virtual ~Now() override = default;
private:
explicit Now(Realm&);
JS_DECLARE_NATIVE_FUNCTION(time_zone);
JS_DECLARE_NATIVE_FUNCTION(instant);
JS_DECLARE_NATIVE_FUNCTION(plain_date_time);
JS_DECLARE_NATIVE_FUNCTION(plain_date_time_iso);
JS_DECLARE_NATIVE_FUNCTION(zoned_date_time);
JS_DECLARE_NATIVE_FUNCTION(zoned_date_time_iso);
JS_DECLARE_NATIVE_FUNCTION(plain_date);
JS_DECLARE_NATIVE_FUNCTION(plain_date_iso);
JS_DECLARE_NATIVE_FUNCTION(plain_time_iso);
};
TimeZone* system_time_zone(VM&);
BigInt* system_utc_epoch_nanoseconds(VM&);
Instant* system_instant(VM&);
ThrowCompletionOr<PlainDateTime*> system_date_time(VM&, Value temporal_time_zone_like, Value calendar_like);
ThrowCompletionOr<ZonedDateTime*> system_zoned_date_time(VM&, Value temporal_time_zone_like, Value calendar_like);
}

View file

@ -1,583 +0,0 @@
/*
* Copyright (c) 2021, Idan Horowitz <idan.horowitz@serenityos.org>
* Copyright (c) 2021-2023, Linus Groh <linusg@serenityos.org>
* Copyright (c) 2024, Shannon Booth <shannon@serenityos.org>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#include <AK/TypeCasts.h>
#include <LibJS/Runtime/AbstractOperations.h>
#include <LibJS/Runtime/Completion.h>
#include <LibJS/Runtime/Date.h>
#include <LibJS/Runtime/GlobalObject.h>
#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/PlainDateConstructor.h>
#include <LibJS/Runtime/Temporal/PlainDateTime.h>
#include <LibJS/Runtime/Temporal/PlainYearMonth.h>
#include <LibJS/Runtime/Temporal/TimeZone.h>
#include <LibJS/Runtime/Temporal/ZonedDateTime.h>
namespace JS::Temporal {
GC_DEFINE_ALLOCATOR(PlainDate);
// 3 Temporal.PlainDate Objects, https://tc39.es/proposal-temporal/#sec-temporal-plaindate-objects
PlainDate::PlainDate(i32 year, u8 month, u8 day, Object& calendar, Object& prototype)
: Object(ConstructWithPrototypeTag::Tag, prototype)
, m_iso_year(year)
, m_iso_month(month)
, m_iso_day(day)
, m_calendar(calendar)
{
}
void PlainDate::visit_edges(Visitor& visitor)
{
Base::visit_edges(visitor);
visitor.visit(m_calendar);
}
// 3.5.2 CreateISODateRecord ( year, month, day ), https://tc39.es/proposal-temporal/#sec-temporal-create-iso-date-record
ISODateRecord create_iso_date_record(i32 year, u8 month, u8 day)
{
// 1. Assert: IsValidISODate(year, month, day) is true.
VERIFY(is_valid_iso_date(year, month, day));
// 2. Return the Record { [[Year]]: year, [[Month]]: month, [[Day]]: day }.
return { .year = year, .month = month, .day = day };
}
// 3.5.1 CreateTemporalDate ( isoYear, isoMonth, isoDay, calendar [ , newTarget ] ), https://tc39.es/proposal-temporal/#sec-temporal-createtemporaldate
ThrowCompletionOr<PlainDate*> create_temporal_date(VM& vm, i32 iso_year, u8 iso_month, u8 iso_day, Object& calendar, FunctionObject const* new_target)
{
auto& realm = *vm.current_realm();
// 1. Assert: isoYear is an integer.
// 2. Assert: isoMonth is an integer.
// 3. Assert: isoDay is an integer.
// 4. Assert: Type(calendar) is Object.
// 5. If IsValidISODate(isoYear, isoMonth, isoDay) is false, throw a RangeError exception.
if (!is_valid_iso_date(iso_year, iso_month, iso_day))
return vm.throw_completion<RangeError>(ErrorType::TemporalInvalidPlainDate);
// 6. If ISODateTimeWithinLimits(isoYear, isoMonth, isoDay, 12, 0, 0, 0, 0, 0) is false, throw a RangeError exception.
if (!iso_date_time_within_limits(iso_year, iso_month, iso_day, 12, 0, 0, 0, 0, 0))
return vm.throw_completion<RangeError>(ErrorType::TemporalInvalidPlainDate);
// 7. If newTarget is not present, set newTarget to %Temporal.PlainDate%.
if (!new_target)
new_target = realm.intrinsics().temporal_plain_date_constructor();
// 8. Let object be ? OrdinaryCreateFromConstructor(newTarget, "%Temporal.PlainDate.prototype%", « [[InitializedTemporalDate]], [[ISOYear]], [[ISOMonth]], [[ISODay]], [[Calendar]] »).
// 9. Set object.[[ISOYear]] to isoYear.
// 10. Set object.[[ISOMonth]] to isoMonth.
// 11. Set object.[[ISODay]] to isoDay.
// 12. Set object.[[Calendar]] to calendar.
auto object = TRY(ordinary_create_from_constructor<PlainDate>(vm, *new_target, &Intrinsics::temporal_plain_date_prototype, iso_year, iso_month, iso_day, calendar));
return object.ptr();
}
// 3.5.2 ToTemporalDate ( item [ , options ] ), https://tc39.es/proposal-temporal/#sec-temporal-totemporaldate
ThrowCompletionOr<PlainDate*> to_temporal_date(VM& vm, Value item, Object const* options)
{
// 1. If options is not present, set options to undefined.
// 2. Assert: Type(options) is Object or Undefined.
// 3. If Type(item) is Object, then
if (item.is_object()) {
auto& item_object = item.as_object();
// a. If item has an [[InitializedTemporalDate]] internal slot, then
if (is<PlainDate>(item_object)) {
// i. Return item.
return static_cast<PlainDate*>(&item_object);
}
// b. If item has an [[InitializedTemporalZonedDateTime]] internal slot, then
if (is<ZonedDateTime>(item_object)) {
auto& zoned_date_time = static_cast<ZonedDateTime&>(item_object);
// i. Perform ? ToTemporalOverflow(options).
(void)TRY(to_temporal_overflow(vm, options));
// ii. Let instant be ! CreateTemporalInstant(item.[[Nanoseconds]]).
auto* instant = create_temporal_instant(vm, zoned_date_time.nanoseconds()).release_value();
// iii. Let plainDateTime be ? BuiltinTimeZoneGetPlainDateTimeFor(item.[[TimeZone]], instant, item.[[Calendar]]).
auto* plain_date_time = TRY(builtin_time_zone_get_plain_date_time_for(vm, &zoned_date_time.time_zone(), *instant, zoned_date_time.calendar()));
// iv. Return ! CreateTemporalDate(plainDateTime.[[ISOYear]], plainDateTime.[[ISOMonth]], plainDateTime.[[ISODay]], plainDateTime.[[Calendar]]).
return create_temporal_date(vm, plain_date_time->iso_year(), plain_date_time->iso_month(), plain_date_time->iso_day(), plain_date_time->calendar());
}
// c. If item has an [[InitializedTemporalDateTime]] internal slot, then
if (is<PlainDateTime>(item_object)) {
auto& date_time_item = static_cast<PlainDateTime&>(item_object);
// i. Perform ? ToTemporalOverflow(options).
(void)TRY(to_temporal_overflow(vm, options));
// ii. Return ! CreateTemporalDate(item.[[ISOYear]], item.[[ISOMonth]], item.[[ISODay]], item.[[Calendar]]).
return create_temporal_date(vm, date_time_item.iso_year(), date_time_item.iso_month(), date_time_item.iso_day(), date_time_item.calendar());
}
// d. Let calendar be ? GetTemporalCalendarWithISODefault(item).
auto* calendar = TRY(get_temporal_calendar_with_iso_default(vm, item_object));
// e. Let fieldNames be ? CalendarFields(calendar, « "day", "month", "monthCode", "year" »).
auto field_names = TRY(calendar_fields(vm, *calendar, { "day"sv, "month"sv, "monthCode"sv, "year"sv }));
// f. Let fields be ? PrepareTemporalFields(item, fieldNames, «»).
auto* fields = TRY(prepare_temporal_fields(vm, item_object, field_names, Vector<StringView> {}));
// g. Return ? CalendarDateFromFields(calendar, fields, options).
return calendar_date_from_fields(vm, *calendar, *fields, options);
}
// 4. Perform ? ToTemporalOverflow(options).
(void)TRY(to_temporal_overflow(vm, options));
// 5. Let string be ? ToString(item).
auto string = TRY(item.to_string(vm));
// 6. Let result be ? ParseTemporalDateString(string).
auto result = TRY(parse_temporal_date_string(vm, string));
// 7. Assert: IsValidISODate(result.[[Year]], result.[[Month]], result.[[Day]]) is true.
VERIFY(is_valid_iso_date(result.year, result.month, result.day));
// 8. Let calendar be ? ToTemporalCalendarWithISODefault(result.[[Calendar]]).
auto* calendar = TRY(to_temporal_calendar_with_iso_default(vm, result.calendar.has_value() ? PrimitiveString::create(vm, *result.calendar) : js_undefined()));
// 9. Return ? CreateTemporalDate(result.[[Year]], result.[[Month]], result.[[Day]], calendar).
return create_temporal_date(vm, result.year, result.month, result.day, *calendar);
}
// 3.5.3 DifferenceISODate ( y1, m1, d1, y2, m2, d2, largestUnit ), https://tc39.es/proposal-temporal/#sec-temporal-differenceisodate
DateDurationRecord difference_iso_date(VM& vm, i32 year1, u8 month1, u8 day1, i32 year2, u8 month2, u8 day2, StringView largest_unit)
{
VERIFY(largest_unit.is_one_of("year"sv, "month"sv, "week"sv, "day"sv));
// 1. If largestUnit is "year" or "month", then
if (largest_unit.is_one_of("year"sv, "month"sv)) {
// a. Let sign be -(! CompareISODate(y1, m1, d1, y2, m2, d2)).
auto sign = -compare_iso_date(year1, month1, day1, year2, month2, day2);
// b. If sign is 0, return ! CreateDateDurationRecord(0, 0, 0, 0).
if (sign == 0)
return create_date_duration_record(0, 0, 0, 0);
// c. Let start be the Record { [[Year]]: y1, [[Month]]: m1, [[Day]]: d1 }.
auto start = ISODateRecord { .year = year1, .month = month1, .day = day1 };
// d. Let end be the Record { [[Year]]: y2, [[Month]]: m2, [[Day]]: d2 }.
auto end = ISODateRecord { .year = year2, .month = month2, .day = day2 };
// e. Let years be end.[[Year]] - start.[[Year]].
double years = end.year - start.year;
// f. Let mid be ! AddISODate(y1, m1, d1, years, 0, 0, 0, "constrain").
auto mid = MUST(add_iso_date(vm, year1, month1, day1, years, 0, 0, 0, "constrain"sv));
// g. Let midSign be -(! CompareISODate(mid.[[Year]], mid.[[Month]], mid.[[Day]], y2, m2, d2)).
auto mid_sign = -compare_iso_date(mid.year, mid.month, mid.day, year2, month2, day2);
// h. If midSign is 0, then
if (mid_sign == 0) {
// i. If largestUnit is "year", return ! CreateDateDurationRecord(years, 0, 0, 0).
if (largest_unit == "year"sv)
return create_date_duration_record(years, 0, 0, 0);
// ii. Return ! CreateDateDurationRecord(0, years × 12, 0, 0).
return create_date_duration_record(0, years * 12, 0, 0);
}
// i. Let months be end.[[Month]] - start.[[Month]].
double months = end.month - start.month;
// j. If midSign is not equal to sign, then
if (mid_sign != sign) {
// i. Set years to years - sign.
years -= sign;
// ii. Set months to months + sign × 12.
months += sign * 12;
}
// k. Set mid to ! AddISODate(y1, m1, d1, years, months, 0, 0, "constrain").
mid = MUST(add_iso_date(vm, year1, month1, day1, years, months, 0, 0, "constrain"sv));
// l. Set midSign to -(! CompareISODate(mid.[[Year]], mid.[[Month]], mid.[[Day]], y2, m2, d2)).
mid_sign = -compare_iso_date(mid.year, mid.month, mid.day, year2, month2, day2);
// m. If midSign is 0, then
if (mid_sign == 0) {
// i. If largestUnit is "year", return ! CreateDateDurationRecord(years, months, 0, 0).
if (largest_unit == "year"sv)
return create_date_duration_record(years, months, 0, 0);
// ii. Return ! CreateDateDurationRecord(0, months + years × 12, 0, 0).
return create_date_duration_record(0, months + years * 12, 0, 0);
}
// n. If midSign is not equal to sign, then
if (mid_sign != sign) {
// i. Set months to months - sign.
months -= sign;
// ii. If months is equal to -sign, then
if (months == -sign) {
// 1. Set years to years - sign.
years -= sign;
// 2. Set months to 11 × sign.
months = 11 * sign;
}
// iii. Set mid to ! AddISODate(y1, m1, d1, years, months, 0, 0, "constrain").
mid = MUST(add_iso_date(vm, year1, month1, day1, years, months, 0, 0, "constrain"sv));
}
double days;
// o. If mid.[[Month]] = end.[[Month]], then
if (mid.month == end.month) {
// i. Assert: mid.[[Year]] = end.[[Year]].
VERIFY(mid.year == end.year);
// ii. Let days be end.[[Day]] - mid.[[Day]].
days = end.day - mid.day;
}
// p. Else if sign < 0, let days be -mid.[[Day]] - (! ISODaysInMonth(end.[[Year]], end.[[Month]]) - end.[[Day]]).
else if (sign < 0) {
days = -mid.day - (iso_days_in_month(end.year, end.month) - end.day);
}
// q. Else, let days be end.[[Day]] + (! ISODaysInMonth(mid.[[Year]], mid.[[Month]]) - mid.[[Day]]).
else {
days = end.day + (iso_days_in_month(mid.year, mid.month) - mid.day);
}
// r. If largestUnit is "month", then
if (largest_unit == "month"sv) {
// i. Set months to months + years × 12.
months += years * 12;
// ii. Set years to 0.
years = 0;
}
// s. Return ! CreateDateDurationRecord(years, months, 0, days).
return create_date_duration_record(years, months, 0, days);
}
// 2. Else,
else {
// a. Assert: largestUnit is "day" or "week".
VERIFY(largest_unit.is_one_of("day"sv, "week"sv));
// b. Let epochDays1 be MakeDay(𝔽(y1), 𝔽(m1 - 1), 𝔽(d1)).
auto epoch_days_1 = make_day(year1, month1 - 1, day1);
// c. Assert: epochDays1 is finite.
VERIFY(isfinite(epoch_days_1));
// d. Let epochDays2 be MakeDay(𝔽(y2), 𝔽(m2 - 1), 𝔽(d2)).
auto epoch_days_2 = make_day(year2, month2 - 1, day2);
// e. Assert: epochDays2 is finite.
VERIFY(isfinite(epoch_days_2));
// f. Let days be (epochDays2) - (epochDays1).
auto days = epoch_days_2 - epoch_days_1;
// g. Let weeks be 0.
double weeks = 0;
// h. If largestUnit is "week", then
if (largest_unit == "week"sv) {
// i. Set weeks to truncate(days / 7).
weeks = trunc(days / 7);
// ii. Set days to remainder(days, 7).
days = fmod(days, 7);
}
// i. Return ! CreateDateDurationRecord(0, 0, weeks, days).
return create_date_duration_record(0, 0, weeks, days);
}
}
// 3.5.4 RegulateISODate ( year, month, day, overflow ), https://tc39.es/proposal-temporal/#sec-temporal-regulateisodate
ThrowCompletionOr<ISODateRecord> regulate_iso_date(VM& vm, double year, double month, double day, StringView overflow)
{
VERIFY(year == trunc(year) && month == trunc(month) && day == trunc(day));
// 1. If overflow is "constrain", then
if (overflow == "constrain"sv) {
// IMPLEMENTATION DEFINED: This is an optimization that allows us to treat this double as normal integer from this point onwards. This
// does not change the exposed behavior as the parent's call to CreateTemporalDate will immediately check that this value is a valid
// ISO value for years: -273975 - 273975, which is a subset of this check.
if (!AK::is_within_range<i32>(year))
return vm.throw_completion<RangeError>(ErrorType::TemporalInvalidPlainDate);
// a. Set month to the result of clamping month between 1 and 12.
month = clamp(month, 1, 12);
// b. Let daysInMonth be ! ISODaysInMonth(year, month).
auto days_in_month = iso_days_in_month(static_cast<i32>(year), static_cast<u8>(month));
// c. Set day to the result of clamping day between 1 and daysInMonth.
day = clamp(day, 1, days_in_month);
// d. Return CreateISODateRecord(year, month, day).
return create_iso_date_record(static_cast<i32>(year), static_cast<u8>(month), static_cast<u8>(day));
}
// 2. Else,
else {
// a. Assert: overflow is "reject".
VERIFY(overflow == "reject"sv);
// IMPLEMENTATION DEFINED: This is an optimization that allows us to treat these doubles as normal integers from this point onwards.
// This does not change the exposed behavior as the call to IsValidISODate will immediately check that these values are valid ISO
// values (for years: -273975 - 273975, for months: 1 - 12, for days: 1 - 31) all of which are subsets of this check.
if (!AK::is_within_range<i32>(year) || !AK::is_within_range<u8>(month) || !AK::is_within_range<u8>(day))
return vm.throw_completion<RangeError>(ErrorType::TemporalInvalidPlainDate);
auto y = static_cast<i32>(year);
auto m = static_cast<u8>(month);
auto d = static_cast<u8>(day);
// b. If IsValidISODate(year, month, day) is false, throw a RangeError exception.
if (!is_valid_iso_date(y, m, d))
return vm.throw_completion<RangeError>(ErrorType::TemporalInvalidPlainDate);
// c. Return the Record { [[Year]]: year, [[Month]]: month, [[Day]]: day }.
return ISODateRecord { .year = y, .month = m, .day = d };
}
}
// 3.5.5 IsValidISODate ( year, month, day ), https://tc39.es/proposal-temporal/#sec-temporal-isvalidisodate
bool is_valid_iso_date(i32 year, u8 month, u8 day)
{
// 1. If month < 1 or month > 12, then
if (month < 1 || month > 12) {
// a. Return false.
return false;
}
// 2. Let daysInMonth be ! ISODaysInMonth(year, month).
auto days_in_month = iso_days_in_month(year, month);
// 3. If day < 1 or day > daysInMonth, then
if (day < 1 || day > days_in_month) {
// a. Return false.
return false;
}
// 4. Return true.
return true;
}
// 3.5.6 DifferenceDate ( calendarRec, one, two, options ), https://tc39.es/proposal-temporal/#sec-temporal-differencedate
ThrowCompletionOr<GC::Ref<Duration>> difference_date(VM& vm, CalendarMethods const& calendar_record, PlainDate const& one, PlainDate const& two, Object const& options)
{
// FIXME: 1. Assert: one.[[Calendar]] and two.[[Calendar]] have been determined to be equivalent as with CalendarEquals.
// FIXME: 2. Assert: options is an ordinary Object.
// 3. Assert: options.[[Prototype]] is null.
VERIFY(!options.prototype());
// 4. Assert: options has a "largestUnit" data property.
VERIFY(MUST(options.has_own_property(vm.names.largestUnit)));
// 5. If one.[[ISOYear]] = two.[[ISOYear]] and one.[[ISOMonth]] = two.[[ISOMonth]] and one.[[ISODay]] = two.[[ISODay]], then
if (one.iso_year() == two.iso_year() && one.iso_month() == two.iso_month() && one.iso_day() == two.iso_day()) {
// a. Return ! CreateTemporalDuration(0, 0, 0, 0, 0, 0, 0, 0, 0, 0).
return MUST(create_temporal_duration(vm, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0));
}
// 6. If ! Get(options, "largestUnit") is "day", then
auto largest_unit = MUST(options.get(vm.names.largestUnit));
if (largest_unit.is_string() && largest_unit.as_string().utf8_string_view() == "day"sv) {
// a. Let days be DaysUntil(one, two).
auto days = days_until(one, two);
// b. Return ! CreateTemporalDuration(0, 0, 0, days, 0, 0, 0, 0, 0, 0).
return MUST(create_temporal_duration(vm, 0, 0, 0, days, 0, 0, 0, 0, 0, 0));
}
// 7. Return ? CalendarDateUntil(calendarRec, one, two, options).
return TRY(calendar_date_until(vm, calendar_record, Value { &one }, Value { &two }, options));
}
// 3.5.6 BalanceISODate ( year, month, day ), https://tc39.es/proposal-temporal/#sec-temporal-balanceisodate
ISODateRecord balance_iso_date(double year, double month, double day)
{
// 1. Let epochDays be MakeDay(𝔽(year), 𝔽(month - 1), 𝔽(day)).
auto epoch_days = make_day(year, month - 1, day);
// 2. Assert: epochDays is finite.
VERIFY(isfinite(epoch_days));
// 3. Let ms be MakeDate(epochDays, +0𝔽).
auto ms = make_date(epoch_days, 0);
// 4. Return CreateISODateRecord((YearFromTime(ms)), (MonthFromTime(ms)) + 1, (DateFromTime(ms))).
return create_iso_date_record(year_from_time(ms), static_cast<u8>(month_from_time(ms) + 1), date_from_time(ms));
}
// 3.5.7 PadISOYear ( y ), https://tc39.es/proposal-temporal/#sec-temporal-padisoyear
ThrowCompletionOr<String> pad_iso_year(VM& vm, i32 y)
{
// 1. Assert: y is an integer.
// 2. If y ≥ 0 and y ≤ 9999, then
if (y >= 0 && y <= 9999) {
// a. Return ToZeroPaddedDecimalString(y, 4).
return TRY_OR_THROW_OOM(vm, String::formatted("{:04}", y));
}
// 3. If y > 0, let yearSign be "+"; otherwise, let yearSign be "-".
auto year_sign = y > 0 ? '+' : '-';
// 4. Let year be ToZeroPaddedDecimalString(abs(y), 6).
// 5. Return the string-concatenation of yearSign and year.
return TRY_OR_THROW_OOM(vm, String::formatted("{}{:06}", year_sign, abs(y)));
}
// 3.5.8 TemporalDateToString ( temporalDate, showCalendar ), https://tc39.es/proposal-temporal/#sec-temporal-temporaldatetostring
ThrowCompletionOr<String> temporal_date_to_string(VM& vm, PlainDate& temporal_date, StringView show_calendar)
{
// 1. Assert: Type(temporalDate) is Object.
// 2. Assert: temporalDate has an [[InitializedTemporalDate]] internal slot.
// 3. Let year be ! PadISOYear(temporalDate.[[ISOYear]]).
auto year = MUST_OR_THROW_OOM(pad_iso_year(vm, temporal_date.iso_year()));
// 4. Let month be ToZeroPaddedDecimalString(monthDay.[[ISOMonth]], 2).
auto month = TRY_OR_THROW_OOM(vm, String::formatted("{:02}", temporal_date.iso_month()));
// 5. Let day be ToZeroPaddedDecimalString(monthDay.[[ISODay]], 2).
auto day = TRY_OR_THROW_OOM(vm, String::formatted("{:02}", temporal_date.iso_day()));
// 6. Let calendar be ? MaybeFormatCalendarAnnotation(temporalDate.[[Calendar]], showCalendar).
auto calendar = TRY(maybe_format_calendar_annotation(vm, &temporal_date.calendar(), show_calendar));
// 7. Return the string-concatenation of year, the code unit 0x002D (HYPHEN-MINUS), month, the code unit 0x002D (HYPHEN-MINUS), day, and calendar.
return TRY_OR_THROW_OOM(vm, String::formatted("{}-{}-{}{}", year, month, day, calendar));
}
// 3.5.9 AddISODate ( year, month, day, years, months, weeks, days, overflow ), https://tc39.es/proposal-temporal/#sec-temporal-addisodate
ThrowCompletionOr<ISODateRecord> add_iso_date(VM& vm, i32 year, u8 month, u8 day, double years, double months, double weeks, double days, StringView overflow)
{
// 1. Assert: year, month, day, years, months, weeks, and days are integers.
VERIFY(years == trunc(years) && months == trunc(months) && weeks == trunc(weeks) && days == trunc(days));
// 2. Assert: overflow is either "constrain" or "reject".
VERIFY(overflow == "constrain"sv || overflow == "reject"sv);
// 3. Let intermediate be ! BalanceISOYearMonth(year + years, month + months).
auto intermediate_year_month = balance_iso_year_month(year + years, month + months);
// 4. Let intermediate be ? RegulateISODate(intermediate.[[Year]], intermediate.[[Month]], day, overflow).
auto intermediate = TRY(regulate_iso_date(vm, intermediate_year_month.year, intermediate_year_month.month, day, overflow));
// 5. Set days to days + 7 × weeks.
days += 7 * weeks;
// 6. Let d be intermediate.[[Day]] + days.
auto d = intermediate.day + days;
// 7. Return BalanceISODate(intermediate.[[Year]], intermediate.[[Month]], d).
return balance_iso_date(intermediate.year, intermediate.month, d);
}
// 3.5.10 CompareISODate ( y1, m1, d1, y2, m2, d2 ), https://tc39.es/proposal-temporal/#sec-temporal-compareisodate
i8 compare_iso_date(i32 year1, u8 month1, u8 day1, i32 year2, u8 month2, u8 day2)
{
// 1. Assert: y1, m1, d1, y2, m2, and d2 are integers.
// 2. If y1 > y2, return 1.
if (year1 > year2)
return 1;
// 3. If y1 < y2, return -1.
if (year1 < year2)
return -1;
// 4. If m1 > m2, return 1.
if (month1 > month2)
return 1;
// 5. If m1 < m2, return -1.
if (month1 < month2)
return -1;
// 6. If d1 > d2, return 1.
if (day1 > day2)
return 1;
// 7. If d1 < d2, return -1.
if (day1 < day2)
return -1;
// 8. Return 0.
return 0;
}
// 3.5.11 DifferenceTemporalPlainDate ( operation, temporalDate, other, options ), https://tc39.es/proposal-temporal/#sec-temporal-differencetemporalplaindate
ThrowCompletionOr<GC::Ref<Duration>> difference_temporal_plain_date(VM& vm, DifferenceOperation operation, PlainDate& temporal_date, Value other_value, Value options)
{
// 1. If operation is SINCE, let sign be -1. Otherwise, let sign be 1.
i8 sign = operation == DifferenceOperation::Since ? -1 : 1;
// 2. Set other to ? ToTemporalDate(other).
auto* other = TRY(to_temporal_date(vm, other_value));
// 3. If ? CalendarEquals(temporalDate.[[Calendar]], other.[[Calendar]]) is false, throw a RangeError exception.
if (!TRY(calendar_equals(vm, temporal_date.calendar(), other->calendar())))
return vm.throw_completion<RangeError>(ErrorType::TemporalDifferentCalendars);
// 4. Let resolvedOptions be ? SnapshotOwnProperties(? GetOptionsObject(options), null).
auto resolved_options = TRY(TRY(get_options_object(vm, options))->snapshot_own_properties(vm, nullptr));
// 5. Let settings be ? GetDifferenceSettings(operation, resolvedOptions, DATE, « », "day", "day").
auto settings = TRY(get_difference_settings(vm, operation, resolved_options, UnitGroup::Date, {}, { "day"sv }, "day"sv));
// 6. If temporalDate.[[ISOYear]] = other.[[ISOYear]], and temporalDate.[[ISOMonth]] = other.[[ISOMonth]], and temporalDate.[[ISODay]] = other.[[ISODay]], then
if (temporal_date.iso_year() == other->iso_year() && temporal_date.iso_month() == other->iso_month() && temporal_date.iso_day() == other->iso_day()) {
// a. Return ! CreateTemporalDuration(0, 0, 0, 0, 0, 0, 0, 0, 0, 0).
return MUST(create_temporal_duration(vm, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0));
}
// 7. Let calendarRec be ? CreateCalendarMethodsRecord(temporalDate.[[Calendar]], « DATE-ADD, DATE-UNTIL »).
// FIXME: The type of calendar in PlainDate does not align with latest spec
auto calendar_record = TRY(create_calendar_methods_record(vm, GC::Ref<Object> { temporal_date.calendar() }, { { CalendarMethod::DateAdd, CalendarMethod::DateUntil } }));
// 8. Perform ! CreateDataPropertyOrThrow(resolvedOptions, "largestUnit", settings.[[LargestUnit]]).
MUST(resolved_options->create_data_property_or_throw(vm.names.largestUnit, PrimitiveString::create(vm, settings.largest_unit)));
// 9. Let result be ? DifferenceDate(calendarRec, temporalDate, other, resolvedOptions).
auto result = TRY(difference_date(vm, calendar_record, temporal_date, *other, resolved_options));
// 10. If settings.[[SmallestUnit]] is "day" and settings.[[RoundingIncrement]] = 1, let roundingGranularityIsNoop be true; else let roundingGranularityIsNoop be false.
bool rounding_granularity_is_noop = settings.smallest_unit == "day"sv && settings.rounding_increment == 1;
// 11. If roundingGranularityIsNoop is false, then
if (!rounding_granularity_is_noop) {
// a. Let roundRecord be ? RoundDuration(result.[[Years]], result.[[Months]], result.[[Weeks]], result.[[Days]], ZeroTimeDuration(), settings.[[RoundingIncrement]], settings.[[SmallestUnit]], settings.[[RoundingMode]], temporalDate, calendarRec).
auto round_record = TRY(round_duration(vm, result->years(), result->months(), result->weeks(), result->days(), 0, 0, 0, 0, 0, 0, settings.rounding_increment, settings.smallest_unit, settings.rounding_mode, &temporal_date, calendar_record)).duration_record;
// FIXME: b. Let roundResult be roundRecord.[[NormalizedDuration]].
// FIXME: c. Set result to ? BalanceDateDurationRelative(roundResult.[[Years]], roundResult.[[Months]], roundResult.[[Weeks]], roundResult.[[Days]], settings.[[LargestUnit]], settings.[[SmallestUnit]], temporalDate, calendarRec).
result = MUST(create_temporal_duration(vm, round_record.years, round_record.months, round_record.weeks, round_record.days, 0, 0, 0, 0, 0, 0));
}
// 16. Return ! CreateTemporalDuration(sign × result.[[Years]], sign × result.[[Months]], sign × result.[[Weeks]], sign × result.[[Days]], 0, 0, 0, 0, 0, 0).
return MUST(create_temporal_duration(vm, sign * result->years(), sign * result->months(), sign * result->weeks(), sign * result->days(), 0, 0, 0, 0, 0, 0));
}
}

View file

@ -1,64 +0,0 @@
/*
* Copyright (c) 2021, Idan Horowitz <idan.horowitz@serenityos.org>
* Copyright (c) 2021-2023, Linus Groh <linusg@serenityos.org>
* Copyright (c) 2024, Shannon Booth <shannon@serenityos.org>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#pragma once
#include <LibJS/Forward.h>
#include <LibJS/Runtime/Completion.h>
#include <LibJS/Runtime/Object.h>
#include <LibJS/Runtime/Temporal/AbstractOperations.h>
namespace JS::Temporal {
class PlainDate final : public Object {
JS_OBJECT(PlainDate, Object);
GC_DECLARE_ALLOCATOR(PlainDate);
public:
virtual ~PlainDate() override = default;
[[nodiscard]] i32 iso_year() const { return m_iso_year; }
[[nodiscard]] u8 iso_month() const { return m_iso_month; }
[[nodiscard]] u8 iso_day() const { return m_iso_day; }
[[nodiscard]] Object const& calendar() const { return m_calendar; }
[[nodiscard]] Object& calendar() { return m_calendar; }
private:
PlainDate(i32 iso_year, u8 iso_month, u8 iso_day, Object& calendar, Object& prototype);
virtual void visit_edges(Visitor&) override;
// 3.4 Properties of Temporal.PlainDate Instances, https://tc39.es/proposal-temporal/#sec-properties-of-temporal-plaindate-instances
i32 m_iso_year { 0 }; // [[ISOYear]]
u8 m_iso_month { 1 }; // [[ISOMonth]]
u8 m_iso_day { 1 }; // [[ISODay]]
GC::Ref<Object> m_calendar; // [[Calendar]]
};
// 3.5.1 ISO Date Records, https://tc39.es/proposal-temporal/#sec-temporal-iso-date-records
struct ISODateRecord {
i32 year;
u8 month;
u8 day;
};
ISODateRecord create_iso_date_record(i32 year, u8 month, u8 day);
ThrowCompletionOr<PlainDate*> create_temporal_date(VM&, i32 iso_year, u8 iso_month, u8 iso_day, Object& calendar, FunctionObject const* new_target = nullptr);
ThrowCompletionOr<PlainDate*> to_temporal_date(VM&, Value item, Object const* options = nullptr);
DateDurationRecord difference_iso_date(VM&, i32 year1, u8 month1, u8 day1, i32 year2, u8 month2, u8 day2, StringView largest_unit);
ThrowCompletionOr<ISODateRecord> regulate_iso_date(VM&, double year, double month, double day, StringView overflow);
bool is_valid_iso_date(i32 year, u8 month, u8 day);
ISODateRecord balance_iso_date(double year, double month, double day);
ThrowCompletionOr<String> pad_iso_year(VM&, i32 y);
ThrowCompletionOr<String> temporal_date_to_string(VM&, PlainDate&, StringView show_calendar);
ThrowCompletionOr<ISODateRecord> add_iso_date(VM&, i32 year, u8 month, u8 day, double years, double months, double weeks, double days, StringView overflow);
i8 compare_iso_date(i32 year1, u8 month1, u8 day1, i32 year2, u8 month2, u8 day2);
ThrowCompletionOr<GC::Ref<Duration>> difference_temporal_plain_date(VM&, DifferenceOperation, PlainDate&, Value other, Value options);
ThrowCompletionOr<GC::Ref<Duration>> difference_date(VM& vm, CalendarMethods const&, PlainDate const& one, PlainDate const& two, Object const& options);
}

View file

@ -1,111 +0,0 @@
/*
* Copyright (c) 2021, Idan Horowitz <idan.horowitz@serenityos.org>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#include <AK/Checked.h>
#include <AK/TypeCasts.h>
#include <LibJS/Runtime/GlobalObject.h>
#include <LibJS/Runtime/Temporal/AbstractOperations.h>
#include <LibJS/Runtime/Temporal/Calendar.h>
#include <LibJS/Runtime/Temporal/PlainDate.h>
#include <LibJS/Runtime/Temporal/PlainDateConstructor.h>
namespace JS::Temporal {
GC_DEFINE_ALLOCATOR(PlainDateConstructor);
// 3.1 The Temporal.PlainDate Constructor, https://tc39.es/proposal-temporal/#sec-temporal-plaindate-constructor
PlainDateConstructor::PlainDateConstructor(Realm& realm)
: NativeFunction(realm.vm().names.PlainDate.as_string(), realm.intrinsics().function_prototype())
{
}
void PlainDateConstructor::initialize(Realm& realm)
{
Base::initialize(realm);
auto& vm = this->vm();
// 3.2.1 Temporal.PlainDate.prototype, https://tc39.es/proposal-temporal/#sec-temporal.plaindate.prototype
define_direct_property(vm.names.prototype, realm.intrinsics().temporal_plain_date_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(3), Attribute::Configurable);
}
// 3.1.1 Temporal.PlainDate ( isoYear, isoMonth, isoDay [ , calendarLike ] ), https://tc39.es/proposal-temporal/#sec-temporal.plaindate
ThrowCompletionOr<Value> PlainDateConstructor::call()
{
auto& vm = this->vm();
// 1. If NewTarget is undefined, throw a TypeError exception.
return vm.throw_completion<TypeError>(ErrorType::ConstructorWithoutNew, "Temporal.PlainDate");
}
// 3.1.1 Temporal.PlainDate ( isoYear, isoMonth, isoDay [ , calendarLike ] ), https://tc39.es/proposal-temporal/#sec-temporal.plaindate
ThrowCompletionOr<GC::Ref<Object>> PlainDateConstructor::construct(FunctionObject& new_target)
{
auto& vm = this->vm();
// 2. Let y be ? ToIntegerWithTruncation(isoYear).
auto y = TRY(to_integer_with_truncation(vm, vm.argument(0), ErrorType::TemporalInvalidPlainDate));
// 3. Let m be ? ToIntegerWithTruncation(isoMonth).
auto m = TRY(to_integer_with_truncation(vm, vm.argument(1), ErrorType::TemporalInvalidPlainDate));
// 4. Let d be ? ToIntegerWithTruncation(isoDay).
auto d = TRY(to_integer_with_truncation(vm, vm.argument(2), ErrorType::TemporalInvalidPlainDate));
// 5. Let calendar be ? ToTemporalCalendarWithISODefault(calendarLike).
auto* calendar = TRY(to_temporal_calendar_with_iso_default(vm, vm.argument(3)));
// IMPLEMENTATION DEFINED: This is an optimization that allows us to treat these doubles as normal integers from this point onwards.
// This does not change the exposed behavior as the call to CreateTemporalDate will immediately check that these values are valid
// ISO values (for years: -273975 - 273975, for months: 1 - 12, for days: 1 - 31) all of which are subsets of this check.
if (!AK::is_within_range<i32>(y) || !AK::is_within_range<u8>(m) || !AK::is_within_range<u8>(d))
return vm.throw_completion<RangeError>(ErrorType::TemporalInvalidPlainDate);
// 6. Return ? CreateTemporalDate(y, m, d, calendar, NewTarget).
return *TRY(create_temporal_date(vm, y, m, d, *calendar, &new_target));
}
// 3.2.2 Temporal.PlainDate.from ( item [ , options ] ), https://tc39.es/proposal-temporal/#sec-temporal.plaindate.from
JS_DEFINE_NATIVE_FUNCTION(PlainDateConstructor::from)
{
// 1. Set options to ? GetOptionsObject(options).
auto const* options = TRY(get_options_object(vm, vm.argument(1)));
auto item = vm.argument(0);
// 2. If Type(item) is Object and item has an [[InitializedTemporalDate]] internal slot, then
if (item.is_object() && is<PlainDate>(item.as_object())) {
auto& plain_date_item = static_cast<PlainDate&>(item.as_object());
// a. Perform ? ToTemporalOverflow(options).
(void)TRY(to_temporal_overflow(vm, options));
// b. Return ! CreateTemporalDate(item.[[ISOYear]], item.[[ISOMonth]], item.[[ISODay]], item.[[Calendar]]).
return MUST(create_temporal_date(vm, plain_date_item.iso_year(), plain_date_item.iso_month(), plain_date_item.iso_day(), plain_date_item.calendar()));
}
// 3. Return ? ToTemporalDate(item, options).
return TRY(to_temporal_date(vm, item, options));
}
// 3.2.3 Temporal.PlainDate.compare ( one, two ), https://tc39.es/proposal-temporal/#sec-temporal.plaindate.compare
JS_DEFINE_NATIVE_FUNCTION(PlainDateConstructor::compare)
{
// 1. Set one to ? ToTemporalDate(one).
auto* one = TRY(to_temporal_date(vm, vm.argument(0)));
// 2. Set two to ? ToTemporalDate(two).
auto* two = TRY(to_temporal_date(vm, vm.argument(1)));
// 3. Return 𝔽(! CompareISODate(one.[[ISOYear]], one.[[ISOMonth]], one.[[ISODay]], two.[[ISOYear]], two.[[ISOMonth]], two.[[ISODay]])).
return Value(compare_iso_date(one->iso_year(), one->iso_month(), one->iso_day(), two->iso_year(), two->iso_month(), two->iso_day()));
}
}

View file

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

Some files were not shown because too many files have changed in this diff Show more