Temporal: Getting started with JavaScript's new date time API
2ality.com
2ality.com
However, I hate working with dates because many APIs make question about dates a slog.
Consider the following for the poor people on the dark side of the planet (UTC + something). If any application only saves the date and not the time and you have dates like 2021-06-28T00:00:00 or something like that.
Now it gets saved in a DB as 2021-06-27Z23:00:00, because the user lives in UTC+1-land. It is correct but makes my blood boil and propably causes a lot of errors, even in apps that set their time zone correctly. How often have I seen implementations that just /(.*)T.*/ match the date and create a bug.
I have no solution for this, but I wasted too much time on problems like this. Such errors also notoriously evade unit tests...
This seems decent from a quick look. But let us be honest, it has to compete with moment.js, not with Date().
One of the earliest axioms we drill into programmers is "use UTC, use date-time types". Don't store dates as strings, use your database's DateTime type.
If your database has a Date sans-Time type, use that. But many databases don't, and that's where the axiom starts breaking down, because its very natural to say "well, I'll use the next best thing, a DateTime type". But this is wrong! Its far, far better to simply use a YYYY-MM-DD string. I cannot stress how much better this is, but its not obvious why its better to someone who has had that axiom drilled into their heads, and has never ran into this situation before.
Basically when you tried to purchase a ticket, their frontend would take the date-of-flight, and adjust it by the user's timezone. So a ticket for 2015-01-30 00:00 would become 2015-01-29 20:00, or whatever. The dates in the request did not match what was expected for your booking code, and 500 error.
As for how it breaks, you have a booking for 8pm the day after the start of summer time. You stored the date/time in UTC. Government changes the start of summer time. Your database now thinks it's at 7pm because it uses UTC.
Here's some examples:
https://codeofmatt.com/on-the-timing-of-time-zone-changes/
Also the EU is seriously debating getting rid of clock changes, and it comes up in the UK semi-regularly.
Your database will not fix your bad data when that happens.
I work on a registration and reservation system for rowing boats in my rowing club https://github.com/dsroklub/roprotokol
The only time that matters is whatever the clock in the clubhouse shows (Danish time) If you sign out a boat from the UK (or more realistically from a laptop that thinks it is in the UK) at 14:00 it is still 14:00 Danish time. This is surprisingly difficult to achieve with JS Date object, browsers and popular JS components.
So the Temporal Plain time looks promising.
And especially the explicit timezones. Because even without timezone you need to handle DST. For example my system needs to know that if you start a 3 hour trip at midnight a certain day in the spring, you are expected back at 04:00.
I just hope that browsers and components will support is properly.
* Your meeting is scheduled for 2pm tomorrow.
* Oh oh! Your meeting got bumped to the next day.
* Daylight savings switched overnight.
Some things are measured in relative time. Some things are measured in absolute time. Never confuse the two in your database.
Because it’s not bad data, HaHaOnlySerious. I tell developers that if the database attribute accepts a value it must be in the domain of allowable values. Often people refuse check constraints in order to avoid expressing business logic in the database.
If you're storing future datetimes that semantically represent wall clock time, you need to store the locale time plus the full time zone (such as America/New_York) so that your program does the right thing in response to any common regulatory changes that happen after you store the value. Storing the time zone abbreviation (e.g., EST) is inadvisable, as computers sometimes care whether you asked for EST vs EDT. Storing the time offset (e.g., -500) is incorrect, as it has the same pitfalls as storing UTC - you're precomputing the locale's expected time offset at storage time, and your data won't automatically be corrected if time regulations change.
If you're storing historical timestamps, UTC is fine because you can safely convert it to whatever time zone you want to display, knowing that changes to time zone / DST regulations tend not to affect the past.
At this point in the process first normal form flies out the window. Trying to generalize too much can lead you down some weird garden paths. If it looks like you need a function to validate an prospective column value then you probably need to model the value as a relation corresponding to the function parameters. Then you can make it into a foreign key constraint and get on with your project.
I truly appreciate the efforts of those who attempt to expand the utility of datetime value representation to perfect a wider variety of denotational semantics. But with a relational model it may be better to delegate to simpler abstraction sufficient to the specific case.
It's usecase dependent so it seems like the application should deal with this, at which point persisting in UTC is still a reasonable choice.
If you store this in UTC and the timezone changes, you end up converting to the wrong time.
So for future events you should store local time with a location you can map to a timezone.
But if you want to have precise date time far in the future you must not use UTC based milliseconds representations unless you want to check for necessary database migrations every time the time zone definitions change.
My point in mentioning the solstice is that while some events are at “2pm”, others are “when something happens”.
Sure, UTC is not suitable for conducting astronomy. It is suitable for working with human friendly times, since it is the foundation of timezones.
(1) The time in the local timezone (+ the timezone itself) (2) The UTC conversion (3) The version of the IANA database used to compute (2)
Then you can use (2) the UTC time for most computations/queries, but if a timezone changes then you can use (3) to tell which UTC dates need to be updated, and (1) to recompute the new correct version of (2).
No mucking around with IANA databases, everything's easy to program, server usually keeps itself up-to-date.
"Always use UTC" works if you really, really always use UTC.
Never use UTC for dates. Like OP says, a simple YYYYMMDD string is better.
Temporal is very well designed thanks to building on years of prior art. Give it a try and you'll realize that it leads you to ask those questions the right way.
Seems like everyone has to stub their own toe on this to get it.
Except, there is the cost intrinsic to this discussion, where going back to strings makes sense. That doesn't mean that decision comes with no cost. If you store someone's birthday as "1990-02-05", then need to answer the question "who has birthdays in the next month", that's actually essentially impossible to do in practically any database query engine. You have the problem of both ignoring the year field of that string (ok, so, maybe we just store "02-05" because the year doesn't matter for birthdays, moreso just 'dates of birth', we can denormalize)... but even beyond that, the closest most if not all engines could get is generating an array of every upcoming day in application code (02-05, 02-06, 02-07, etc) then matching on each. Alternatively, just sort on the field (sans year) then filter in application code, but that's also not happening in the database, which has negatives, and moreover, this would suddenly start failing across year boundaries, but you can special-case that by running a second query starting at 01-01 if the first one didn't return enough results...
There's no easy solution. Time is just really hard. Its good to have as many tools as possible in your belt, but with that you have to know when to reach for one versus another. There isn't one solution to any of these problems.
You'll have to do a CHECK on the YYYY-MM-DD string anyway to protect your database from bad data, so why not just do it on the native type?
The native type is likely to be more compact, performant, consistent across the database (with strings, some columns may be YYYY-MM-DD, some may be YYYY.MM.DD etc.) and more readily convertible to the client's type system.
Moment.js is deprecated: "Moment.js is a legacy project, now in maintenance mode. In most cases, you should choose a different library."
_"We now generally consider Moment to be a legacy project in maintenance mode. It is not dead, but it is indeed done."_
> We recognize that many existing projects may continue to use Moment, but we would like to discourage Moment from being used in new projects going forward.
What a strange thing to say. Baffling.
It's deprecated because programming standards have changed, the team agrees with those standards and nevertheless feels it would be too much work to incorporate them. Not "there's nothing more to add" but "we choose to prioritize stability over new features"
Nevertheless the handling has to be at least on a similar level.
Well shit, that's one of the biggest things I want from a time library. There are about 15 different forms of human readable string formats that various APIs give you in their JSON responses and if they don't solve that, they haven't solved the biggest pain point.
I guess congratulations for not solving the problem and instead making Javascript even more complicated to use by introducing yet another incomplete date library that skirts around perhaps the most important feature and which millions of hippie JS devs will jump on the bandwagon to use.
Also you mention ISO-8601 but this document does not even describe how to input an ISO-8601 string.
I agree with you on the rest though. And I would love to be able to enforce ISO 8601 for text entry of dates.
I literally just spent hours going around a code base fixing this exact problem.
Our back-end is a legacy, 20+ year old database where it was decided to save dates without a timezone.
console.log(new Date("2011-09-24"));
is:
Fri Sep 23 2011 20:00:00 GMT-0400 (Eastern Daylight Time)
These dates were all "thought" to be EST, not UTC. Good times.
More fun examples here:
Why would you ever do this? Save it as a JSON date. coolDate.toJSON() would give a timezoned human parseable date. Just parse it back to the users locale with a built in Date constructor. You can do milliseconds UTC but someone like my younger stupid self would screw it up like most people.
No "which time zone" confusion necessary.
Safari: "2021-06-28T22:59:18.366Z"
Chrome: "2021-06-28T23:01:09.131Z"
Firefox: "2021-06-28T23:00:14.385Z"
The three that I strive to support all utilize this format. YYYY-MM-DDTHH:mm:ss.sssZ. Looking on MDN this seems to call toISOString() which returns a simplified extended ISO format date.
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Refe...
You ignored the officially named format and instead settled on the officially named format. Those are ISO8601 timestamps, specified in UTC time. (The Z at the end means there's (Z)ero timezone offset from UTC). If you're going to talk about a standard, its worth knowing its name.
Each of them fall on two separate dates, depending on time zone.
A timestamp is the number of seconds or milliseconds or units like 1624938650 seconds from epoch UTC. What I posted above are human parseable datetimes in ISO 8601. Each of them is a point in time that depending on the locale and tz fall on two separate dates but the most important aspect is that it doesn't matter whether I'm using them in my DB or on a user's machine in a different locale and tz. That's the point.
I've settled with storing Zulu time and in a separate field the timezone offset of the event in seconds.
- Months start at 0
- Converting from string datetime to a datetime object will automatically convert the time to the local tz
Java eventually torched its date/time API not once, but twice: the JDK 1.1 addition of Calendar that deprecated most of the naïve methods on Date, and JDK 8 adding java.time.*, which is roughly the modern model of date/time APIs.
IIRC (and it's been a while, so I may well not RC), specifies that 1 is Monday, 2 is Tuesday, ..., 6 is Saturday and both 0 and 7 are Sunday. And the reason that looks weird is that there is (IIUC) a genuine difference between western nations as to if a week starts o Sunday or Monday.
Which, you know, sucks. But, also makes it somewhat easy to make a C array that you can index with a (.tm_wday%7) to easily get the day of the week.
Which may also be the reason why months start at 0.
1. Look up some attribute or property of the month in a table (number of days in the month in a non-leap year, name of the month, list of fixed holidays in the month, etc).
2. Determine if the month is before or after a different month.
3. Determine how many months one month is after or before another month.
4. Determine which month is N months before/after a given month.
5. Print the month number for a human.
In languages that use 0-based indexing:
#1 is more convenient with 0-based months.
#2-4 only need that the month numbers form an arithmetic progression, so are indifferent to where we start counting.
#5 is more convenient with 1-based months.
So it comes down to is it better to have your low level date functions require that you have to remember to subtract 1 whenever you do something from #1, or to add 1 whenever you do something from #5?
I'd expect most programs do a lot more #1 than #5, so the code is going to be cleaner if you go with 0-based months.
I'm glad most modern date APIs use 1-based numbers to represent the month and the day (not to mention the year from 1 AD onwards).
The first two cases basically exist internally within the date-time library, so it's not a particularly common example for end users to be doing. It's also not particularly hard to do #1 with 1-based months, because you can either subtract one before indexing, or you can just have entry 0 be a null-ish value.
The thing is, month names already have an implicit numerical mapping: ask anyone, even most programmers, what number to assign to the month of January, and you're likely to get 1. So an integer representing a month name is going to be presumed to be 1-based unless documented otherwise, and having it return 0 instead is going to confuse more programmers than not.
In other words, the trade-off isn't "is the code going to be cleaner for this case or that case" but rather "do we make an API that is going to confuse everyone's first impression of it, or do we make the code marginally more complex in one specific scenario?"
Couldn't you make a similar argument then that when representing English text, 'A' should be 1? That's the number most people would give when asked to assign numbers to the alphabet. Not many people are going to say 65 which is 'A' in ASCII and Unicode.
Programs are not people.
I generally prefer, and believe it leads to less bugs, to represent things in programs in the ways that best fit the operations that the program will do doing on them, translating between that representation and external representations upon input and output.
It actually is!
>>> hex(ord('A'))
'0x41'
>>> hex(ord('a'))
'0x61'
The five-bit intuitive numerical mapping of the letter is prefixed with the bits 10 (for uppercase) or 11 (for lowercase). A similar thing happens with digits, where the four-bit intuitive numerical mapping of the digit is prefixed with the bits 011.For letters, this leads to a "hole" at 0x40 and 0x60. Instead of making the letters zero-based (that is, 'A' being 0x40 and 'a' being 0x60), they decided to keep the intuitive mapping (which starts at 1), and fill the "hole" with a symbol.
These are absolutely not exclusive. 0indexing months was probably intentional, and was without a doubt a mistake.
This happens in C# too:
DateTime.Parse("2021-06-28T12:00:00Z").Hour
Outputs 5 instead of 12 (for me, US Pacific) due to a default conversion to local time. It does have an opt-in flag to "adjust to UTC" though.Not intuitive at all, at least to me...
Wait… how is this not a bug according to both Microsoft's own spec[0] and ISO 8601[1]? The Z specifically means this time is in UTC.
The behavior is not at all what I would expect from reading the docs [0]:
> A string that includes time zone information and conforms to ISO 8601. In the following examples, the first string designates Coordinated Universal Time (UTC), and the second designates the time in a time zone that's seven hours earlier than UTC:
> "2008-11-01T19:35:00.0000000Z"
> "2008-11-01T19:35:00.0000000-07:00"
[EDIT] I get it now: it's parsing it right, it's just that it's then putting it into a datetime object that's in the local timezone – which is what I would expect. The alternative would be counterintuitive to me.
[0] https://docs.microsoft.com/en-us/dotnet/api/system.datetime....
[1] https://en.wikipedia.org/wiki/ISO_8601#Coordinated_Universal...
Further down the page:
> If s contains time zone information, this method returns a DateTime value whose Kind property is DateTimeKind.Local and converts the date and time in s to local time. Otherwise, it performs no time zone conversion and returns a DateTime value whose Kind property is DateTimeKind.Unspecified.
> new Date().getYear()
121
Of course this is avoided by using getFullYear(), but I've always wondered why a language that came out in 1995 had a function that returned two-digit years.I think this makes sense in the context of client side javascript whose sole point is to present UI to the user. The dates aren't necessarily converted to local TZ (the date will always remain in the same and fixed point in time), but rather any output is local to the timezone.
I think this is more good than bad IMHO, given the intention of JS is to build (or augment) UIs. It's just not great that it's hard to do anything other than that.
This is what we were all waiting for.
edit: The dayOfWeek read-only property gives the weekday number that the date falls on. For the ISO 8601 calendar, the weekday number is defined as in the ISO 8601 standard: a value between 1 and 7, inclusive, with Monday being 1, and Sunday 7. For an overview, see ISO 8601 on Wikipedia.
They lost me at “ date time values without time zones.”
I’m mostly kidding with the second line. Working with third-party React calendar components requires doing some moment.js math and then hacking off the last few characters of the .toISOString() to make the calendar happy. So I can see how it’s a very practical addition. Please don’t misuse it.
This is rather useful for date of birth, which shouldn't be set to a time zone in most use cases (it can get rather confusing if you do).
``` new Date("1/1/2021").toLocaleDateString() ```
If you want it to return the same DOB regardless of local timezone, you have to pass a timezone to the Date constructor, which is not super obvious. This kind of bug is also unlikely to get caught by unit tests unless you’re testing date formatting for different timezones, which you probably wouldn’t do if you’re not aware of the footgun to begin with.
Do people really not know the difference between internal representation and serialisation? Are we even speaking the same language? Truly baffled by this.
How many days is in the delta 1 hour? 1? or 0?
Midnight is zero. What's confusing about the number of days starting from 0 too?
Why do we use months? No sane internal representation stores the number of months. They are completely and utterly useless and exist only in printed representations of dates.
The downside is that their full calendar is somewhat more complicated, so they would say that today is (checks) 13.0.8.11.11 8 Chuwin 9 Sek, and tomorrow is 13.0.8.11.12 9 Eb' 10 Sek (that Tzolkin calendar component doesn't work like our months do!).
A case can be made for either 0 (for consistency with time representation) or 1 (to go with the defacto standard of how most if us bipedal meatsacks process dates) but there must have been alcohol or something stronger involved when the current date object came into being.
Why's that?
But as I mentioned in another comment, really you should be touching the internals of the datetime object at all. It's a mistake to do something like `myDatetime.day += 1`. You should instead do `myDatetime + timeDelta(days=1)` or something.
So it shouldn't matter whether it's 0-based or 1-based at all. But since it apparently does (I don't know why), then it might as well by 0-based like everything else.
UTC is what people normally mean when they refer to GMT or Universal Time. It has a steady definition for a second based on TAI, and is adjusted by adding leap seconds to keep it within .9 seconds of UT1.
Every day the International Earth Rotation Service publishes values for UT1-UTC based on actual observations as well as other Earth Orientation Parameters. They are also responsible for determining and announcing in advance of when a leap second will be added.
E.g.: https://www.iers.org/IERS/EN/DataProducts/EarthOrientationDa... See "finals.all IAU2000"
GMT does not exist as a separate thing any more. It was the timescale maintained by the Greenwich observatory, but their timescale matched UTC since the rubber seconds era in the 1960s, and in 1972 Greenwich time became UTC with leap seconds. The Greenwich observatory as an institution does not exist any more (there’s a museum where it used to be but it isn’t a working national scientific laboratory). GMT is now an informal synonym for UTC that still appears in things like British law for historical reasons.
What is TAI?
https://twitter.com/jodastephen/status/1393146501073973248
He has opened a few issues along the way, trying to help TC39 learn from Joda's experience, with varying results:
https://github.com/tc39/proposal-temporal/issues?q=is%3Aissu...
> Sorry to be blunt, but the current approach really is a terrible one.
That is blunt, and would require my utmost restraint to avoid falling into the trap of getting overly defensive if I were the proposal authors. Which is a shame, because he makes a good point.
That said, it is also reassuring to see that they did actually consider his point, and I can follow their motivation [2]. I'm not sure whether I'd have made the same decision, but it's certainly a valid one to make.
[1] https://github.com/tc39/proposal-temporal/issues/1454
[2] https://tc39.es/proposal-temporal/docs/calendar-review#why-a...
For example, month is a number between 1 and 14, where 1 through 12 are Jan-Dec, but a month of '13' means "Odd Months" and month '14' means 'Even Months', and there's an explicit 'Not specified' for things you might want to run every month.
Similarly there are special values for 'Even Days' and 'Odd Days', and special values for Week of Month: 1-7(1), 8-14(2), 15-21(3), 22-28(4), 29-31(5), 'Last 7 Days of the month'(6), and 'Unspecified'(255)
I like that there's nice compact and standard ways to express and serialize these common choices. I wish there was something like that included in temporal but it doesn't look like there is?
Falsehoods programmers believe about time
It’s finally in the mainline release now as of version 14.1!
> Although Temporal is currently Stage 3 in order to gather feedback from implementers, implementers must ship Temporal support behind a feature flag until we finish work with IETF to standardize the string formats used for calendar and time zone annotations
Well shit, that's one of the biggest things I want from a time library. There are about 15 different forms of human readable string formats that various APIs give you in their JSON responses and if they don't solve that, they haven't solved the biggest pain point.
I guess congratulations for not solving the problem and instead making Javascript even more complicated to use by introducing yet another date library that skirts around perhaps the most important feature.
Does anyone know how the canonical cities/regions associated with a time zone were determined? It seems kind of arbitrary. Wouldn’t “America/California” or “America/Sacramento, CA” make more sense?
For most zones, it's the largest city in a region where the timekeeping has been the same since 1970. This can cross state or national boundaries.
https://en.wikipedia.org/wiki/Tz_database#Location
> Country names are not used in this scheme, primarily because they would not be robust, owing to frequent political and boundary changes. The names of large cities tend to be more permanent...
> Usually the most populous city in a region is chosen to represent the entire time zone, although another city may be selected if it are more widely known, and another location, including a location other than a city, may be used if it results in a less ambiguous name.
Basically: globally, regions and countries change, but cities are much slower to change. They don't just pick up and move like borders can.
Kotlin is more like a mixture of Typescript and/or Reason (or whatever the name is now)
Groovy was that flash-in-the pan idea that had some really cool ideas.
But then again, I'm not sure if it really inspired any real language changes to Java like Kotlin has. CoffeeScript did a TON of inspiration for ES6... so heck, maybe Kotlin IS the CoffeeScript
I hope Kotlin survives though.