Skip to content

Commit

Permalink
Add equals() method
Browse files Browse the repository at this point in the history
This adds an equals() method to Absolute, Date, DateTime, MonthDay, Time,
and YearMonth. For types with a compare() method, `a.equals(b)` is
equivalent to `Temporal.X.compare(a, b) === 0`

Duration doesn't have an equals() method at this time because, does
`{ minutes: 1, seconds: 30 }` equal `{ seconds: 90 }`? TimeZone doesn't
have one either because it's simple enough to compare the .name
property.
  • Loading branch information
ptomato committed May 22, 2020
1 parent a92a3c1 commit 5f54668
Show file tree
Hide file tree
Showing 43 changed files with 754 additions and 21 deletions.
21 changes: 21 additions & 0 deletions docs/absolute.md
Original file line number Diff line number Diff line change
Expand Up @@ -334,6 +334,27 @@ epoch.inTimeZone(utc).difference(billion.inTimeZone(utc), { largestUnit: 'years'
// => P31Y8M8DT1H46M40S
```

### absolute.**equals**(_other_: Temporal.Absolute) : boolean

**Parameters:**
- `other` (`Temporal.Absolute`): Another time to compare.

**Returns:** `true` if `absolute` and `other` are equal, or `false` if not.

Compares two `Temporal.Absolute` objects for equality.

This function exists because it's not possible to compare using `absolute == other` or `absolute === other`, due to ambiguity in the primitive representation and between Temporal types.

If you don't need to know the order in which the two times occur, then this function may be less typing and more efficient than `Temporal.Absolute.compare`.

Example usage:
```javascript
one = Temporal.Absolute.fromEpochSeconds(1.0e9);
two = Temporal.Absolute.fromEpochSeconds(1.1e9);
one.equals(two) // => false
one.equals(one) // => true
```

### absolute.**toString**(_timeZone_?: Temporal.TimeZone | string) : string

**Parameters:**
Expand Down
23 changes: 23 additions & 0 deletions docs/date.md
Original file line number Diff line number Diff line change
Expand Up @@ -371,6 +371,29 @@ date.withTime(midnight).difference(other.withTime(midnight), { largestUnit: 'hou
// => PT109032H
```

### date.**equals**(_other_: Temporal.Date) : boolean

**Parameters:**
- `other` (`Temporal.Date`): Another date to compare.

**Returns:** `true` if `date` and `other` are equal, or `false` if not.

Compares two `Temporal.Date` objects for equality.

This function exists because it's not possible to compare using `date == other` or `date === other`, due to ambiguity in the primitive representation and between Temporal types.

If you don't need to know the order in which the two dates occur, then this function may be less typing and more efficient than `Temporal.Date.compare`.

Note that this function will return `true` if the two dates are equal, even if they are expressed in different calendar systems.

Example usage:
```javascript
date = Temporal.Date.from('2006-08-24');
other = Temporal.Date.from('2019-01-31');
date.equals(other) // => false
date.equals(date) // => true
```

### date.**toString**() : string

**Returns:** a string in the ISO 8601 date format representing `date`.
Expand Down
23 changes: 23 additions & 0 deletions docs/datetime.md
Original file line number Diff line number Diff line change
Expand Up @@ -429,6 +429,29 @@ feb1.difference(mar1); // => P29D
feb1.difference(mar1, { largestUnit: 'months' }); // => P1M
```

### datetime.**equals**(_other_: Temporal.DateTime) : boolean

**Parameters:**
- `other` (`Temporal.DateTime`): Another date/time to compare.

**Returns:** `true` if `datetime` and `other` are equal, or `false` if not.

Compares two `Temporal.DateTime` objects for equality.

This function exists because it's not possible to compare using `datetime == other` or `datetime === other`, due to ambiguity in the primitive representation and between Temporal types.

If you don't need to know the order in which the two dates/times occur, then this function may be less typing and more efficient than `Temporal.DateTime.compare`.

Note that this function will return `true` if the two date/times are equal, even if they are expressed in different calendar systems.

Example usage:
```javascript
dt1 = Temporal.DateTime.from('1995-12-07T03:24:30.000003500');
dt2 = Temporal.DateTime.from('2019-01-31T15:30');
dt1.equals(dt2) // => false
dt1.equals(dt1) // => true
```

### datetime.**toString**() : string

**Returns:** a string in the ISO 8601 date format representing `datetime`.
Expand Down
21 changes: 21 additions & 0 deletions docs/monthday.md
Original file line number Diff line number Diff line change
Expand Up @@ -150,6 +150,27 @@ md.with({ day: 31 }) // => 11-30
Temporal.MonthDay.from('02-01').with({ day: 31 }); // => 02-29
```

### monthDay.**equals**(_other_: Temporal.MonthDay) : boolean

**Parameters:**
- `other` (`Temporal.MonthDay`): Another month-day to compare.

**Returns:** `true` if `monthDay` and `other` are equal, or `false` if not.

Compares two `Temporal.MonthDay` objects for equality.

This function exists because it's not possible to compare using `monthDay == other` or `monthDay === other`, due to ambiguity in the primitive representation and between Temporal types.

Note that two `Temporal.MonthDay`s expressed in different calendar systems can never be equal, because it's impossible to tell whether they fall on the same day without knowing the year.

Example usage:
```javascript
dt1 = Temporal.DateTime.from('1995-12-07T03:24:30.000003500');
dt2 = Temporal.DateTime.from('2019-01-31T15:30');
dt1.equals(dt2) // => false
dt1.equals(dt1) // => true
```

### monthDay.**toString**() : string

**Returns:** a string in the ISO 8601 date format representing `monthDay`.
Expand Down
21 changes: 21 additions & 0 deletions docs/time.md
Original file line number Diff line number Diff line change
Expand Up @@ -270,6 +270,27 @@ time.difference(Temporal.Time.from('20:13:20.971398099')) // => PT34M11.9030518
Temporal.Time.from('01:00').difference(Temporal.Time.from('23:00')) // => P2H
```

### time.**equals**(_other_: Temporal.Time) : boolean

**Parameters:**
- `other` (`Temporal.Time`): Another time to compare.

**Returns:** `true` if `time` and `other` are equal, or `false` if not.

Compares two `Temporal.Time` objects for equality.

This function exists because it's not possible to compare using `time == other` or `time === other`, due to ambiguity in the primitive representation and between Temporal types.

If you don't need to know the order in which the two dates occur, then this function may be less typing and more efficient than `Temporal.Time.compare`.

Example usage:
```javascript
time = Temporal.Time.from('19:39:09.068346205');
other = Temporal.Time.from('20:13:20.971398099');
time.equals(other) // => false
time.equals(time) // => true
```

### time.**toString**() : string

**Returns:** a string in the ISO 8601 time format representing `time`.
Expand Down
23 changes: 23 additions & 0 deletions docs/yearmonth.md
Original file line number Diff line number Diff line change
Expand Up @@ -313,6 +313,29 @@ ym.difference(other, { largestUnit: 'months' }) // => P154M
ym.withDay(1).difference(other.withDay(1), { largestUnit: 'days' }); // => P4687D
```

### yearMonth.**equals**(_other_: Temporal.YearMonth) : boolean

**Parameters:**
- `other` (`Temporal.YearMonth`): Another month to compare.

**Returns:** `true` if `yearMonth` and `other` are equal, or `false` if not.

Compares two `Temporal.YearMonth` objects for equality.

This function exists because it's not possible to compare using `yearMonth == other` or `yearMonth === other`, due to ambiguity in the primitive representation and between Temporal types.

Note that equality of two months from different calendar systems only makes sense in a few cases, such as when the two calendar systems both use the Gregorian year.

Even if you are using the same calendar system, if you don't need to know the order in which the two months occur, then this function may be less typing and more efficient than `Temporal.YearMonth.compare`.

Example usage:
```javascript
ym = Temporal.YearMonth.from('2019-06');
other = Temporal.YearMonth.from('2006-08');
ym.equals(other) // => false
ym.equals(ym) // => true
```

### yearMonth.**toString**() : string

**Returns:** a string in the ISO 8601 date format representing `yearMonth`.
Expand Down
7 changes: 7 additions & 0 deletions polyfill/lib/absolute.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -118,6 +118,13 @@ export class Absolute {
);
return new Duration(0, 0, days, hours, minutes, seconds, milliseconds, microseconds, nanoseconds);
}
equals(other) {
if (!ES.IsTemporalAbsolute(this)) throw new TypeError('invalid receiver');
if (!ES.IsTemporalAbsolute(other)) throw new TypeError('invalid Absolute object');
const one = GetSlot(this, EPOCHNANOSECONDS);
const two = GetSlot(other, EPOCHNANOSECONDS);
return bigInt(one).equals(two);
}
toString(temporalTimeZoneLike = 'UTC') {
if (!ES.IsTemporalAbsolute(this)) throw new TypeError('invalid receiver');
const timeZone = ES.ToTemporalTimeZone(temporalTimeZoneLike);
Expand Down
10 changes: 10 additions & 0 deletions polyfill/lib/date.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -131,6 +131,16 @@ export class Date {
const Duration = GetIntrinsic('%Temporal.Duration%');
return new Duration(years, months, days, 0, 0, 0, 0, 0, 0);
}
equals(other) {
if (!ES.IsTemporalDate(this)) throw new TypeError('invalid receiver');
if (!ES.IsTemporalDate(other)) throw new TypeError('invalid Date object');
for (const slot of [YEAR, MONTH, DAY]) {
const val1 = GetSlot(this, slot);
const val2 = GetSlot(other, slot);
if (val1 !== val2) return false;
}
return true;
}
toString() {
if (!ES.IsTemporalDate(this)) throw new TypeError('invalid receiver');
let year = ES.ISOYearString(GetSlot(this, YEAR));
Expand Down
10 changes: 10 additions & 0 deletions polyfill/lib/datetime.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -259,6 +259,16 @@ export class DateTime {
const Duration = GetIntrinsic('%Temporal.Duration%');
return new Duration(years, months, days, hours, minutes, seconds, milliseconds, microseconds, nanoseconds);
}
equals(other) {
if (!ES.IsTemporalDateTime(this)) throw new TypeError('invalid receiver');
if (!ES.IsTemporalDateTime(other)) throw new TypeError('invalid Date object');
for (const slot of [YEAR, MONTH, DAY, HOUR, MINUTE, SECOND, MILLISECOND, MICROSECOND, NANOSECOND]) {
const val1 = GetSlot(this, slot);
const val2 = GetSlot(other, slot);
if (val1 !== val2) return false;
}
return true;
}
toString() {
if (!ES.IsTemporalDateTime(this)) throw new TypeError('invalid receiver');
let year = ES.ISOYearString(GetSlot(this, YEAR));
Expand Down
10 changes: 10 additions & 0 deletions polyfill/lib/monthday.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,16 @@ export class MonthDay {
if (!ES.IsTemporalMonthDay(result)) throw new TypeError('invalid result');
return result;
}
equals(other) {
if (!ES.IsTemporalMonthDay(this)) throw new TypeError('invalid receiver');
if (!ES.IsTemporalMonthDay(other)) throw new TypeError('invalid MonthDay object');
for (const slot of [MONTH, DAY]) {
const val1 = GetSlot(this, slot);
const val2 = GetSlot(other, slot);
if (val1 !== val2) return false;
}
return true;
}
toString() {
if (!ES.IsTemporalMonthDay(this)) throw new TypeError('invalid receiver');
let month = ES.ISODateTimePartString(GetSlot(this, MONTH));
Expand Down
10 changes: 10 additions & 0 deletions polyfill/lib/time.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -191,6 +191,16 @@ export class Time {
const Duration = GetIntrinsic('%Temporal.Duration%');
return new Duration(0, 0, 0, hours, minutes, seconds, milliseconds, microseconds, nanoseconds);
}
equals(other) {
if (!ES.IsTemporalTime(this)) throw new TypeError('invalid receiver');
if (!ES.IsTemporalTime(other)) throw new TypeError('invalid Time object');
for (const slot of [HOUR, MINUTE, SECOND, MILLISECOND, MICROSECOND, NANOSECOND]) {
const val1 = GetSlot(this, slot);
const val2 = GetSlot(other, slot);
if (val1 !== val2) return false;
}
return true;
}

toString() {
if (!ES.IsTemporalTime(this)) throw new TypeError('invalid receiver');
Expand Down
10 changes: 10 additions & 0 deletions polyfill/lib/yearmonth.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -112,6 +112,16 @@ export class YearMonth {
const Duration = GetIntrinsic('%Temporal.Duration%');
return new Duration(years, months);
}
equals(other) {
if (!ES.IsTemporalYearMonth(this)) throw new TypeError('invalid receiver');
if (!ES.IsTemporalYearMonth(other)) throw new TypeError('invalid YearMonth object');
for (const slot of [YEAR, MONTH]) {
const val1 = GetSlot(this, slot);
const val2 = GetSlot(other, slot);
if (val1 !== val2) return false;
}
return true;
}
toString() {
if (!ES.IsTemporalYearMonth(this)) throw new TypeError('invalid receiver');
let year = ES.ISOYearString(GetSlot(this, YEAR));
Expand Down
19 changes: 19 additions & 0 deletions polyfill/test/Absolute/prototype/equals/argument-wrong-type.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
// Copyright (C) 2020 Igalia, S.L. All rights reserved.
// This code is governed by the BSD license found in the LICENSE file.

/*---
esid: sec-temporal.absolute.prototype.equals
features: [Symbol]
---*/

const instance = Temporal.Absolute.fromEpochSeconds(0);

assert.throws(TypeError, () => instance.equals(undefined), "undefined");
assert.throws(TypeError, () => instance.equals(null), "null");
assert.throws(TypeError, () => instance.equals(true), "true");
assert.throws(TypeError, () => instance.equals(""), "empty string");
assert.throws(TypeError, () => instance.equals(Symbol()), "symbol");
assert.throws(TypeError, () => instance.equals(1), "1");
assert.throws(TypeError, () => instance.equals({}), "plain object");
assert.throws(TypeError, () => instance.equals(Temporal.Absolute), "Temporal.Absolute");
assert.throws(TypeError, () => instance.equals(Temporal.Absolute.prototype), "Temporal.Absolute.prototype");
21 changes: 21 additions & 0 deletions polyfill/test/Absolute/prototype/equals/branding.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
// Copyright (C) 2020 Igalia, S.L. All rights reserved.
// This code is governed by the BSD license found in the LICENSE file.

/*---
esid: sec-temporal.absolute.prototype.equals
features: [Symbol]
---*/

const equals = Temporal.Absolute.prototype.equals;

assert.sameValue(typeof equals, "function");

assert.throws(TypeError, () => equals.call(undefined), "undefined");
assert.throws(TypeError, () => equals.call(null), "null");
assert.throws(TypeError, () => equals.call(true), "true");
assert.throws(TypeError, () => equals.call(""), "empty string");
assert.throws(TypeError, () => equals.call(Symbol()), "symbol");
assert.throws(TypeError, () => equals.call(1), "1");
assert.throws(TypeError, () => equals.call({}), "plain object");
assert.throws(TypeError, () => equals.call(Temporal.Absolute), "Temporal.Absolute");
assert.throws(TypeError, () => equals.call(Temporal.Absolute.prototype), "Temporal.Absolute.prototype");
19 changes: 19 additions & 0 deletions polyfill/test/Absolute/prototype/equals/prop-desc.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
// Copyright (C) 2020 Igalia, S.L. All rights reserved.
// This code is governed by the BSD license found in the LICENSE file.

/*---
includes: [propertyHelper.js]
---*/

const { Absolute } = Temporal;
assert.sameValue(
typeof Absolute.prototype.equals,
"function",
"`typeof Absolute.prototype.equals` is `function`"
);

verifyProperty(Absolute.prototype, "equals", {
writable: true,
enumerable: false,
configurable: true,
});
19 changes: 19 additions & 0 deletions polyfill/test/Date/prototype/equals/argument-wrong-type.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
// Copyright (C) 2020 Igalia, S.L. All rights reserved.
// This code is governed by the BSD license found in the LICENSE file.

/*---
esid: sec-temporal.date.prototype.equals
features: [Symbol]
---*/

const instance = Temporal.Date.from({ year: 2000, month: 5, day: 2 });

assert.throws(TypeError, () => instance.equals(undefined), "undefined");
assert.throws(TypeError, () => instance.equals(null), "null");
assert.throws(TypeError, () => instance.equals(true), "true");
assert.throws(TypeError, () => instance.equals(""), "empty string");
assert.throws(TypeError, () => instance.equals(Symbol()), "symbol");
assert.throws(TypeError, () => instance.equals(1), "1");
assert.throws(TypeError, () => instance.equals({}), "plain object");
assert.throws(TypeError, () => instance.equals(Temporal.Date), "Temporal.Date");
assert.throws(TypeError, () => instance.equals(Temporal.Date.prototype), "Temporal.Date.prototype");
21 changes: 21 additions & 0 deletions polyfill/test/Date/prototype/equals/branding.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
// Copyright (C) 2020 Igalia, S.L. All rights reserved.
// This code is governed by the BSD license found in the LICENSE file.

/*---
esid: sec-temporal.date.prototype.equals
features: [Symbol]
---*/

const equals = Temporal.Date.prototype.equals;

assert.sameValue(typeof equals, "function");

assert.throws(TypeError, () => equals.call(undefined), "undefined");
assert.throws(TypeError, () => equals.call(null), "null");
assert.throws(TypeError, () => equals.call(true), "true");
assert.throws(TypeError, () => equals.call(""), "empty string");
assert.throws(TypeError, () => equals.call(Symbol()), "symbol");
assert.throws(TypeError, () => equals.call(1), "1");
assert.throws(TypeError, () => equals.call({}), "plain object");
assert.throws(TypeError, () => equals.call(Temporal.Date), "Temporal.Date");
assert.throws(TypeError, () => equals.call(Temporal.Date.prototype), "Temporal.Date.prototype");
18 changes: 18 additions & 0 deletions polyfill/test/Date/prototype/equals/prop-desc.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
// Copyright (C) 2020 Igalia, S.L. All rights reserved.
// This code is governed by the BSD license found in the LICENSE file.

/*---
includes: [propertyHelper.js]
---*/

assert.sameValue(
typeof Temporal.Date.prototype.equals,
"function",
"`typeof Date.prototype.equals` is `function`"
);

verifyProperty(Temporal.Date.prototype, "equals", {
writable: true,
enumerable: false,
configurable: true,
});
Loading

0 comments on commit 5f54668

Please sign in to comment.