Temporal: A nine-year journey to fix time in JavaScript (bloomberg.github.io)

334 points by robpalmer 4 hours ago

julius_eth_dev an hour ago

Nine years is a long time, but honestly it tracks with how deeply broken Date has been since Brendan Eich cargo-culted java.util.Date in 1995. The real win with Temporal isn't just immutability or timezone support — it's that PlainDate and ZonedDateTime finally give us types that match how humans actually think about time. I've lost count of how many bugs I've shipped because Date silently coerces everything to UTC instants when half the time what you actually have is a "wall clock" value with no timezone attached.

VanCoding 3 hours ago

A big step in the right direction, but I still don't like the API, here's why: Especially in JavaScript where I often share a lot of code between the client and the server and therefore also transfer data between them, I like to strictly separate data from logic. What i mean by this is that all my data is plain JSON and no class instances or objects that have function properties, so that I can serialize/deserialize it easily.

This is not the case for Temporal objects. Also, the temporal objects have functions on them, which, granted, makes it convenient to use, but a pain to pass it over the wire.

I'd clearly prefer a set of pure functions, into which I can pass data-only temporal objects, quite a bit like date-fns did it.

jayflux an hour ago

This was an intentional design decision. We wanted to make sure all the temporal types could be serialize/deserializable, but as you mentioned, you couldn't implicitly go back to the object you started with as JSON.parse doesn't support that.

Instead the onus is on the developer to re-create the correct object they need on the other side. I don't believe this is problematic because if you know you're sending a Date, DateTime, MonthDay, YearMonth type from one side, then you know what type to rebuild from the ISO string on the other. Having it be automatic could be an issue if you receive unexpected values and are now dealing with the wrong types.

There is an example here in the docs of a reviver being used for Temporal.Instant https://tc39.es/proposal-temporal/docs/instant.html#toJSON

perfmode 3 hours ago

This is a real pain point and I run into the same tension in systems where data crosses serialization boundaries constantly. The prototype-stripping problem you're describing with JSON.parse/stringify is a specific case of a more general issue: rich domain objects don't survive wire transfer without a reconstitution step.

That said, I think the Temporal team made the right call here. Date-time logic is one of those domains where the "bag of data plus free functions" approach leads to subtle bugs because callers forget to pass the right context (calendar system, timezone) to the right function. Binding the operations to the object means the type system can enforce that a PlainDate never accidentally gets treated as a ZonedDateTime. date-fns is great but it can't give you that.

The serialization issue is solvable at the boundary. If you're using tRPC or similar, a thin transform layer that calls Temporal.Whatever.from() on the way in and .toString() on the way out is pretty minimal overhead. Same pattern people use with Decimal types or any value object that doesn't roundtrip through JSON natively. Annoying, sure, but the alternative is giving up the type safety that makes the API worth having in the first place.

VanCoding 2 hours ago

It's not that much about type safety. Since TypeScript uses duck typing, a DateTime could not be used as a ZonedDateTime because it'd lack the "timezone" property. The other way around, though, it would work. But I wouldn't even mind that, honestly.

The real drawback of the functional approach is UX, because it's harder to code and you don't get nice auto-complete.

But I'd easily pay that price.

causal 2 hours ago

I'm with you on this. I worked on a big Temporal project briefly and I was really turned off by how much of the codebase was just rote mapping properties from one layer to the next.

qcoret 3 hours ago

All Temporal objects are easily (de)serializable, though. `.toString` and `Temporal.from` work great.

VanCoding 3 hours ago

That's not what I mean. Even though it is serializable, it's still not the same when you serialize/deserialize it.

For example `JSON.parse(JSON.stringify(Temporal.PlainYearMonth.from({year:2026,month:1}))).subtract({ years: 1})` won't work, because it misses the prototype and is no longer an instance of Temporal.PlainYearMonth.

This is problematic if you use tRPC for example.

flyingmeteor 3 hours ago

rimunroe 2 hours ago

gowld 3 hours ago

Avamander an hour ago

> Especially in JavaScript where I often share a lot of code between the client and the server and therefore also transfer data between them, I like to strictly separate data from logic

Which makes me wonder how it'll look like when interfacing with WASM. Better than Date?

chrisweekly 2 hours ago

It should still be possible to continue using date-fns (or a similar lib) to suit your preference, right?

VanCoding 2 hours ago

yes, sure. probably there will even pop up a functional wrapper around the temporal API occasionally. But would've been nice if it was like this from the start.

