IETF feedback highlighted a gap in Temporal string formats
2021-08-31T12:30[Asia/Tokyo] local time + TZ
2021-08-31T12:30+09:00[Asia/Tokyo] local time + offset + TZ
2021-08-31T12:30+00:00[Asia/Tokyo] bad offset, throw
2021-08-31T03:30Z[Asia/Tokyo] instant + TZ
// Proposed: support for cases where local time is unknown (e.g. legacy Date migration)
Temporal.ZonedDateTime.from('2021-08-31T03:30Z[Asia/Tokyo]')
// => 2021-08-31T12:30+09:00[Asia/Tokyo] (currently: throws)
Temporal.TimeZone.from('2021-08-31T03:30Z[Asia/Tokyo]')
// => Asia/Tokyo (currently: UTC; this is additionally inconsistent)
Temporal.Duration.from({ years: 5 });
// Intended: creates a Temporal.Duration of 5 years// Actual, according to current spec text: throws RangeError
Duration property bags unintentionally had to have all 10 properties!
Duration string serialization bugs
Temporal.Duration.from({ minutes: 5, seconds: 30 }).toString()
// Intended: 'PT5M30S'// Actual, according to current spec text: 'PT5M30S30S'new Temporal.Duration().toString({ fractionalSecondDigits: 2 })
// Intended: 'PT0.00S'// Actual, according to current spec text: 'PT0S'
Temporal.Duration.from({ hours: 6.7 }) // throws, as intended
Temporal.Duration.from({ hours: 6 }).with({ hours: 6.7 })
// Intended: throws// Actual, according to current spec text: a Duration of 6 hours
When making non-integer Duration properties throw, in order to avoid surprises for users, we forgot about with()
called = 0;
observer = { valueOf() { called++; returnInfinity; }};
d = new Temporal.Duration(observer, observer); // => throws RangeError// Intended:
called === 1// Actual, according to the current spec text:
called === 2
d = Temporal.Duration.from('P12M');
Object.defineProperty(d, 'months', { get() { returnInfinity; }});
d.total({ unit: 'days', relativeTo: Temporal.Now.plainDateTimeISO() });
// Throws according to current spec text. Not intended
An accidental Get(duration, "months") instead of duration.[[Months]]
instant = Temporal.Now.instant();
instant.round(); // missing a unit to round to
duration = Temporal.Duration.from({ seconds: 45 });
duration.total(); // missing a unit to get the total of// Intended: throws TypeError// Actual, according to current spec text: throws RangeError
TypeError is more appropriate here than RangeError
We discussed whether required property bag is OK and feel that this is the right tradeoff
date = Temporal.PlainDate.from({ year: 2021, month: 8, day: Infinity });
// Intended: throws RangeError// Actual, according to current spec text: 2021-08-31
date = date.with({ month: -Infinity });
// Intended: throws RangeError// Actual, according to current spec text: 2021-01-31
Also, corresponding changes to all other APIs where a property bag is automatically converted into a Temporal object
Surprising results above due to Infinity being subject to { overflow: 'constrain' }
classCextendsTemporal.Calendar{
constructor() { super('iso8601'); }
dateAdd(date, duration, options) {
const result = super.dateAdd(date, duration, options);
options.overflow = 'bad value';
return result;
}
}
month = Temporal.Now.plainDate(new C()).toPlainYearMonth();
month.add({ months: 1 });
// Intended: same result as if the options object hadn't been messed with// Actual, according to current spec text: throws RangeError
We audited the spec text for instances of this, but missed two
Object passed twice to user code (cont'd)
classCextendsTemporal.Calendar{
constructor() { super('iso8601'); }
dateFromFields(fields, options) {
const result = super.dateFromFields(fields, options);
options.overflow = 'bad value';
return result;
}
}
plain = Temporal.Now.plainDateTime(new C());
plain.with({ hour: 13 });
// Intended: same result as if the options object hadn't been messed with// Actual, according to current spec text: throws RangeError
Incorrect assertion in CalendarDaysInMonth (PR #1716)
classCextendsTemporal.Calendar{
constructor() { super('iso8601'); }
daysInMonth() { returnInfinity; }
}
ym = Temporal.Now.plainDate(new C()).toPlainYearMonth();
ym.subtract({ months: 6 });
// Intended: Throw RangeError// Currently fails an assertion in the spec text as written
Incorrect assertion in Duration.compare() (PR #1726)
classTextendsTemporal.TimeZone{
constructor() { super('UTC'); }
getOffsetNanosecondsFor() { thrownewError('gotcha'); }
}
const relativeTo = new Temporal.ZonedDateTime(0n, new T());
Temporal.Duration.compare({ hours: 24 }, { days: 1 }, { relativeTo });
// Intended: Throw Error('gotcha')// Currently fails an assertion in the spec text as written