Skip to content

Commit

Permalink
Bug 1856338 - Part 1: Implement Temporal.TimeZone.prototype.equals. r…
Browse files Browse the repository at this point in the history
…=mgaudet

Implement the changes from <tc39/proposal-temporal#2633>.

Differential Revision: https://phabricator.services.mozilla.com/D189772
  • Loading branch information
anba committed Nov 6, 2023
1 parent 91bbb88 commit 2a60266
Show file tree
Hide file tree
Showing 5 changed files with 238 additions and 41 deletions.
94 changes: 83 additions & 11 deletions js/src/builtin/temporal/TemporalParser.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -884,6 +884,8 @@ class TemporalParser final {

mozilla::Result<TimeZoneUTCOffset, ParserError> timeZoneUTCOffsetName();

mozilla::Result<TimeZoneAnnotation, ParserError> timeZoneIdentifier();

mozilla::Result<TimeZoneAnnotation, ParserError> timeZoneAnnotation();

bool timeZoneIANANameComponent();
Expand Down Expand Up @@ -914,6 +916,8 @@ class TemporalParser final {
mozilla::Result<ZonedDateTimeString, ParserError>
parseTemporalTimeZoneString();

mozilla::Result<TimeZoneAnnotation, ParserError> parseTimeZoneIdentifier();

mozilla::Result<TimeZoneUTCOffset, ParserError> parseTimeZoneOffsetString();

mozilla::Result<DateTimeUTCOffset, ParserError> parseDateTimeUTCOffset();
Expand Down Expand Up @@ -1297,21 +1301,11 @@ TemporalParser<CharT>::utcOffsetSubMinutePrecision() {

template <typename CharT>
mozilla::Result<TimeZoneAnnotation, ParserError>
TemporalParser<CharT>::timeZoneAnnotation() {
// TimeZoneAnnotation :
// [ AnnotationCriticalFlag? TimeZoneIdentifier ]
//
TemporalParser<CharT>::timeZoneIdentifier() {
// TimeZoneIdentifier :
// TimeZoneIANAName
// TimeZoneUTCOffsetName

if (!character('[')) {
return mozilla::Err(JSMSG_TEMPORAL_PARSER_BRACKET_BEFORE_TIMEZONE);
}

// Skip over the optional critical flag.
annotationCriticalFlag();

TimeZoneAnnotation result = {};
if (hasSign()) {
auto offset = timeZoneUTCOffsetName();
Expand All @@ -1327,6 +1321,27 @@ TemporalParser<CharT>::timeZoneAnnotation() {
result.name = name.unwrap();
}

return result;
}

template <typename CharT>
mozilla::Result<TimeZoneAnnotation, ParserError>
TemporalParser<CharT>::timeZoneAnnotation() {
// TimeZoneAnnotation :
// [ AnnotationCriticalFlag? TimeZoneIdentifier ]

if (!character('[')) {
return mozilla::Err(JSMSG_TEMPORAL_PARSER_BRACKET_BEFORE_TIMEZONE);
}

// Skip over the optional critical flag.
annotationCriticalFlag();

auto result = timeZoneIdentifier();
if (result.isErr()) {
return result.propagateErr();
}

if (!character(']')) {
return mozilla::Err(JSMSG_TEMPORAL_PARSER_BRACKET_AFTER_TIMEZONE);
}
Expand Down Expand Up @@ -1681,6 +1696,63 @@ bool js::temporal::ParseTemporalTimeZoneString(
return true;
}

template <typename CharT>
mozilla::Result<TimeZoneAnnotation, ParserError>
TemporalParser<CharT>::parseTimeZoneIdentifier() {
auto result = timeZoneIdentifier();
if (result.isErr()) {
return result.propagateErr();
}
if (!reader_.atEnd()) {
return mozilla::Err(JSMSG_TEMPORAL_PARSER_GARBAGE_AFTER_INPUT);
}
return result;
}

/**
* ParseTimeZoneIdentifier ( identifier )
*/
template <typename CharT>
static auto ParseTimeZoneIdentifier(mozilla::Span<const CharT> str) {
TemporalParser<CharT> parser(str);
return parser.parseTimeZoneIdentifier();
}

/**
* ParseTimeZoneIdentifier ( identifier )
*/
static auto ParseTimeZoneIdentifier(Handle<JSLinearString*> str) {
JS::AutoCheckCannotGC nogc;
if (str->hasLatin1Chars()) {
return ParseTimeZoneIdentifier<Latin1Char>(str->latin1Range(nogc));
}
return ParseTimeZoneIdentifier<char16_t>(str->twoByteRange(nogc));
}

/**
* ParseTimeZoneIdentifier ( identifier )
*/
bool js::temporal::ParseTimeZoneIdentifier(
JSContext* cx, Handle<JSString*> str,
MutableHandle<ParsedTimeZone> result) {
Rooted<JSLinearString*> linear(cx, str->ensureLinear(cx));
if (!linear) {
return false;
}

// Steps 1-2.
auto parseResult = ::ParseTimeZoneIdentifier(linear);
if (parseResult.isErr()) {
JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
parseResult.unwrapErr());
return false;
}
auto timeZone = parseResult.unwrap();

// Steps 3-4.
return ParseTimeZoneAnnotation(cx, timeZone, linear, result);
}

template <typename CharT>
mozilla::Result<TimeZoneUTCOffset, ParserError>
TemporalParser<CharT>::parseTimeZoneOffsetString() {
Expand Down
6 changes: 6 additions & 0 deletions js/src/builtin/temporal/TemporalParser.h
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,12 @@ bool ParseTemporalInstantString(JSContext* cx, JS::Handle<JSString*> str,
bool ParseTemporalTimeZoneString(JSContext* cx, JS::Handle<JSString*> str,
JS::MutableHandle<ParsedTimeZone> result);

/**
* ParseTimeZoneIdentifier ( identifier )
*/
bool ParseTimeZoneIdentifier(JSContext* cx, JS::Handle<JSString*> str,
JS::MutableHandle<ParsedTimeZone> result);

/**
* ParseTimeZoneOffsetString ( isoString )
*/
Expand Down
134 changes: 134 additions & 0 deletions js/src/builtin/temporal/TimeZone.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1079,6 +1079,108 @@ JSString* js::temporal::GetOffsetStringFor(
return NewStringCopyN<CanGC>(cx, result, n);
}

/**
* TimeZoneEquals ( one, two )
*/
bool js::temporal::TimeZoneEquals(JSContext* cx, Handle<JSString*> one,
Handle<JSString*> two, bool* equals) {
// Steps 1-3. (Not applicable)

// Step 4.
if (!EqualStrings(cx, one, two, equals)) {
return false;
}
if (*equals) {
return true;
}

// Step 5.
Rooted<ParsedTimeZone> timeZoneOne(cx);
if (!ParseTimeZoneIdentifier(cx, one, &timeZoneOne)) {
return false;
}

// Step 6.
Rooted<ParsedTimeZone> timeZoneTwo(cx);
if (!ParseTimeZoneIdentifier(cx, two, &timeZoneTwo)) {
return false;
}

// Step 7.
if (timeZoneOne.name() && timeZoneTwo.name()) {
// Step 7.a.
Rooted<JSAtom*> validTimeZoneOne(cx);
if (!IsValidTimeZoneName(cx, timeZoneOne.name(), &validTimeZoneOne)) {
return false;
}
if (!validTimeZoneOne) {
*equals = false;
return true;
}

// Step 7.b.
Rooted<JSAtom*> validTimeZoneTwo(cx);
if (!IsValidTimeZoneName(cx, timeZoneTwo.name(), &validTimeZoneTwo)) {
return false;
}
if (!validTimeZoneTwo) {
*equals = false;
return true;
}

// Step 7.c and 9.
Rooted<JSString*> canonicalOne(
cx, CanonicalizeTimeZoneName(cx, validTimeZoneOne));
if (!canonicalOne) {
return false;
}

JSString* canonicalTwo = CanonicalizeTimeZoneName(cx, validTimeZoneTwo);
if (!canonicalTwo) {
return false;
}

return EqualStrings(cx, canonicalOne, canonicalTwo, equals);
}

// Step 8.a.
if (!timeZoneOne.name() && !timeZoneTwo.name()) {
*equals = (timeZoneOne.offset() == timeZoneTwo.offset());
return true;
}

// Step 9.
*equals = false;
return true;
}

/**
* TimeZoneEquals ( one, two )
*/
bool js::temporal::TimeZoneEquals(JSContext* cx, Handle<TimeZoneValue> one,
Handle<TimeZoneValue> two, bool* equals) {
// Step 1.
if (one.isObject() && two.isObject() && one.toObject() == two.toObject()) {
*equals = true;
return true;
}

// Step 2.
Rooted<JSString*> timeZoneOne(cx, ToTemporalTimeZoneIdentifier(cx, one));
if (!timeZoneOne) {
return false;
}

// Step 3.
Rooted<JSString*> timeZoneTwo(cx, ToTemporalTimeZoneIdentifier(cx, two));
if (!timeZoneTwo) {
return false;
}

// Steps 4-9.
return TimeZoneEquals(cx, timeZoneOne, timeZoneTwo, equals);
}

// ES2019 draft rev 0ceb728a1adbffe42b26972a6541fd7f398b1557
// 5.2.5 Mathematical Operations
static inline double PositiveModulo(double dividend, double divisor) {
Expand Down Expand Up @@ -1803,6 +1905,37 @@ static bool TimeZone_from(JSContext* cx, unsigned argc, Value* vp) {
return true;
}

/**
* Temporal.TimeZone.prototype.equals ( other )
*/
static bool TimeZone_equals(JSContext* cx, const CallArgs& args) {
Rooted<TimeZoneValue> timeZone(cx, &args.thisv().toObject());

// FIXME: spec bug - argument needs to be converted to time zone.
Rooted<TimeZoneValue> other(cx);
if (!ToTemporalTimeZone(cx, args.get(0), &other)) {
return false;
}

// Step 3.
bool equals;
if (!TimeZoneEquals(cx, timeZone, other, &equals)) {
return false;
}

args.rval().setBoolean(equals);
return true;
}

/**
* Temporal.TimeZone.prototype.equals ( other )
*/
static bool TimeZone_equals(JSContext* cx, unsigned argc, Value* vp) {
// Steps 1-2.
CallArgs args = CallArgsFromVp(argc, vp);
return CallNonGenericMethod<IsTimeZone, TimeZone_equals>(cx, args);
}

/**
* Temporal.TimeZone.prototype.getOffsetNanosecondsFor ( instant )
*/
Expand Down Expand Up @@ -2224,6 +2357,7 @@ static const JSFunctionSpec TimeZone_methods[] = {
};

static const JSFunctionSpec TimeZone_prototype_methods[] = {
JS_FN("equals", TimeZone_equals, 1, 0),
JS_FN("getOffsetNanosecondsFor", TimeZone_getOffsetNanosecondsFor, 1, 0),
JS_FN("getOffsetStringFor", TimeZone_getOffsetStringFor, 1, 0),
JS_FN("getPlainDateTimeFor", TimeZone_getPlainDateTimeFor, 1, 0),
Expand Down
12 changes: 12 additions & 0 deletions js/src/builtin/temporal/TimeZone.h
Original file line number Diff line number Diff line change
Expand Up @@ -286,6 +286,18 @@ JSObject* ToTemporalTimeZoneObject(JSContext* cx,
JSString* ToTemporalTimeZoneIdentifier(JSContext* cx,
JS::Handle<TimeZoneValue> timeZone);

/**
* TimeZoneEquals ( one, two )
*/
bool TimeZoneEquals(JSContext* cx, JS::Handle<JSString*> one,
JS::Handle<JSString*> two, bool* equals);

/**
* TimeZoneEquals ( one, two )
*/
bool TimeZoneEquals(JSContext* cx, JS::Handle<TimeZoneValue> one,
JS::Handle<TimeZoneValue> two, bool* equals);

/**
* GetPlainDateTimeFor ( timeZone, instant, calendar )
*/
Expand Down
33 changes: 3 additions & 30 deletions js/src/builtin/temporal/ZonedDateTime.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1050,33 +1050,6 @@ bool js::temporal::DifferenceZonedDateTime(JSContext* cx, const Instant& ns1,
largestUnit, nullptr, result);
}

/**
* TimeZoneEquals ( one, two )
*/
static bool TimeZoneEquals(JSContext* cx, Handle<TimeZoneValue> one,
Handle<TimeZoneValue> two, bool* equals) {
// Step 1.
if (one.isObject() && two.isObject() && one.toObject() == two.toObject()) {
*equals = true;
return true;
}

// Step 2.
Rooted<JSString*> timeZoneOne(cx, ToTemporalTimeZoneIdentifier(cx, one));
if (!timeZoneOne) {
return false;
}

// Step 3.
JSString* timeZoneTwo = ToTemporalTimeZoneIdentifier(cx, two);
if (!timeZoneTwo) {
return false;
}

// Steps 4-5.
return EqualStrings(cx, timeZoneOne, timeZoneTwo, equals);
}

/**
* TimeZoneEquals ( one, two )
*/
Expand All @@ -1094,14 +1067,14 @@ static bool TimeZoneEqualsOrThrow(JSContext* cx, Handle<TimeZoneValue> one,
}

// Step 3.
JSString* timeZoneTwo = ToTemporalTimeZoneIdentifier(cx, two);
Rooted<JSString*> timeZoneTwo(cx, ToTemporalTimeZoneIdentifier(cx, two));
if (!timeZoneTwo) {
return false;
}

// Steps 4-5.
// Steps 4-9.
bool equals;
if (!EqualStrings(cx, timeZoneOne, timeZoneTwo, &equals)) {
if (!TimeZoneEquals(cx, timeZoneOne, timeZoneTwo, &equals)) {
return false;
}
if (equals) {
Expand Down

0 comments on commit 2a60266

Please sign in to comment.