To another answer has already given an overview, I would just like to complement a few points.
Obs: as the API is still subject to change, I haven’t gone into the examples much. I’ve only put a few that use the current version of polyfill (which may or may not have changed when you are reading this reply), to give a sense of what the API is capable of.
We can start with the main motivation of this proposal, according to their own creators:
Date
has been a long-Standing Pain point in Ecmascript. This proposes Temporal
... that brings a Modern date/time API to the Ecmascript language.
In free translation, it would be: "Date
has long been a problem point in Ecmascript. We propose Temporal
... that brings a modern date API to the language".
The idea of proposal, therefore, is to create a new date API, to correct some points that they consider problematic in Date
.
One curiosity is that some of those responsible for the proposal are the creators of the Moment js..
One of the problems is the fact that Date
, despite the name, not being exactly a date (in order to represent a single value of day, month, year, hour, minute and second). As already explained here and here, the Date
actually represents a timestamp: the quantity of milliseconds since the Unix Epoch (which in turn amounts to 1970-01-01T00:00Z
- January 1, 1970 at midnight, at UTC).
That is, the Date
represents a unique moment, a point in the timeline. Think of "now": at this very moment, what day is today and what time is it? In every part of the world, the answer will be different (in some parts of the world, it is February 14, in others it can be February 13 or 15, and the time will also be different). Although the date and time are different, the instant (the value of the timestamp) is the same for everyone. And the Date
, according to the language specification, only saves timestamp information.
When you print the date (via alert
or console.log
) or when it obtains information from it (via getters or toString()
), this timestamp is converted to the time zone that is configured in the environment where the code is running (browser, Node, etc). That is, the Date
uses the time zone setting to convert the timestamp to the correct date and time values (or converts to UTC when you use methods such as toISOString()
or the "getters UTC", as getUTCHours
, getUTCMonth
, etc.).
That approach one-size-Fits-all (a guy to to all govern all situations) can even "simplify" things (so we have a single type of general purpose), but also ends up generating various situations - in my opinion - drawbacks.
For example, if you just want a birthday date (only day, month and year), no matter what time it is or Timezone, just do something like new Date(ano, mes, dia)
. However, actually internally the time will be set to midnight in the time zone of the environment in which the code runs (be the browser, or the Node, etc.). This is because only the day, month and year is not enough to get the timestamp (for example, a date like the 14/02/2021 does not represent a single instant, you need to know what time and in which time zone it refers, to have a timestamp value). So with Date
you are always dealing with timestamps and time zones, even when you do not want or need.
An example (running on Node - remembering that set the process.env.TZ
does not work on Windows, and set it in the middle of the script works only from Node 13):
let d = new Date(2021, 1, 14); // em Date, fevereiro é mês 1 :-(
let t = Temporal.PlainDate.from({ year: 2021, month: 2, day: 14 }); // fevereiro é mês 2 \o/
console.log(d.toString(), t);
// mudar para o timezone da Califórnia
process.env.TZ = 'America/Los_Angeles';
console.log(d.toString(), t);
I create a Date
and a Temporal.PlainDate
which corresponds to February 14, 2021 - incidentally, another incredible bonus of this API: the months nay are indexed at zero - January is 1 and not zero!
Timezone configured in my environment is America/Sao_Paulo
(alias "Brasília Time"). So the Date
will have the set time for midnight in Brasilia Time, and this is the Timezone used to print the date.
But when I change the Timezone of the environment, it affects the output of toString()
(the timestamp of Date
is still the same, but this will be converted to the new Timezone when printed). Now PlainDate
is not affected by this setting as it only stores the day, month and year, no matter what time or Timezone. The output is:
Sun Feb 14 2021 00:00:00 GMT-0300 (Brasilia Standard Time) Temporal.PlainDate <2021-02-14>
Sat Feb 13 2021 19:00:00 GMT-0800 (Pacific Standard Time) Temporal.PlainDate <2021-02-14>
Remembering that the result may vary according to the Timezone that is configured in your environment.
Notice that Date.toString()
shows a different value according to the Timezone that is configured (by the way, the same occurs with the getters: d.getDate()
return 14 or 13, depending on the configured Timezone). Now PlainDate
, by having no information about schedules or timezones, is not affected by any configuration and keeps its value constant.
I believe this will avoid many mistakes that usually occur until today ("I created a Date
but it shows the wrong day/time").
In addition, the new API defines several different types, one for each situation (going in the opposite direction from the one-size-Fits-all): has one type for date (only day, month and year), another for time (only hour, minute, second), another for date and time, but without time zone, another for date/time and time zone, another for timestamp, etc.
This image (taken from documentation) gives us a good summary of the types available:
Conversion between timezones
One thing Javascript doesn’t have is conversion between different timezones.
The closest we have is something like d.toLocaleString('pt-BR', { timeZone: 'Europe/Berlin' })
, which actually returns the converted date and time values to the given Timezone. But the problem is that the format depends on the locale, and we have no control over it. And if we want the date/time in a specific Timezone, but in another format? I would have to go changing the locale until I find one, but what if none has the specific format I want? Or if I just don’t want a format, but an object containing the date and time values converted to another Timezone?
With Date
, not enough. Already the Temporal
provides for various ways to convert:
- keeping local time: I have an object representing 02/14/2021 at 10am in Brasilia Time, I want to change only Timezone (ie convert to 02/14/2021 at 10am in Japan zone, for example)
- keeping the moment: when is 14/02/2021 at 10am in Brasilia Time, what day and time is it in Japan? (A: 22h of the same day)
The API still handles more complex cases that only timezones bring to you, such as daylight savings: it allows you to handle the various corner cases which occur (when an hour is skipped, or when it occurs 2 times - in cases where the clock is set back by one hour when daylight saving time ends, for example), and allow you to view each Timezone’s change history.
Arithmetic of dates
Javascript does not currently have good support for date arithmetic. In practice, the best we can do to calculate the difference between two dates is to subtract the values from the timestamps and then do the calculations in hand.
But this is not always enough. Just to stay in a case, the difference in months is not something as trivial as it seems: for example, between 01/01/2019 and 01/02/2019 there are 31 days. Dividing by 30 and rounding, gives 1 month.
But between 01/02/2019 and 01/03/2019 there are 28 days. Dividing by 30, gives 0,93: if round down, gives zero months. But between February 1 and March 1 the difference is not one month? Then we should round up in this case?
But if it was between 01/01/2019 and 29/01/2019, the difference is also 28 days. Only between January 1st and January 29th it hasn’t been 1 month, so I can’t round up in this case. "Ah, so I just jump up if I’m not in the same month".
Then you see that between 01/01/2019 and 27/02/2019 the difference is 57 days, divided by 30 is 1,9. If you round it up, it’s 2, but between January 1st and February 27th it hasn’t been two months. And now?
With Temporal
, just do something like:
const today = Temporal.PlainDate.from('2019-01-01');
const futureDate = Temporal.PlainDate.from('2019-02-27');
const until = today.until(futureDate, { largestUnit: 'months' });
console.log(until); // 1 mês e 26 dias
// ou, se quiser apenas a quantidade de meses, use until.months
In fact, a specific type is foreseen for durations: Temporal.Duration
. This will make it easier to perform operations such as "adding days/months/years/any-other-amount-of-time to a date".
Of course, this could change the behavior of some corner cases. For example, if you add 1 month to 31 January:
let d = new Date(2021, 0, 31); // 31 de janeiro de 2021
// somar 1 mês
d.setMonth(d.getMonth() + 1);
console.log(d.toString()); // 3 de março!
In fact the "sum" is made by setting the value of the month and keeping the other fields. In the above case, we change the month to February, and the day stays at 31, but as February does not have 31 days, an adjustment is made to March 3 (the "surplus" days end up "giving overflow" and "invading" the following month).
But here comes a semantic question: if I have a date in January and we are 1 month, why is the result a date in March? Shouldn’t it be in February? This is arguable, since there is no "official" rule for date arithmetic (as it exists in mathematics) and each API implements it in a way. But the Temporal
(using the current implementation of polyfill) adjusts the date to the last day of February if the value of the day exceeds the number of days of the month:
let d = Temporal.PlainDate.from('2021-01-31'); // 31 de janeiro de 2021
d = d.add(Temporal.Duration.from({ months: 1 }));
console.log(d); // Temporal.PlainDate <2021-02-28> (28 de fevereiro de 2021)
Finally, a good summary of the possibilities is in itself documentation.
In short, the problems of working with dates will continue to exist (how to treat correctly, convert between timezones, make calculations, etc). What can be easier - I hope - is to solve these problems :-)
And remembering that Date
will not go away, including the new API provides the conversion of Date
for Temporal.Instant
.
(Off-topic) Just for the record, "silver bullet" refers to a solution or technology capable of substantially increasing the productivity of projects, it has not been found at a more "macro" level (such as the change of the programming paradigm) but significant gains at a more "micro" level there are, for example, adopting the right technology for the problem in question (as opposed to adopting a less suitable one) or using a productivity-enhancing library to solve a certain problem. There would also enter a more modern API to deal with dates (I don’t know if a class can only manage to improve).
– Piovezan