nekevss 4 hours ago

Super happy to see Temporal accepted!

Congrats to all the champions who worked super hard on this for so long! It's been fun working on temporal_rs for the last couple years :)

alanning 7 minutes ago

The Temporal Cookbook on TC39's site provides examples of how using the new API looks/feels:

https://tc39.es/proposal-temporal/docs/cookbook.html

For example, calc days until a future date: https://tc39.es/proposal-temporal/docs/cookbook.html#how-man...

...or, compare meeting times across timezones: https://tc39.es/proposal-temporal/docs/cookbook.html#book-a-...

the__alchemist an hour ago

Maybe I will be able to move away from my custom/minimal DT lib, and ISO-8601 timestamp strings in UTC. JS datetime handling in both Date and Moment are disasters. Rust's Chrono is great. Python's builtin has things I don't like, but is useable. Date and Moment are traps. One of their biggest mistakes is not having dedicated Date and Time types; the accepted reason is "Dates and times don't exist on their own", which is bizarre. So, it's canon to use a datetime (e.g. JS "Date") with 00:00 time, which leads to subtle errors.

From the link, we can see Temporal does have separate Date/Time/Datetime types. ("PlainDate" etc)

plucas 4 hours ago

Would have been interesting to connect back to Java's own journey to improve its time APIs, with Joda-Time leading into JSR 310, released with Java 8 in 2014. Immutable representations, instants, proper timezone support etc.

Given that the article refers to the "radical proposal" to bring these features to JavaScript came in 2018, surely Java's own solutions had some influence?

apaprocki 4 hours ago

I would characterize it more as Joda likely informed Moment.js, which better informed TC39 because it was within the JavaScript ecosystem. As we discussed in plenary today when achieving consensus, every programming language that implements or revamps its date time primitives has the benefit of all the prior art that exists at that instant. TC39 always casts a wide net to canvas what other ecosystems do, but isn't beholden to follow in their footsteps and achieves consensus on what is best for JavaScript. So my view is this more represents what the committee believes is the most complete implementation of such an API that an assembled group of JavaScript experts could design over 9 years and finalize in 2026.

mrkeen 3 hours ago

Yep, JavaScript got the bad version from Java too!

https://news.ycombinator.com/item?id=42816135

xp84 an hour ago

They travelled through time (forward, at 1X) by nine years to do this for us. I appreciate it.

wpollock 2 hours ago

> "It was a straight port by Ken Smith (the only code in "Mocha" I didn't write) of Java's Date code from Java to C."

This is funny to me; Java's util.Date was almost certainly a port of C's time.h API!

bnb 4 hours ago

Can't wait for it to land in the server-side runtimes, really the last thing preventing me from adopting it wholesale.

WorldMaker 2 hours ago

Deno has had it behind the `--untable-temporal` flag for quite a few Minor versions now and the latest Minor update (because of TC-39's Stage 4 acceptance and V8 itself also marking the API as Stable) removed the requirement for the flag and it is out of the box.

apaprocki 4 hours ago

Node 26! Only a matter of time... :)

CharlesW 3 hours ago

FWIW, I've been using it server-side via the js-temporal polyfill for some time, no issues.

bnb 2 hours ago

ooh I'd not seen that yet, will have to take a look.

zvqcMMV6Zcr 4 hours ago

> Safari (Partial Support in Technology Preview)

Safari confirmed as IE Spiritual successor in 2020+.

WorldMaker 2 hours ago

Slower to implement new features, but still implementing them, just makes it the new Firefox. IE's larger problem was how popular it had been before it stopped implementing new features. It was like if Google got bored with Chrome and decided to stop all funding on it. People would be stuck on Chrome for years after that investment stopped because of all the Chrome-specific things built around it (Electron, Puppeteer, Selenium, etc and so forth).

Right now the world needs a lot more Safari and Firefox users complaining about Chrome-only sites and tools than it does people complaining about Safari "holding the web back". Safari's problems are temporary. Chrome is the new Emperor and IE wasn't bad because it stopped, it was bad because it stopped after being the Emperor for some time. People remember how bad the time was after the Empire crumbled, but it's how IE took so many other things down with it that it is easier to remember the interregnum after IE crumbled than to remember the heyday when "IE-only websites are good enough for business" sounded like a good idea and not a cautionary tale.

nchmy 19 minutes ago

> Right now the world needs a lot more Safari and Firefox users complaining about Chrome-only sites and tools than it does people complaining about Safari "holding the web back".

