Top
Best
New

Posted by robpalmer 9 hours ago

Temporal: A nine-year journey to fix time in JavaScript(bloomberg.github.io)
433 points | 153 commentspage 4
NooneAtAll3 7 hours ago|
so Temporal is copying cpp's std::chrono?
andrewl-hn 7 hours ago|
More like a copy of Java’s JSR310, which in turn took many years to get right.
ChrisArchitect 7 hours ago||
Aside: Bloomberg JS blog? ok.
robpalmer 6 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 7 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 7 hours ago||
oh, just meant it was a new tech blog from them.
jon_kuperman 5 hours ago||
Yes! Brand new!
wiseowise 7 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

ChrisArchitect 7 hours ago||
A good article and discussion from January:

Date is out, Temporal is in

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

patchnull 8 hours ago||
[flagged]
Waterluvian 8 hours ago||
The worst are methods that both mutate and return values.

I know this gets into a complex land of computer science that I don’t understand well, but I wish I could define in TypeScript “any object passed into this function is now typed _never_. You’ve destroyed it and can’t use it after this.” Because I sometimes want to mutate something in a function and return it for convenience and performance reasons, but I want you to have to reason about the returned type and never again touch the original type, even if they are the same object.

thayne 6 hours ago|||
> any object passed into this function is now typed _never_. You’ve destroyed it and can’t use it after this.

That is basically what affine types are. Once the value is "consumed" it can't be used again.

In rust, this is expressed as passing an "owned" value to a function. Once you pass ownership, you can't use that value anymore.

And having used it in rust, I wish more languages had something like that.

kibwen 5 hours ago||
> And having used it in rust, I wish more languages had something like that.

