Temporal: A nine-year journey to fix time in JavaScript

(bloomberg.github.io)

340 points | by robpalmer 4 hours ago ago

75 comments

  • kemitchell 5 minutes ago ago

    > The first proposal I worked on was Promise.allSettled, which was fulfilling.

    Har har.

  • VanCoding 3 hours ago 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 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 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 3 hours ago 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 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 ago

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

      • VanCoding 3 hours ago 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 ago

          You would need to use the `reviver` parameter of `JSON.parse()` to revive your date strings to Temporal objects. As others have said, it's a simple `Temporal.from()`

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

          • Bratmon an hour ago ago

            Having to provide a complete schema of your json everywhere your json gets parsed negates the advantages of json.

          • cyral 3 hours ago ago

            I've been doing this for so long and never knew there was a reviver param, thanks - that is super useful.

        • rimunroe 2 hours ago ago

          > 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.

          I don't know if I'm missing something, but that's exactly how I'd expect it to compose. Does the following do what you wanted your snippet to do?

            Temporal.PlainYearMonth.from(JSON.parse(JSON.stringify(Temporal.PlainYearMonth.from({year:2026,month:1}))))
          
          JSON.stringify and JSON.parse should not be viewed as strict inverses of each other. `JSON.parse(JSON.stringify(x)) = x` is only true for a for a small category of values. That category is even smaller if parsing is happening in a different place than stringification because JSON doesn't specify runtime characteristics. This can lead to things like JSON parsing incorrect in JS because they're too large for JS to represent as a number.
        • gowld 3 hours ago ago

          Would a plain data object be an instance of PlainYearMonth?

          If not, that regardless of being plain data or a serialized object with functions, you'd still need to convert it to the type you want.

    • Avamander an hour ago 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 3 hours ago ago

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

      • VanCoding 2 hours ago 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 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 :)

  • julius_eth_dev an hour ago 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.

  • alanning 17 minutes ago 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-...

  • plucas 4 hours ago 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 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 ago

      Yep, JavaScript got the bad version from Java too!

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

  • the__alchemist an hour ago 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)

  • xp84 2 hours ago ago

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

  • wpollock 3 hours ago 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 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 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 ago

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

    • CharlesW 3 hours ago ago

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

      • bnb 2 hours ago ago

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

  • zvqcMMV6Zcr 4 hours ago ago

    > Safari (Partial Support in Technology Preview)

    Safari confirmed as IE Spiritual successor in 2020+.

    • WorldMaker 2 hours ago 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 29 minutes ago 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 4 hours ago ago

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

      • CharlesW 3 hours ago ago

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

  • johncomposed 2 hours ago 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 ago

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

    https://caniuse.com/temporal

    • beezlewax 21 minutes ago 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 ago

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

  • hungryhobbit 3 hours ago 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 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 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 ago

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

  • kemayo 3 hours ago 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 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 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 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 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.

  • redbell 3 hours ago ago

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

  • samwho 4 hours ago ago

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

  • ventuss_ovo an hour ago ago

    interesting point about immutability

  • sharktheone 4 hours ago ago

    Very happy for it finally being there!

  • normie3000 4 hours ago ago

    No mention of JodaTime?

  • darepublic 3 hours ago 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 29 minutes ago 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 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 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.

    • recursive an hour ago 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 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 3 hours ago ago

      Same here, this is the way

    • NooneAtAll3 2 hours ago ago

      why UTC and not epoch then?

      • SoftTalker 2 hours ago 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 ago

    What a journey!

  • ChrisArchitect 3 hours ago ago

    A good article and discussion from January:

    Date is out, Temporal is in

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

  • NooneAtAll3 3 hours ago ago

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

    • andrewl-hn 2 hours ago ago

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

  • virgil_disgr4ce 4 hours ago 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 ago

    Aside: Bloomberg JS blog? ok.

    • robpalmer 2 hours ago 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 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).

    • wiseowise 3 hours ago 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