There wouldn't be Chrome-only sites and tools if Safari wasn't holding the web back (no "quotes" needed, as that's precisely what they're doing).

> Safari's problems are temporary.

What are you talking about? They've been woefully behind for like a decade. Here's an excellent article on the topic: https://infrequently.org/2023/02/safari-16-4-is-an-admission...

And an entire series: https://infrequently.org/series/browser-choice-must-matter/

cubefox 3 hours ago

2026 A.D., still no support for native date pickers in mobile Safari.

CharlesW 3 hours ago

Safari for iOS got native date pickers in 2012, and desktop Safari got them in 2021.

johncomposed an hour ago

As a side note, huge fan of Promise.allSettled. When that dropped it cleaned up so much of the code I was writing at the time.

tracker1 an hour ago

Looking at the caniuse results... f*king Safari (and Opera)...

https://caniuse.com/temporal

beezlewax 11 minutes ago

And I have to support safari while dealing with all the problems that are mentioned in this article. Maybe there is a polyfill.

agos an hour ago

I usually am not too harsh on Safari on implementation of new features but this is a bummer, and reflects poorly on them

kemayo 2 hours ago

> Developers would often write helper functions that accidently mutated the original Date object in place when they intended to return a new one

It's weird that they picked example code that is extremely non-accidentally doing this.

SoftTalker 2 hours ago

It's been a while since I worked in JS but dealing with dates/times, and the lack of real integer types were always two things that frustrated me.

philipallstar 3 hours ago

> have to agree on what "now" means, even when governments change DST rules with very little notice.

I didn't spot how Temporal fixes this. What happens when "now" changes? Does the library get updated and pushed out rapidly via browsers?

WorldMaker 2 hours ago

Right, browsers own it instead of websites needing to rebuild Moment.js bundles. Additionally, most browsers pass the ownership further to the user's OS as the IANA timezone database is a useful system-level service and best updated at the cadence of OS "required" updates.

nekevss 3 hours ago

Typically time zone data is updated in IANA's time zone database. That data would need to be updated in the implementation. In this case, the browser would need to update their time zone data.

hungryhobbit 3 hours ago

From the article:

    const now = new Date();
The Temporal equivalent is:

    const now = Temporal.Now.zonedDateTimeISO();
Dear god, that's so much uglier!

I mean, I guess it's two steps forward and one step back ... but couldn't they have come up with something that was just two steps forward, and none back ... instead of making us write this nightmare all over the place?

Why not?

    const now = DateTime();

sourcegrift 2 hours ago

If you give me your background I'll explain in longer terms but in short it's about making the intent clear and anyone who understands s modicum of PL theory understands why what's a constant is so and what's a function is so.

Bratmon an hour ago

I'm excited for this conversation. If you see someone respond to a developer ergonomics complaint with "If you give me your background I'll explain in longer terms... anyone who understands s modicum of PL theory" you're about to see some legendary bullshit.

It's like witnessing a meteor shower!

themafia 2 hours ago

I'm a programmer. I'm a human. Perhaps we should also allow for some "human theory" inside our understanding.

redbell 3 hours ago

Oh, for a second, TeMPOraL (https://news.ycombinator.com/user?id=TeMPOraL) came to my mind!

samwho 4 hours ago

Thanks for linking to my silly little quiz in the article! :)

ventuss_ovo an hour ago

interesting point about immutability

sharktheone 4 hours ago

Very happy for it finally being there!

normie3000 4 hours ago

No mention of JodaTime?

darepublic 3 hours ago

My playbook for JavaScript dates is.. store in UTC.. exchange only in UTC.. convert to locale date time only in the presentation logic. This has worked well for me enough that Im skeptical of needing anything else

WorldMaker 19 minutes ago

Storing in UTC is lossy. You've lost information about the event's original UTC offset, at the very least, and probably also its original time zone. Most backends today have good ways to round-trip offset information, and still compare dates easily (as if they were normalized to UTC). Some backends can even round-trip timezone information in addition to offsets.

It's easy not to feel that loss as a big deal, but captured offsets can be very helpful for exactly debugging things like "what time did this user think this was?" versus time zone math (and DST lookups) from UTC. It can help debug cases where the user's own machine had missed a DST jump or was briefly on a different calendar or was traveling.