Same. I'm at the point where I feel like copy-by-default semantics are one of the ancient original sins of programming languages. Single-ownership is so, so useful, and it's trivial to implement and not at all difficult to understand (especially compared to something like Rust's borrow checker).

Aurornis 7 hours ago||||
> but I wish I could define in TypeScript “any object passed into this function is now typed _never_.

Having explicit language to differentiate between pass by reference and pass by value avoids this confusion. It requires a little more thought from the programmer but it’s really minimal once you internalize it.

Rust takes this a step further with an explicit ownership and borrowing model. The compiler will refuse your code if you try to write something that that violates the borrow checker. This is endlessly frustrating to beginners but after adapting your mind to ownership safety you find yourself thinking in the same way in other languages.

I always found real-world JavaScript codebases frustrating because there was so much sharing that wasn’t entirely intentionally. It only got fixed when someone recognized a bug as a result.

Waterluvian 7 hours ago||
Yeah exactly. That's what I've loved about Rust and hated about real-world JS. I end up having to reason about an entire case that might not be real at all: does this function mutate what I'm passing it? Should I eagerly deep copy my object? UGH.
stephbook 6 hours ago||
Just call "Object.freeze()" before "return" in your function.
cdmckay 5 hours ago||
That only goes one level deep so it’s not much of a guarantee
vimwizard 7 hours ago||||
Rust ownership model ("stacked borrows" I believe it's called) is basically this
kibwen 5 hours ago|||
Single-ownership ("affine types") is a separate concept from a borrow checker. Your language doesn't need a borrow checker (or references at all) to benefit from single-ownership, though it may make some patterns more convenient or efficient.
ChadNauseam 5 hours ago||
rust would be pretty unusable without references. affine lambda calculus isn’t even turing complete. however, you’re right that a borrow checker is unnecessary, as uniqueness types (the technical term for types that guarantee single ownership) are implemented in clean and idris without a borrow checker. the borrow checker mainly exists because it dramatically increases the number of valid programs.
kibwen 2 hours ago||
Supporting single-ownership in a language doesn't mean you can't have opt-in copyability and/or multiple-ownership. This is how Rust already works, and is independent of the borrow checker.

If we consider a Rust-like language without the borrow checker, it's obviously still Turing-complete. For functions that take references as parameters, instead you would simply pass ownership of the value back to the caller as part of the return value. And for structs that hold references, you would instead have them hold reference-counted handles. The former case is merely less convenient, and the latter case is merely less efficient.

NooneAtAll3 7 hours ago||||
I think it's the other way around - he's projecting rust as what he wants
LoganDark 7 hours ago|||
Actually there's tree borrows now. https://www.ralfj.de/blog/2023/06/02/tree-borrows.html
magnio 7 hours ago||||
What you are describing is linear (or affine) types in academic parlance, where a value must be used exactly (or at most) once, e.g., being passed to a function or having a method invoked, after which the old value is destroyed and not accessible. Most common examples are prolly move semantics in C++ and Rust.
ecshafer 7 hours ago||||
ruby has the convention of ! for dangerous destructive or mutating methods. This is something that I wish would spread around a bit.

For example:

# Original array

array = [1, 2, 3]

# Using map (non-destructive)

new_array = array.map { |x| x * 2 }

# new_array is [2, 4, 6]

# array is still [1, 2, 3] (unchanged)

# Using map! (destructive)

array.map! { |x| x * 2 }

# array is now [2, 4, 6] (modified in-place)

wiseowise 7 hours ago||
> convention

Is the keyword. Anything that should never be broken isn’t a convention. There’s no better convention than compiler error.

goatlover 7 hours ago||
Because Ruby is a dynamic language which mutates state. That isn't considered wrong or bad in those kinds of languages, just a way to make sure the programmer knows they're doing that. Not every PL tries to live up to the ideals of Haskell.

If you don't want an object mutated in Ruby, you can freeze it.

idle_zealot 6 hours ago||
I don't think they're saying it shouldn't be possible to mutate arguments, just that the ! convention should be enforced. The Ruby runtime could, for instance, automatically freeze all arguments to a function that doesn't end with a !. That way all code that correctly follows the mutation naming convention will continue to work, and any development who doesn't know about it will quickly learn when they try to mutate an argument and get an error. Ideally a helpful error telling them to add the !.
xp84 6 hours ago||
That's really interesting. Although my worry is the freezing having bad effects down the line after the function returns.

  a = [1, 2]
  
  def check_this(arr)
    raise "doesn't start with 1" unless a.first == 1
  end

  check_this(a)
  
  a << 3 # Raises "FrozenError (can't modify frozen Array)" because check_this froze `a`
Now, if you could temporarily freeze, and then unfreeze only the ones you froze, that could be really cool.
idle_zealot 4 hours ago||
> Now, if you could temporarily freeze, and then unfreeze only the ones you froze, that could be really cool.

Is that a missing feature in Ruby? You can't have a frozen reference to an object while retaining unfrozen ones in another scope? That's too bad.

Xenoamorphous 6 hours ago||||
> The worst are methods that both mutate and return values

Been bitten a few times by Array.sort().

Luckily there’s Array.toSorted() now.

z3t4 4 hours ago||||
If you want to upset people on the internet tell them that JavaScript is strongly typed, immutable, and everything is passed by value. Which is true. You can change member values though, which is the footgun.
darick 7 hours ago|||
This is possible with the asserts x is y pattern no?

https://www.typescriptlang.org/play/?#code/C4TwDgpgBAYg9nKBe...

Waterluvian 7 hours ago||
I think the sticking points are:

1. You cannot return anything (say an immutable result that has consumed the input)

Okay, so don't return anything, just mutate the original. Except:

2. You cannot mutate the original, return nothing, but the mutated original isn't a subset of the original. For example: https://www.typescriptlang.org/play/?#code/GYVwdgxgLglg9mABB...

darick 6 hours ago||
Hmm, I see, yes it's quite limited.
homebrewer 5 hours ago|||
These LLM spambots are getting so good they're at the top of many discussions now, and people are none the wiser. Sad, but it was predictable.

Please look into its comment history and flag this. Not that it will solve anything.

1718627440 4 hours ago||
I hear you, but this also affects the good discussion below it.
OptionOfT 7 hours ago|||
> The other half come from the implicit local timezone conversion in the Date constructor.

Outlook at that issue even in their old C++ (I think) version.

You're in London, you save your friend's birthday as March 11th.

You're now in SF. When is your friend's birthday? It's still all-day March 11th, not March 10th, starting at 5PM, and ending March 11th at 5PM.

WorldMaker 6 hours ago||
If your friend lives in London it may be useful to have that associated timezone so that you can be sure to message them that day in their timezone. They might better appreciate a message from SF sent on March 10th at 9PM "early that morning in London" than March 11th at 9PM "a day late".

A lot of that gets back to why Temporal adds so many different types, because there are different uses for time zone information and being clear how you shift that information can make a big difference. (A birthday is a PlainDate at rest, but when it is time to send them an ecard you want the ZonedDateTime of the recipient's time zone to find the best time to send it.)

bluGill 6 hours ago||
His birthday is always all day. The question is where he is. If he travels to Japan his birthday won't change - even if he was born late at night and thus it would be a different day if he was born in Japan.
Maxion 6 hours ago||
The other fun ones are daily recurring events for e.g. taking some medication. You take the last one at 10pm before going to bed. You go on a trip which moves you three hours east. Now your calendar helpfully reminds you to take your meds at 1 am.
ndr 8 hours ago|||
Immutability is underrated in general. It's a sore point every time I have to handle non-clojure code.
recursive 8 hours ago||
Given the ubiquity of react, I think immutability is generally rated pretty appropriately. If anything, I think mutability is under-rated. I mean, it wouldn't be applicable to the domain of Temporal, but sometimes a mutable hash map is a simpler/more performant solution than any of the immutable alternatives.
LunaSea 8 hours ago|||
Props data passed to React itself isn't immutable which is probably one of the missing bricks.

React only checks references but since the objects aren't immutable they could have changed even without the reference changing.

Immutability also has a performance price which is not always great.

recursive 7 hours ago||
Yes, you can mutate props. But no, it's probably not going to do what you want if you did it intentionally. If react added Object.freeze() (or deepFreeze) to the component render invoker, everything would be the same, except props would be formally immutable, instead of being only expected to be immutable. But this seems like a distinction without much of a difference, because if you just try to use a pattern like that without having a pretty deep understanding of react internals, it's not going to do what you wanted anyway.
kccqzy 7 hours ago||||
React doesn’t really force you to make your props immutable data. Using mutable data with React is allowed and just as error prone as elsewhere. But certainly you are encouraged to use something like https://immutable-js.com together with React. At least that’s what I used before I discovered ClojureScript.
hrmtst93837 6 hours ago||||
Immutability is often promoted to work around the complexity introduced by state management patterns in modern JS. If your state is isolated and you don't need features like time travel debugging, mutable data structures can be simpler and faster. Some so-called immutable libraries use hidden mutations or copy-on-write, which can actually make things slower or harder to reason about. Unless you have a specific need for immutability, starting with mutable structures is usually more sane.
hombre_fatal 5 hours ago|||
Well, mutability is the default, and React tries to address some of the problems with mutability. So React being popular as a subecosystem inside a mutable environment isn't really evidence that people are missing out on the benefits of mutability.

Though React is less about immutability and more about uni-directional flow + the idiosyncrasy where you need values that are 'stable' across renders.

tracker1 5 hours ago|||
Yeah... I pretty early in my career firmly cemented on a couple things with date-times. It's either a date-time + zone/location detail or always seconds from unix epoc or UTC in iso-8601 style (later JSON's adopted default) across the wire. Since most systems and JS convert pretty easily between UTC and local.

Same for storage details. I started using the 8601 style mostly in file/log naming so they always sorted correctly, this kind of carried over into my code use pre-dating JSON spec.

Doing the above saves a lot of headaches... I'd also by convention use a few utility scripts for common formatting and date changes (I use date-fns now mostly), that would always start with dtm = new Date(dtm); before manipulation, returning the cloned dtm.

VorpalWay 6 hours ago|||
It is not just in time keeping that mutable shared state is an issue, I have seen problems arising from it elsewhere as well in Python especially, but also in C and C++. Probably because Python is pass by reference implicitly, while C and C++ makes pointers/references more explicit, thus reducing the risk of such errors in the code.

There a few schools of thought about what should be done about it. One is to make (almost) everything immutable and hope it gets optimised away/is fast enough anyway. This is the approach taken by functional languages (and functional style programming in general).

Another approach is what Rust does: make state mutable xor shared. So you can either have mutable state that you own exclusively, or you can have read only state that is shared.

Both approaches are valid and helpful in my experience. As someone working with low level performance critical code, I personally prefer the Rust approach here.

pjmlp 6 hours ago|||
One of the first things I learnt to appreciate in C++ already during its C++ARM days was the ability to model mutability.

Naturally there are other languages that do it much better.

The problem is that it still isn't widespread enough.

ravenstine 7 hours ago|||
When I write JavaScript, I make as many things immutable as I can. Sometimes it adds verbosity and leads to less efficient computational patterns, but overall I believe I run into far fewer bugs that are hard to make sense of. There are things about the design of Temporal I don't really like, but immutability was a solid move.

What I don't understand is why they had to make string formatting so rigid. Maybe it has to do with internationalization? I'd have liked if it included a sort of templating system to make the construction of rendered date-time strings much easier.

WorldMaker 6 hours ago|||
> What I don't understand is why they had to make string formatting so rigid. Maybe it has to do with internationalization? I'd have liked if it included a sort of templating system to make the construction of rendered date-time strings much easier.

I think Temporal takes the right approach: toString() is the (mostly) round-trippable ISO format (or close to it) and every other format is accessible by toLocaleString(). In Python terms, it is a bit like formally separating __repl__ and __str__ implementations, respectively. Date's toString() being locale-dependent made it a lot harder to round-trip Date in places like JSON documents if you forgot or missed toISOString().

Temporal's various toLocaleString() functions all take the same Intl.DateTimeFormat constructor parameters, especially its powerful options [1] argument, as Date's own toLocaleString() has had for a long while and has been the preferred approach to locale-aware string formatting.

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

Dragory 6 hours ago||
The problem is that sometimes you want a very specific format, not a locale-based format. This currently still has to be implemented in userland [1].

[1] https://github.com/js-temporal/proposal-temporal-v2/issues/5

kandros 7 hours ago||||
I remember the first time I got in touch with Elixir and immutability as a core principle. It changed the way I wrote JavaScript since
devnotes77 5 hours ago|||
The formatting decision is intentional and documented in the proposal: toString() is meant to be the round-trippable ISO representation, not a display string. For locale-aware output, toLocaleString() accepts the full Intl.DateTimeFormat options bag, which covers most real-world display needs.

For non-locale custom formats ("YYYY-MM-DD" style), there is a gap - Temporal deliberately does not ship a strftime-style templating system. The design rationale was that custom format strings are a source of i18n bugs (hard-coded separators, month/day order assumptions, etc). The idea was to push display formatting toward Intl so locale handling is correct by default.

In practice you can cover most cases with something like:

  const d = Temporal.PlainDate.from('2026-03-11')
  const parts = d.toLocaleString('en-CA', {year:'numeric',month:'2-digit',day:'2-digit'})
for YYYY-MM-DD (Canadian locale uses that order). Fragile, but workable.

The V2 issue Dragory linked is exactly tracking this - custom format pattern support is on the backlog. For now, date-fns or luxon work fine alongside Temporal if you need templated strings.

deepsun 7 hours ago|||
They seem to have taken it from Joda time that revolutionized time in java 10+ years ago. Sadly no mention of Joda.
tadfisher 6 hours ago||
Also no mention of Stephen Colebourne, author of Joda and the JSR-310 spec (the basis of java.time), the one person you should always stop and listen to when he says you are doing something wrong with time.

Alas, this post is not the only time the TC39 people ignored him.

virgil_disgr4ce 8 hours ago|||
I think that actually may be the MOST appreciated design decision in Temporal ;) either way, I'm also a big fan
refulgentis 4 hours ago||
Flagged because account is bot operated (posted again 2h40m later with same general comment)
patchnull 5 hours ago||
[flagged]
sharifhsn 5 hours ago|
Your bot messed up and posted twice in the same thread.
newzino 7 hours ago||
[dead]
aplomb1026 7 hours ago||
[dead]
ilovesamaltman 6 hours ago||
[flagged]
patchnull 5 hours ago|
[flagged]