js-temporal / proposal-temporal-v2 Goto Github PK
View Code? Open in Web Editor NEWFuture additions to Temporal
License: MIT License
Future additions to Temporal
License: MIT License
Lunar Calendars, like the Islamic calendar, begin their new days at sunset rather than midnight. To fully support these calendars, Temporal needs to implement astronomical calculations to determine sunset times, which can vary by location.
Additionally, Temporal should expose astronomical calculation functions and variables, such as sunsetTime
and sunriseTime
, or any other relevant APIs.
This feature was previously discussed in Temporal V1 and was suggested for inclusion in V2.
Supporting this behaviour in lunar calendars would give more accurate results and help in implementing accurate applications such as calendar applications or in the case of Islamic calendars having some astronomical calculations exposed by Temporal can also help in calculating Muslim Prayer times.
The concerns that i can foresee are:
I am not aware of any standard library implementing this behaviour however, here are some libraries that implement astronomical calculations for sunset and sunrise.
StartOfDay
.This is a niche use case, but if you create a custom calendar or time zone, there is currently no way to deserialize it, or Temporal objects including it, from a string.
A proposal is described in the closed issue tc39/proposal-temporal#1423 (comment). To summarize that proposal in some code examples:
class CustomCalendar {
toString() { return 'custom-calendar'; }
// ... implementation of some custom calendar ...
}
const isoString = '2021-04-28[u-ca=custom-calendar]';
function calendarResolver(id) {
if (id === 'custom-calendar') return new CustomCalendar();
return Temporal.Calendar.from(id);
}
// Without calendarResolver, this would throw RangeError: invalid calendar 'custom-calendar'
Temporal.PlainDate.from(isoString, { calendarResolver })
// Same here
time = Temporal.PlainTime.from('12:00');
time.withPlainDate(isoString, { calendarResolver })
Examples for custom time zones not given here, but analogous to the above.
2021-04-28[u-ca=custom-calendar]
and will monkeypatch Temporal to achieve that goal if there's no other way to achieve it, even if they are aware that monkeypatching is not a good idea. Adding this parameter gives these programmers an alternative that would hopefully prevent them from publishing monkeypatches in their custom-calendar libraries, that could conflict with each other.2021-04-28[u-ca=custom-calendar]
is difficult to do correctly, and it would be easy to create discrepancies between how the userland code parses ISO strings and how Temporal does.None so far.
Summary of discussion from tc39/proposal-temporal#796:
When working with legacy or third-party systems, you often find yourself receiving date strings in the most unwieldy formats. To remedy this, date libraries often support custom format strings to make it possible to parse such strings.
A Temporal.(type).fromFormat(string, formatString)
method was proposed. This format from Unicode TR 35 was identified as a likely microformat to use. It's also used by date-fns with a few additions.
This was the most popular feature that didn't make it into the original Temporal proposal, as determined unscientifically by GitHub emoji reactions and number of times it was mentioned in our feedback survey.
Parsing is difficult to do correctly, and possibly out of reach entirely for inexperienced developers. This is one of the reasons developers have turned to third-party libraries instead of using legacy Date.
Parsing textual month names or era names in a locale-aware way would require including a large bundle of locale data, defeating the purpose of using Temporal rather than Moment or another locale-aware library.
The philosophy in the original Temporal proposal was that parsing anything other than ISO strings is in the domain of business logic because everybody knows their own use case better than Temporal can.
Whatever is enabled here should not repeat the mistakes of Date.parse()
.
moment("07-28-2020", "MM-DD-YYYY")
DateTime.fromFormat("07-28-2020", "MM-DD-YYYY")
parse('07/28/2020', 'MM/dd/y', new Date())
Temporal.PlainDate.fromFormat('2021-02-11', 'yyyy-MM')
— format string doesn't include all the required fieldsTemporal.PlainDate.fromFormat('2021-13-11', 'yyyy-MM-dd')
— month is invalid in the ISO calendar but could be valid in another calendar. Need a way to indicate in which calendar the string is parsed.String
validation of date, time or date with time is currently error prone and/or memory hungry. With this proposal, String
validation will have native performance and be correct.
String
is very useful for serialization and communication between services but require validation when expressing date, time or date with time.
By adding a static .test
method to Temporal.PlainDate
, Temporal.PlainTime
and Temporal.PlainDateTime
, validation would be performant, easy and correct.
Temporal.PlainDate.test (String)
Where String
= YYYY-MM-DD
or ±YYYY-MM-DD
Temporal.PlainTime.test (String)
Where String
= hh:mm:ss
Temporal.PlainDateTime.test (String)
Where String
= YYYY-MM-DDThh:mm:ss
YYYY
refers to a year between 0001 and 9999, both included.
MM
refers to a a zero-padded month between 01 and 12, both included.
DD
refers to a a zero-padded month day between 01 and 31, both included, in the Gregorian calendar.
hh
refers to a zero-padded hour between 00 and 23, both included.
mm
refers to a zero-padded minute between 00 and 59, both included.
ss
refers to a zero-padded second between 00 and 60 (where 60 is only used to denote an added leap second), both included.
Ref: https://en.wikipedia.org/wiki/ISO_8601
One use-case is to send a valid date as a String
to a database and while the database will validate the date, we would want to check the date before opening a database connection and wait for the response. This can obviously be done both client-side and server-side before communicating with the database.
With Temporal proposal 1, it can be implemented like so:
const validateDate = x => {
try {
Temporal.PlainDate.from (x)
} catch (e) {
return false
}
return true
}
validateDate ('2022-02-29') // <- false, because there is only 28 days in February 2022.
An optimization would be to have Temporal.PlainDate.test
so that the static .test
method can bypass the instantiation process and return a Boolean
. Perhaps bringing it on par with the performance of the regex solution.
const validateDate = x => /^\d{4}-(?:0[1-9]|1[0-2])-(?:0[1-9]|[1-2]\d|3[0-1])$/.test (x)
validateDate ('2022-02-29') // <- true, because regex has no concept of dates
Communicating between services sometimes also means different formats. This is covered in #2 which in contrast to this suggestion, creates an object instance. Similarly to #2, it might be useful to be able to validate other serializable formats than the ones Temporal's from
method accept.
For outputting different serializable formats from Temporal, there is #5.
I have not gone much into Temporal.PlainTime.test
and Temporal.PlainDateTime.test
. Further examination of use-cases might reveal that it's more complicated than stated here.
There does not seem to be any static date parsing libraries in the current JS ecosystem (as of 2021).
All four libraries below will create instance of Date
.
Lib Name | Static | Syntax | URL |
---|---|---|---|
Moment.js | No | moment("2022-02-29").isValid() |
https://momentjs.com/docs/#/parsing/is-valid/ |
luxon | No | DateTime.fromISO("2022-02-29").isValid |
https://moment.github.io/luxon/#/validity |
date-fns | No | isValid (parseISO('2022-02-29')) |
https://date-fns.org/v2.27.0/docs/isValid#examples |
date-fp | No | isValid (parse ('YYYY-MM-DD', '2022-02-29')) |
https://cullophid.github.io/date-fp/docs/functions/is-valid.html |
Temporal.PlainDate.test ('2020-01-01T00:00Z')
MUST return false
. See tc39/proposal-temporal#1751 for a discussion about why that is important.Add Date.fromTemporalInstant
and Date.fromTemporalZonedDateTime
function to easily create Dates from Temporal values.
Converting to Date currently requires:
new Date(instant.epochMilliseconds)
new Date(zonedDateTime.epochMilliseconds)
This is awkward, and requires remembering the unit for Dates. (seconds? millis? nanos?) For adoption, it's important that converting to/from Dates is easy and rebost.
This proposal offers convenience functions:
Date.fromTemporalInstant(instant)
Date.fromTemporalZonedDateTime(zonedDateTime)
See:
Converting from ZonedDateTime and Date suggests that Date has time zone (not just offset)
Downcasting is common. No one would think that ZonedDateTIme -> Instant suggests that Instant has time zone.
Usage of Date should be discouraged
This sentiment vastly underestimates the stickiness of current Date usage and the necessity of easy conversion between the two.
new Date(instant.epochMilliseconds) is easy enough
With all due respect, not at all. For example MDN list the following constructor calls for Date:
new Date()
new Date(value)
new Date(dateString)
new Date(dateObject)
new Date(year, monthIndex)
new Date(year, monthIndex, day)
new Date(year, monthIndex, day, hours)
new Date(year, monthIndex, day, hours, minutes)
new Date(year, monthIndex, day, hours, minutes, seconds)
new Date(year, monthIndex, day, hours, minutes, seconds, milliseconds)
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Date/Date
I'm supposed to know that I should pass one arg, and that arg is milliseconds? Come on, man.
N/A
N/A
(Migrating from tc39/proposal-temporal#2359.)
I'm currently working with an HTTP API that might return a date, a year–month combination or just a year by itself.
I'm turning them into the type:
type PartialDate = number | PlainYearMonth | PlainDate
This isn't ideal as a year by itself isn't strongly typed nor provides a toLocaleString
function.
(I can work around the latter by turning it into a PlainYearMonth
and then using the { year: 'numeric' }
option to ignore the fake month.)
An equivalent object exists in other languages, e.g. java.time.Year
.
I can only find one reference to a PlainYear
object, but that seems to have been referenced in a different context (tc39/proposal-temporal#1203).
Temporal.PlainDateTime.prototype.toZonedDateTime() has an options argument with which you can choose the disambiguation strategy if the PlainDateTime doesn't exist, or exists twice, in the given time zone.
Temporal.PlainDate and Temporal.PlainTime's toZonedDateTime() methods don't have this options argument. The disambiguation strategy is always "compatible"
when calling these methods.
Here's an example of when this might be needed:
const timeZone = Temporal.TimeZone.from('America/Vancouver');
const plainDate = Temporal.PlainDate.from('2021-03-14');
const plainTime = Temporal.PlainTime.from('02:30');
plainDate.toZonedDateTime({ timeZone, plainTime })
plainTime.toZonedDateTime({ timeZone, plainDate })
Both of these return a ZonedDateTime of 2021-03-14T03:30:00-07:00[America/Vancouver]
. It's not possible to get 01:30 as it would be with PlainDateTime.
Of course, this is easily worked around by creating a PlainDateTime first:
plainDate.toPlainDateTime(plainTime).toZonedDateTime(timeZone, { disambiguation: 'earlier' })
This returns a ZonedDateTime of 2021-03-14T01:30:00-08:00[America/Vancouver]
as expected.
TBD
TBD
Several Temporal types have an allowed range of values (tc39/proposal-temporal#24):
It's not currently possible to get these values from JS, except by knowing what they are through out-of-band documentation.
One recommendation would be to include constants on each type's constructor (e.g. Temporal.Instant.MAX_VALUE
). This would avoid polluting top-level autocomplete with a large number of rarely used options. Also it would match other types' behavior like Number.
These are useful for input validation, expanded polyfills, and tests.
None, this seems useful to have.
datetime.datetime.max
, etc.chrono::MAX_DATE
, etc.iso8601
as the "machine" calendar might be the answer here.PlainMonthDay.from({ monthCode: 'M12', day: 29 })
(?)I'd like an easy way to get a Temporal.TimeZone
object from a serialized time zone in the RFC 8536 TZif format (application/tzif
or application/tzif-leap
) used by the IANA time zone database.
Maybe const tz = Temporal.TimeZone.fromTzif(tzif_blob);
(so from a Blob
or maybe from an ArrayBuffer
).
In an application I'm working on, the server maintains an (in-memory) index of data by calendar day in a particular time zone. For the application to work as expected, the Javascript client should perform date math with exactly the same definition of the time zone. If there's any mismatch, data may appear in a different day than specified by the index, causing confusion. It'd be most convenient if the server could just send its zone file in the standard format and the client could easily load it to a TimeZone
object.
It should be possible with the current proposal to write a custom TimeZone
protocol implementation that parses this format, but it's not completely trivial. I'd expect a Temporal implementation to have a built-in parser for zones stored in this format. Exposing it would avoid redundant effort, reduce application size, and possibly make operations more efficient.
Bloat, if the Temporal implementation keeps its internal zones in a different format or delegates to some system library that doesn't support this loading. I'm not sure if either of these are a concern in practice.
Go's standard library exposes this exact mechanism: https://golang.org/pkg/time/#LoadLocationFromTZData
Python has zoneinfo for explicitly loading IANA-format time zones from the filesystem (or a compiled-in list). It doesn't quite do loading them from a passed-in edit: it can load "file-like" blobs via ZoneInfo.from_file.bytes
object.
Many libraries use this format internally. The IANA has an incomplete list of Other TZif readers.
From @FrankYFTang at tc39/proposal-temporal#2367:
Currently, Temporal.Calendar only has toString ( ) method but no toLocaleString ( ) method as other Temporal objects ( PlainDate, PlainTime, PlainDateTime, PlainYearMonth, PlainMonthDay, ZonedDateTime, Instant, Duration)
Temporal spec should consider adding toLocaleString for Temporal.Calendar
Intl.DisplayNames in ECMA402 already support "calendar", Temporal.Calendar.prototype.toLocaleString() could just delegate to Intl.DisplayNames in the same way as how it delegate to Intl.DateTimeFormat in the current spec.
A more intuitive way to get the localized name of a calendar:
calendar.toLocaleString('en-CA', { style: 'short' });
vs
const name = new Intl.DisplayNames('en-CA', { type: 'calendar', style: 'short' });
name.of(calendar);
Currently calling calendar.toLocaleString()
resolves to Object.prototype.toLocaleString()
which calls calendar.toString()
. So existing behaviour could change. I believe that changing the output of toLocaleString()
would be web-compatible, though.
Intl.DisplayNames
and the precedent of toLocaleString()
.Should there be a Temporal.PlainMonthDay.compare()
static method? Follow up from tc39/proposal-temporal#523.
The main use case is sorting a list of birthdays or anniversaries, holidays that fall on the same date each year, etc. This is easy enough when using the ISO calendar, and human calendars based on it. For example, you can do it in several ways if your instances are guaranteed to have the ISO calendar:
// Rely on the default behaviour of converting to a string and sorting lexicographically:
birthdays.sort();
// Sort by monthCode, then day
birthdays.sort((a, b) => {
if (a.monthCode === b.monthCode) return a.day - b.day;
return a.monthCode < b.monthCode ? -1 : 1;
});
// Sort by projecting into Temporal.PlainDate in a leap year
birthdays.sort((a, b) => {
const year = 2000;
return Temporal.PlainDate.compare(a.toPlainDate({ year }), b.toPlainDate({ year }));
});
However, if sorting in a calendar with leap months, this is not easy at all and requires making choices about how intercalary months are ordered.
Temporal.PlainMonthDay.from({ monthCode: 'M05L', day: 15, calendar: 'chinese' })
come after Temporal.PlainMonthDay.from({ monthCode: 'M04L', day: 15, calendar: 'chinese' })
, or is it undefined because these two months never occur in the same year?In order to advance to Stage 1, we need a champion for this proposal who will advance it in TC39 committee meetings. See, for more information, https://tc39.es/process-document/
Summary of discussion at tc39/proposal-temporal#729:
The proposal is a Temporal.PlainYearMonth.prototype.atEndOfMonth()
method that returns a PlainDate on the last day of the given month.
The current way to do this is a bit roundabout:
yearMonth.toPlainDate({ day: yearMonth.daysInMonth })
The use case is building a date picker with a PlainYearMonth as input parameter.
TBD
YearMonth.atEndOfMonth()
.TBD
The toLocaleString()
method of PlainYearMonth and PlainMonthDay usually requires some surprising extra manipulation in order for it to work. See tc39/proposal-temporal#262 (comment) for the rationale. A future change might make these toLocaleString()
methods easier to use.
A sampler of the copious feedback we've gotten on this part of the proposal:
Currently, to format a PlainYearMonth as human-readable, you have to do one of three things:
const localeCalendar = new Intl.DateTimeFormat().resolvedOptions().calendar;
const pym = Temporal.PlainYearMonth.from({ ...data, calendar: localeCalendar });
console.log(pym.toLocaleString());
console.log(pym.toLocaleString(undefined, { calendar: pym.calendarId });
console.log(date.toLocaleString(undefined, { year: 'numeric', month: 'long' });
These workarounds are surprising because they aren't necessary for other Temporal types (PlainDate, etc.) and unpleasantly verbose for what they do.
The original reasons for this decision still apply:
withCalendar()
method.None yet
None yet
Previous discussion at tc39/proposal-temporal#45 and tc39/proposal-temporal#977.
It could be useful for developers to know when they can rely on nanoseconds in Temporal.now, since not all environments may support that, or artificially limit it.
Some use cases are:
It's not clear whether Temporal is the right place to expose this information, since it would also affect other APIs available in each environment (such as performance.now
and requestAnimationFrame
in the DOM).
Exposing this information may be a browser fingerprinting concern.
The clock resolution must be injectable in environments that patch out Temporal.now.
clock_getres()
TBD
Previous discussion at tc39/proposal-temporal#634 and tc39/proposal-temporal#1058.
Similar to Temporal.Calendar.monthsInYear()
and the associated monthsInYear
properties on types that have a year (PlainDate, PlainDateTime, PlainYearMonth, ZonedDateTime), the proposal is to add a weeksInYear()
method to Temporal.Calendar and weeksInYear
properties to the types that have a year.
Proposed semantics would be to return the integer number of the last week of the year. Alternatively, the number of weeks in the "week year" that the date falls in. (For example, 2021-01-01 is in week 53 of 2020 in the ISO calendar week-numbering rules.)
Also needed would be a yearOfWeek()
method which returns the week year, or a weekYearOffset()
method which returns the difference between the week year and the calendar year.
It's currently not possible to get this information in Temporal. This means that it's not possible to correctly output an ISO year-week-day date, since the year would need to be the week year.
Numbered weeks of the year are fairly commonly used in business contexts in Europe.
See "corner cases" below. This would have to be defined very carefully.
IYYY
in PostgreSQL formattingFollow up from tc39/proposal-temporal#745.
You can get the current Unix time in ms with Temporal.Now.instant().epochMilliseconds
which is quite a lot longer than Date.now()
. In Temporal we had some user feedback that this was objectionably long.
It might hinder adoption of Temporal if developers are tempted to continue using Date.now()
for this purpose because it's conveniently short.
It remains to be seen whether there's a big need for Unix timestamps in ms when fully porting legacy Date code to Temporal. It might be better to use Temporal.Instant, and Temporal.Now.instant()
, while longer than Date.now()
, is not inconveniently so.
In Temporal V1, we restricted until
and since
to same-calendar arguments, even if largestUnit
was a calendar-safe unit like 'days'
or smaller. The reason for this restriction was to allow for future Temporal versions to support calendars that had different conceptions of measuring time, e.g. calendar days that began at sundown instead of 00:00
.
If we don't want to support those kinds of custom time calendars, then we should relax this restriction and allow until
and since
across calendars as long as largestUnit
is 'days'
or smaller.
Note that we removed time calendars from Temporal V1 because we couldn't find any real-world use cases where calendar-specific time was used in software.
Fewer unnecessary exceptions when writing cross-calendar code
If we still want to support time calendars in the future, we either shouldn't make this change OR we could specify the meaning of until
and since
as being relative to the calendar of this
, which would support future time calendars.
Don't know.
See discussion of time calendars above.
Summary of discussions from tc39/proposal-temporal#205 and tc39/proposal-temporal#682.
From user feedback, it seems that the main use cases are iterating through a range between two points on the time line (whether exact time or calendar/wall-clock time), and having an interval object which boxes up two orderable Temporal objects with methods for checking whether another Temporal object or interval lies within the range.
Possible APIs proposed:
const start = new Temporal.PlainDate(2021, 1, 1);
const end = new Temporal.PlainDate(2020, 12, 31);
const step = Temporal.Duration.from({ days: 1 });
for (const date of Temporal.range(start, end, step)) { ... }
class Interval {
constructor(startInstant, endInstant) { ... }
static from(isoStringOrIntervalLike) { ... }
contains(instant) { ... }
intersects(interval) { ... }
encloses(interval) { ... }
equals(interval) { ... }
*iterate(duration) { ... }
get start() { ... }
get end() { ... }
get duration() { ... }
with(intervalLike) { ... }
toString(startTimeZone, endTimeZone) { ... }
...
}
Intervals are not difficult to implement in userland, but it could be a benefit for Temporal to have a type with a consistent set of operations, and to avoid off-by-one errors.
An Interval object doesn't quite fit with the strict typing philosophy of Temporal. It's possible there would have to be different Interval types for the different Temporal types: at least Instant and ZonedDateTime, probably PlainDateTime and PlainDate, and possibly PlainYearMonth and PlainTime. Probably business logic would use at most one of these types at a time.
Interval
DatePeriod
(Moved from tc39/proposal-temporal#1712)
Temporal.Duration strings are currently allowed to specify fractional hours, minutes, and seconds, up to 9 digits.
Example strings that are currently rejected by Temporal.Duration.from(), that nonetheless represent exact numbers of nanoseconds:
PT0.00000000001H
→ 36 nanosecondsPT0.0000000001M
→ 6 nanosecondsIncreasing the allowed precision beyond 9 digits for fractional seconds, 10 digits for fractional minutes, or 11 digits for fractional hours, may pose implementation difficulties since implementations would have to calculate with fractional nanoseconds.
round()
with smallestUnit: 'month'
and smallestUnit: 'year'
should be allowed on PlainDate, PlainDateTime, ZonedDateTime, and (year
only) PlainYearMonth.
In V1, PlainDate lacks a round
method, and PlainDateTime and ZonedDateTime are both limited to smallestUnit: 'day'
or smaller.
Rounding up would advance to the first day of the next month or year, respectively. (Not the last day of the month or year.)
roundingIncrement
would be limited to 1
or undefined
, because the number of months in a year can vary in lunisolar calendars like Hebrew or Chinese that have 12 months in non-leap years but 13 months in leap years.
/** Are we halfway done with the year yet? */
function isHalfwayDoneWithYear(date) {
return date.round({ smallestUnit: 'year' }).year === date.year;
}
// current workaround
function isHalfwayDoneWithYear(date) {
const daysSoFar = date.since(date.with({month: 1, day: 1}));
return daysSoFar >= date.daysInYear / 2;
}
// proposed
date = date.round({smallestUnit: 'month', roundingMode: 'ceil'});
// current, which breaks in the chinese or hebrew calendar where years can have 12 or 13 months
if (date.day > 1) {
date = date.month === 12 ? date.with({ year: date.year + 1, month: 1 }) : date.with({ month: date.month + 1 });
}
// BTW, here's a less obvious alternative to buggy code above
if (date.day > 1) date = date.add({month: 1}).with({day: 1})
function roundToMonthsOrYears(temporalInstance, roundOptions) {
const baseValues = {
year: temporalInstance.year, month: 1, monthCode: 'M01', day: 1,
hour: 0, minute: 0, second: 0, millisecond: 0, microsecond: 0, nanosecond: 0
};
const base = temporalInstance.with(baseValues)
const duration = base.until(temporalInstance, roundOptions);
return base.add(duration);
}
roundToMonthsOrYears(Temporal.PlainDate.from('2021-02-15'), { smallestUnit: 'month', roundingMode: 'trunc' });
// => 2021-02-01
roundToMonthsOrYears(Temporal.PlainDate.from('2021-02-15'), { smallestUnit: 'month', roundingMode: 'ceil' });
// => 2021-03-01
roundToMonthsOrYears(Temporal.PlainDate.from('2021-02-15'), { smallestUnit: 'month', roundingMode: 'halfExpand' });
// => 2021-03-01
roundToMonthsOrYears(Temporal.PlainDate.from('2021-02-14'), { smallestUnit: 'month', roundingMode: 'halfExpand' });
// => 2021-02-01
Year and month rounding already exists on until
and since
and Duration.prototype.round
, just not on the round
methods of PlainDate
, PlainDateTime
, ZonedDateTime
.
See tc39/proposal-temporal#1785 for previous discussion.
None that I know of, but I can add if they're raised in comments.
Similar to #2, but for string formatting instead of string parsing.
If #2 is adopted, an API using the same microformat could be proposed.
Sometimes it's necessary to obtain a string that fits a particular format that is neither ISO 8601 nor locale-sensitive. For these cases, third-party date libraries usually provide a formatting API.
In general, for human-readable strings, developers should not customize the format themselves, and use only the locale-specific formats provided by Intl.DateTimeFormat. While this functionality might be useful for formatting strings that must fit a certain format for machine-readability, providing such an API might result in worse user experiences because developers are tempted to use it for human-readable strings instead of Intl.DateTimeFormat.
Unlike parsing, this kind of functionality is easy to build in business logic.
TBD
TBD
Summary of previous discussion at tc39/proposal-temporal#585:
It could be useful to be able to multiply or divide a duration by a scalar number.
The main use cases involve averaging a list of durations, and making an event happen when a certain percentage of a duration has elapsed.
The current way to average durations is uninituitive and not easily discoverable: decide what unit you want you want the resolution of the average to be, total the durations to that unit, sum and divide, and then balance:
const durations = ['PT3H', 'PT2H30M', 'PT3H15M']
.map(str => Temporal.Duration.from(str));
const sumSeconds = durations
.reduce((total, d) => total + d.total({ unit: 'seconds' }), 0);
const average = Temporal.Duration.from({
seconds: Math.round(sumSeconds / durations.length)
}).round({ largestUnit: 'hours' });
TBD
TimeSpan.Multiply
and TimeSpan.Divide
Summary of tc39/proposal-temporal#888:
It could be convenient to offer a fractionalSecond
or fractionalSeconds
property on PlainTime, PlainDateTime, ZonedDateTime, and Duration. The value would be equal to millisecond / 1e3 + microsecond / 1e6 + nanosecond / 1e9
.
millisecond / 1e3 + microsecond / 1e6 + nanosecond / 1e9
is a reasonably common operation, and providing it in Temporal would reduce the chances of carelessly getting the exponents wrong.
It could be useful for customized formatting, or for validation (e.g. pdt.fractionalSecond === 0
).
The returned value wouldn't be exactly representable as a JS number, which could lead to unwanted effects. An alternative might be to give an integer number of nanoseconds, equal to millisecond * 1e6 + microsecond * 1e3 + nanosecond
.
TBD
TBD
Summary of discussion from tc39/proposal-temporal#1086 and meeting minutes:
The Temporal.(type).compare()
static methods have confusing parameter order, in order for them to be able to be used as the comparator function for Array.prototype.sort().
A suggestion that's come up several times is that Temporal types could have more idiomatic methods named something like lessThan()
, earlierThan()
, isBefore()
, isSameOrAfter()
. Although these would duplicate the functionality of compare()
, they would make user code much easier to read.
For example:
// proposed
function afterKidsBedtime(time) {
return time.greaterThan('20:30');
}
// current
function afterKidsBedtime(time) {
return Temporal.PlainTime.compare(time, '20:30') > 0;
}
There was a work-in-progress at tc39/proposal-temporal#1074 on which a proposal in this repository can be based.
Sets of names that were proposed so far:
The meaning of the return value of compare(a, b)
has been confusing since it was first introduced in C, because you have to think carefully about whether 1 means a > b or b < a. It can't be read left-to-right.
An isSame()
method could, in addition, implement equality based on the internal ISO-calendar slots of the Temporal object, without taking the calendar into account, which makes it fulfill a different function from equals()
.
Such an API might become useless if operator overloading is ever added to the language, at which point the comparison operators could simply be used.
Names like greaterThan()
are closely tied to English grammar and so may only improve the issue for English speakers.
A problem with names implying "before" or "after" is that due to time zones, a wall-clock time that is "greater than" another wall-clock time may not actually be "after" it. These names also wouldn't be consistent if similar methods were added to Temporal.Duration.
isSame('day')
time1.isAfter(time2)
TBD
Summary of discussion at tc39/proposal-temporal#1317:
Technically, if you store a ZonedDateTime that's in the future, it doesn't translate to a unique PlainDateTime (or Instant, if you store it as an ISO string); you need to add the version of the time zone database that was current when it was stored.
An example use case would be to store a ZonedDateTime, with the associated PlainDateTime cached, and the tzdata version. If the tzdata version changes, the cached PlainDateTime would be updated.
Another use case would be to compare the versions of the time zone data between client and server, to make sure that date operations are consistent if both client and server perform them.
There is no requirement for implementations to provide time zone data based on "the" time zone database, or any time zone data at all other than UTC, so a meaningful version is not always possible.
Exposing this information is a browser fingerprinting concern.
timezone_version_get()
tzdata
library for Python has tzdata.IANA_VERSION
.TBD
Add formatting and parsing support for RFC 7231 HTTP-dates, as used in HTTP headers (Expires
, Last-Modified
, etc) and cookies.
There's clear utility, as it's a ubiquitous format on the web, due to being used in many common HTTP headers and cookies. Currently the best way of handling in JS is to use the Date
methods, but that's not ideal as Date
is already being referred to as "legacy" in the Temporal documentation due to its many drawbacks. One could reasonably expect "no Date
" to become a common linter rule once Temporal achieves widespread platform support.
Date
also doesn't implement validation for the string parsing — arbitrary strings will never throw and may or may not return valid dates that may or may not be what was expected.
In addition, Date
parsing fails to fully implement HTTP-date
as specified in RFC 7231, as the two obsolete (pre-1995) formats aren't accounted for.
None that I can think of
Formatting:
Parsing:
Date
constructor, which uses the same parsing logic
toUTCString
Mon, 06 Nov 1994 08:49:37 GMT
throw due to that date being a Sunday not a Monday?Temporal.Instant.toHttpDateString
and Temporal.Instant.fromHttpDateString
? Logically, Instant
seems to make more sense than ZonedDateTime
, despite the IMF-fixdate
and rfc850-date
formats specifying "GMT" (it's always GMT).Prior discussion at tc39/proposal-temporal#255, and a bit at tc39/proposal-temporal#819.
Going even farther than #8, to add first-class support for year-week-day dates, Temporal.PlainDate, Temporal.PlainDateTime, and Temporal.ZonedDateTime should accept the ISO string format 2020-W06-5
(2020-02-12, of which the weekOfYear
property is 6 and dayOfWeek
is 5). (Note that 2020 is the "week year", i.e. 2020-W53-5
is 2021-01-01.) Additionally, add a Temporal.PlainYearWeek type, and methods to convert to and from Temporal.PlainDate. Temporal.PlainDate.toString would also need to gain an option to output itself in the ISO year-week-day format.
Numbered weeks of the year are fairly commonly used in business contexts in Europe. Although not a common request during the development of Temporal, it has been requested several times independently.
TBD
Previously proposed at tc39/proposal-temporal#1324.
The proposal is that the withCalendar()
methods of PlainDate, PlainDateTime, and ZonedDateTime should allow omitting the argument, or undefined
, to mean the ISO calendar.
The ISO calendar acts like a default calendar elsewhere in Temporal, except where a calendar is explicitly needed from the programmer. But if you have a maybe-non-ISO date and want to convert it to ISO (which will be a very common operation when accepting external data), the developer needs to spell out 'iso8601' in every call site. This seems unnecessary, since it's not a call site where omitting the calendar might lead to unexpected results when encountering non-ISO calendars.
TBD
TBD
TBD
A string parsing API accessible to the programmer (not just through Temporal.___.from()
) was considered almost from the very beginning as a potential part of the Temporal (V1) proposal. Ultimately it was decided to be out of scope.
This issue captures the use cases and discussion in case a parser API is proposed in the future.
This feature would probably subsume #22.
These are the use cases that we became aware of during the development of the Temporal V1 proposal:
Previous discussions:
TBD
Many programming languages have an ISO 8601 parser in their standard library, but they all either return the equivalent of a Temporal instance in that language, or an epoch time, or a normalized ISO 8601 string. Some third-party libraries provide a parser functionality:
iso8601:parse_exact
iso_8601
(do not confuse with iso8601
)TBD
The current compare
methods in Temporal ignore calendar. For ZonedDateTime.compare
, time zone is also ignored. When sorting, this method can result in different results depending on the order of the input array. It also means that equals
(which only returns true
if all fields including calendar and timezone are equivalent) behaves differently than compare(t1, t2) === 0
.
A use case that's challenging in the V1 API is "Are these arrays of dates equivalent?", because a custom comparison function is required:
function calendarAwarePlainDateComparer(d1, d2) {
let result = Temporal.PlainDate.compare(d1, d2);
if (result === 0) {
result = d1.calendar.id === d2.calendar.id
? 0
: d1.calendar.id > d2.calendar.id
? 1
: -1;
}
return result;
}
function areDateArraysEquivalent(arr1, arr2) {
if (arr1.length !== arr2.length) return false;
const sorted1 = [...arr1].sort(calendarAwarePlainDateComparer);
const sorted2 = [...arr2].sort(calendarAwarePlainDateComparer);
return sorted1.every((d, i) => d.equals(sorted2[i]));
}
One solution could be to add an option to the existing compare
method. This would make the solution above somewhat more ergonomic by avoiding the custom comparison function. In ZonedDateTime.compare
, this would also allow using different strictness for time zones vs. calendars.
function areDateArraysEquivalent(arr1, arr2) {
if (arr1.length !== arr2.length) return false;
const sorted1 = [...arr1].sort((a, b) => Temporal.PlainDate.compare(a, b, { ignoreCalendar: false }));
const sorted2 = [...arr2].sort((a, b) => Temporal.PlainDate.compare(a, b, { ignoreCalendar: false }));
return sorted1.every((d, i) => d.equals(sorted2[i]));
}
The most ergonomic solution (albeit at the cost of not being able to have different ZDT.compare behavior for calendar vs. timezone differences) would be a new static method that could be used interchangeably with the existing compare
method:
function areDateArraysEquivalent(arr1, arr2) {
if (arr1.length !== arr2.length) return false;
const sorted1 = [...arr1].sort(Temporal.PlainDate.compareStrict);
const sorted2 = [...arr2].sort(Temporal.PlainDate.compareStrict);
return sorted1.every((d, i) => d.equals(sorted2[i]));
}
equals
and compare
:if (!d1.equals(d2)) {
if (Temporal.PlainDate.compare(d1, d2) < 0) {
doSomethingWhenEarlier();
} else {
// BUG! This code will also execute if d1 and d2 are the same date but different calendars
doSomethingWhenLater();
}
}
date1.lessThan(date2)
is a shorthand for Temporal.PlainDate.compareStrict(date1, date2) < 0
"compareTo
method with the same strict semantics proposed here: ("chronology" below means a calendar in Temporal)The comparison is based first on the underlying time-line date, then on the chronology.
The comparison is based first on the instant, then on the local date-time, then on the zone ID, then on the chronology.
(AFAIK, "chrononlogy" is a calendar)
The different semantics of Java's ZonedDateTime comparisons, which sort using the local time as a tiebreaker if the instants match, is interesting. It implies that more Westernly time zones would sort first. I'm not sure why this behavior is valuable. Note that if we adopted that behavior it'd make comparisons slower (because implementations would need to look up the time zone offset on every comparison) so I'd be hesitant to match Java's semantics unless we had a really good use case that was improved by those semantics.
Not aware of any
A declarative, efficient, and flexible JavaScript library for building user interfaces.
🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. 📊📈🎉
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google ❤️ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.