But a lot of the biggest gains in Temporal are the "Plain" family for "wall clock times"/"wall calendar dates" and breaking them apart as very separate data types. Does a UTC timestamp of "2026-02-01 00:00:00Z" mean midnight specifically and exactly or where you trying to mark "2026-02-01" without a time or timezone. Similarly I've seen data like "0001-01-01 12:10:00Z" mean "12:10" on a clock without the date or timezone being meaningful, but Temporal has a PlainTime for that. You can convert a PlainDate + a PlainTime + a Time Zone to build a ZonedDateTime, but that becomes an explicit process that directly explains what you are trying to do, versus accidentally casting a `Date` intended to be just a wall-clock time and getting a garbage wall-clock date.

ibejoeb an hour ago

For recording instantaneous events, that's usually sufficient. It's often not enough for scheduling. You can always present UTC or any other zone relative to some other zone, but you need to know that zone. Maybe you're going to a conference in another region and you want to know the time of a talk in that zone because that's more important than your zone. You either need to couple the zone with the time itself, or you need to refer to it. There are good reasons either way. Having an atomic time+zone type is basically trading space for time. When its embedded, you can just use it, which can be better than assuming UTC and then looking up the zone based on, say, the location of the venue.

andrewl-hn 2 hours ago

The only time you need local dates is for scheduling. Stuff like “Report KPIs for each shift. Shifts start at 8:00 local time.”, or “send this report every day at 10:00 local time”, or “this recurring meeting was created by user X while they were in TimeZone Z, make sure meetings follow DST”.

Outside of scheduling UTC is the way.

masfuerte an hour ago

> Report KPIs for each shift. Shifts start at 8:00 local time.

To represent this you probably don't want a local date. Plain times [1] and plain date/times [2] are a better fit.

[1]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Refe...

[2]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Refe...

recursive an hour ago

It does work quite well. Sometimes you need a time zone to go with it. It might not be common, but sometimes you need to know the local time in a particular zone, which is not necessarily where the user is. I work on software that works with local times in arbitrary time zones. We submit data in a schema over which we have no control, which must include such local times that may or may not be in the time zone of the server or the current client machine.

themafia 2 hours ago

I have a scheduling system that allows users to specify recurring events. "Every Monday at 2pm." Which needs to be understood in the native timezone of that user and needs to be capable of being displayed in that timezone for all viewers or optionally in the native timezone of the viewing user.

Temporal is a blessing.

lpa22 2 hours ago

Same here, this is the way

NooneAtAll3 2 hours ago

why UTC and not epoch then?

SoftTalker 2 hours ago

Epoch (a/k/a "unix timestamps") are OK when you just need an incrementing relative time. When you start converting them back and forth to real calendar dates, times, with time zones, DST, leap seconds, etc. the dragons start to emerge.

A lesson I learned pretty early on is always use the date-time datatypes and libraries your language or platform gives you. Think very carefully before you roll your own with integer timestamps.

jon_kuperman 4 hours ago

What a journey!

NooneAtAll3 2 hours ago

so Temporal is copying cpp's std::chrono?

andrewl-hn 2 hours ago

More like a copy of Java’s JSR310, which in turn took many years to get right.

ChrisArchitect 3 hours ago

A good article and discussion from January:

Date is out, Temporal is in

https://news.ycombinator.com/item?id=46589658

virgil_disgr4ce 4 hours ago

Pretty big fan of Temporal. Been using the polyfill for a while. Very nice to use a modern, extremely well thought-through API!

ChrisArchitect 3 hours ago

Aside: Bloomberg JS blog? ok.

robpalmer 2 hours ago

Yep. You can learn more about why we created this new blog here:

  https://bloomberg.github.io/js-blog/post/intro/
I hope you like it ;-)

And if it seems like a surprise, you can blame me for not publicising this kind of content earlier given how long we've been working in this area. Thankfully Jon Kuperman and Thomas Chetwin (plus others) found the time and energy to put this platform together.

deepsun 3 hours ago

Bloomberg has a pretty large software engineering department, including a lot of offshore contractors. Similar to Walmart Labs that does cool stuff as well, despite being part of a retail chain (retail industry typically sees SWEs a cost, not asset).

ChrisArchitect 3 hours ago

oh, just meant it was a new tech blog from them.

jon_kuperman 19 minutes ago

wiseowise 3 hours ago

What surprises you? Terminal UI is written in JS using Chromium. It’s not just plain Chromium, but it’s still funny that it’s pretty much same approach as universally (according to HN and Reddit) hated Electron.

https://youtu.be/uqehwCWKVVw?is=wBijGwdD2k2jIOu7