Hacker News new | ask | show | jobs
by tomtomtom777 504 days ago
> It even solves the serialization issue of the difference between a "fixed-offset" timestamp (e.g. 2025-01-01T00:00+02:00) and one in a specific timezone (e.g. Europe/Paris).

Could you elaborate on that? What is the issue?

5 comments

+02:00 is not political. It's an offset from UTC and will be the same offset in the past and future.

Europe/Paris is political. It defines that offsets change at certain times of the year, but that could change tomorrow, or the political boundary that the timezone applies to could split such that the person or entity needing a time in their "local" timezone finds another one needs to be picked (see various US states/counties applying/disapplying daylight savings).

It's impossible to be 100% confident what the offset from UTC will be at any time in the future. You also need to be a historian to correctly apply it to times in the past - take https://en.wikipedia.org/wiki/Time_in_the_Republic_of_Irelan... as an example!

Even countries that are on UTC don't save you from this. I had no idea until very recently but there is a time that simply never existed in Iceland!

Wednesday January 2st 1908 00:00 clocks were turned forward 28 minutes to 00:28. So an entire 28 minutes of time never eexisted in Iceland even thought today they are on UTC year round and one might think they are the best and easiest country to handle timezone wise.

https://mm.icann.org/pipermail/tz/1993-November/009236.html

I should point out that UTC didn't begin until 1960...

The 28 minutes jump is likely Iceland coming into alignment with GMT, for much the same reasons as Ireland did; to improve trade and commerce in a world now using telegraphs, telephones and trains. We're ok becoming disconnected from mean solar time in order to connect more with each other.

Samoa skipped a day in 2011, jumping from UTC−11:00 to UTC+13:00, so that it could align with Australia and New Zealand, its biggest trading partners -- so Australia's Friday is also Samoa's Friday.

We'll always have discontinuities in civil timekeeping, as it's there to serve the whims of humans, not the other way around.

The UK skipped 11 days in 1752. There were riots.
> So an entire 28 minutes of time never eexisted in Iceland even thought today they are on UTC year round

Those two facts aren’t connected. At the time that those 28 minutes were skipped, Iceland was using the equivalent of UTC-01:00.

In the same way that an hour is skipped in many places at the beginning of Daylight Saving Time every year (and the offset changes, e.g. from UTC–5 to UTC–4),

on this particular instant, in Iceland, 28 minutes were skipped because Iceland changed from the offset of Reykjavik’s mean solar time, rounded to the nearest minute (UTC–1:28) to the offset of Reykjavik’s mean solar time, rounded to the nearest hour (UTC–1).

So only from that moment on, Iceland was using UTC–1.

That’s a good point, I didn’t think about that.
That but also and for me more importantly it does not tell you how to add time - If you add one month of hours to a date in October, it depends on the timezone whether you will end up one hour of local time earlier or not (due to DST), because then +02:00 might be +01:00
While this is true, most often if you want to do "now plus a month" you'll mean "at the same time on the local clock", and disregarding timezone changes, while most often if you want to do "now plus four hours" you'd actually mean four real hours, and you want to calculate in the DST changes to make sure you have four actual hours in your duration
That's how Temporal works:

    >> zdt = Temporal.ZonedDateTime.from("2024-03-09T17:00-05[US/Eastern]")
    >> zdt.add("P1d").toString()
    "2024-03-10T17:00:00-04:00[US/Eastern]"
    >> zdt.add("PT24h").toString()
    "2024-03-10T18:00:00-04:00[US/Eastern]"
If you don't have the time zone and instead just an offset, then Temporal can't do this:

    >> zdt = Temporal.ZonedDateTime.from("2024-03-09T17:00-05[-05]")
    >> zdt.add("P1d").toString()
    "2024-03-10T17:00:00-05:00[-05:00]"
    >> zdt.add("PT24h").toString()
    "2024-03-10T17:00:00-05:00[-05:00]"
Adding 1 day still works the same with respect to civil time but the offset is wrong since it doesn't account for the DST change. And adding 24 hours leads to a different result as well. This is one of the reasons why RFC 9557 is so important for serializing zoned datetimes when you want your arithmetic to be DST safe. Previously, to get this right, you had to include a time zone out of band somehow along with your RFC 3339 timestamp.
https://datatracker.ietf.org/doc/rfc9557/ for folks' reference about the new [] suffix syntax.

It's a really well thought out RFC: the offset, the civil time zone name, and a flag for whether that civil time zone is critical information can all be stored, and an inconsistency marked critical MUST be acted upon by the application explicitly, either by rejecting or requesting user interaction.

This may seem redundant, but it's really important to answer "what happens if I have a future timestamp stored, and the USA suddenly rejects daylight savings time. Do I honor the absolute point in time, or do I honor the notion of 5pm?"

Unfortunately, there's going to be a lot of chaos if this happens. Systems like Postgres only store the absolute point in time, normalized to UTC, despite what the name "timestamp with time zone" might imply; an application team or DBA making a decision about this might need to look at other domain-specific metadata e.g. the physical location of the associated asset to determine whether to add or remove an hour. I shudder to think about what this might imply for e.g. HIPAA protected medical systems; the impact of the ensuing bugs might be measured in lives.

Yeah the PostgreSQL situation is just utterly appalling. The fact that there is a type called "timestamp with time zone," that specifically calls out the fact that it has a time zone, but actually doesn't have a time zone is absolutely crazytown.

> This may seem redundant, but it's really important to answer "what happens if I have a future timestamp stored, and the USA suddenly rejects daylight savings time. Do I honor the absolute point in time, or do I honor the notion of 5pm?"

Yeah! It's great that Temporal rejects by default, but does let you override that and choose whether to respect the instant or respect to the civil time. And it lets you do that with a high level configuration knob. You don't have to code up the logic yourself.

> most often if you want to do

most often it is "most often" that causes bugs in software. And when related to date/time it often are severe bugs. ;-)

I wonder what happens when a timezone ceases to exist, e.g. what if Paris is renamed to New New York after the British take revenge and take over France.
Your example of “New X” is spot on, because there was a timezone with that name, “US/Pacific-New”, which was different than “US/Pacific”. Bit of history: it was added to some timezone libraries because there was a bill in congress to add it, but it never passed. I had to fix a bug because users were getting set with that timezone and then other systems/libraries were barfing on it…

https://github.com/moment/moment-timezone/issues/498

A timezone changing or being eliminated doesn’t erase the history of that timezone. So you can still use the last definition of such a timezone if the date is still in the future, or use the appropriate definition of the timezone at the time of a date marked in the past.
> you can still use the last definition of such a timezone if the date is still in the future

but that'll possibly give you an incorrect time!

If you want to store "8am in Paris on the 2026-01-01", then you store "2026-01-01T08:00:00 Europe/Paris". Great, if Paris decides to change their offset rules, that's fine.

Currently, that would be `2026-01-01T08:00:00+01:00` but if Paris decide to ditch DST, it could become `2026-01-01T08:00:00+00:00`. All good, you stored a timezone rather than an offset, so you're decoupled from the timezone->offset changes.

But if tomorrow `Europe/Paris` cease to exist, and is replaced with Europe/NewNewYork? The last definition of Europe/Paris would make this date `2026-01-01T08:00:00+01:00`, but if Europe/NewNewYork ditched DST then it wouldn't be 8am for the people living there, it'd be 7am.

You're decoupled from the timezone->offset changes, but not from location->timezone changes.

The reason the zones use city names is because city identity is far more stable than national or regional borders. Still, the system can gracefully handle deprecation of a zone name. If Paris is renamed NewNewYork by edict of some new global despot, the IANA will just alias the two names together.

See https://github.com/eggert/tz/blob/main/backward for all the deprecated zones.

https://www.icann.org/en/blogs/details/how-time-zones-are-co...

Hopefully the British would be kind enough to email the TZ DB group at the IANA (tz@iana.org) a couple years in advance of the legislation to change the name so that the group can get started on collecting the DST rules for Europe/NewNewYork. Some people and devices will probably stick to Europe/Paris out of habit and/or resistance to the change, so the TZ DB would probably be unlikely to remove it, but they may point references from it to Europe/NewNewYork as the new "canonical" name. Plenty of the DB entries are just pointers to other entries already today, for one instance it was decided that America/City and Europe/City maybe is too conflicted a namespace and we see increasingly more "the canonical name is America/State/City" or "the canonical name is Europe/Country/City".

As a Canadian, I would love for my timezone to be Americas/Toronto or Americas/Canada/Toronto rather than America/Toronto, but that's pretty far down my register of first world problems.
How about something like:

Sol3/CA/Toronto

I'd also like all 50 state capitols (and possibly also their largest city) to exist as E.G.

Sol3/US/WA-Olympia and Sol3/US/WA-Seattle (respectively to the above; I don't know Canadian provinces that well)

Sol3 is a prefix for the 3rd planet (starting ordinal 1, but anything in the orbit of Sol can be Sol0) in our solar system. It's also nicely short so easy to type out in command lines.

Every state _should_ have it's own TZ file, even if it's just an alias. That's a good forward compatible way of allowing the same config to work if future legislative efforts produce or remove timezones. It would also allow E.G. Arizona's non DST timezone to remain correctly configured in some future where the US finally ends the nightmare of DST forever.

Including the state would be odd for the exact reason you demonstrate; a lot of people outside the country don’t know or care, especially with capital cities — who knows what region Ottawa or Canberra are in?

Country largely just makes sense to disambiguate cases like the two different cities a few hundred km apart both named Vancouver.

The prefered canonical name is continent-or-ocean/city-or-small-island because continents and cities are more stable than countries and country names. The America/state/city convention is the exception, not the rule.

Some timezone identifiers have changed, e.g. Asia/Calcutta to Asia/Kolkata in 2008 and Europe/Kiev to Europe/Kyiv in 2022. But the TZ DB maintainers are rather reluctant to make such changes, and require “long-time widespread use of the new city name” in English before deciding so.

The naming conventions for timezone identifiers are written out at https://ftp.iana.org/tz/tzdb-2022b/theory.html#naming

Here are a couple starting points

https://medium.com/servicios-a0/on-solving-the-tzdb-changes-...

https://github.com/tc39/proposal-canonical-tz - appropriately to these comments, a proposal to handle tzdb changes, built on top of JS Temporal, includes some great examples of all the ways this can happen

> https://github.com/tc39/proposal-canonical-tz - appropriately to these comments, a proposal to handle tzdb changes, built on top of JS Temporal, includes some great examples of all the ways this can happen

Thanks! I was the co-champion of that proposal. Parts of it were merged into Temporal last year, and other parts are already part of the JS Internationalization (ECMA-402) specification here: https://tc39.es/ecma402/#sec-use-of-iana-time-zone-database

Then Europe/Paris will become an alias for Europe/NewNewYork
What if England and Germany fight for France, half the country being occupied by Germany using `Europe/Francefurt` and the other half by England using `Europe/NewNewYork`? Then you can't know for sure how to interpret a "Europe/Paris" date without knowing the exact geographical location
If I understand [1] correctly, two new zones (or one new one and the old one) would be created in that case. E.g. if Strasbourg would be occupied by Germany in 2027, a new Europe/Strasbourg could be created which has the same rules as Europe/Paris until 2026, and the rules of Europe/Berlin as of 2027.

[1] https://en.wikipedia.org/wiki/Tz_database

--

By the way, I looked up the time zone in Crimea.

> On 29 March 2014, after annexation by Russia, Crimea switched from Eastern European Time (UTC+02:00) to Moscow Time (UTC+04:00 then, subsequently changed to UTC+03:00). [2]

Crimea has its own zone in the IANA database, Europe/Simferopol, which was updated in 2014 (and already existed before, as it already used Moscow time from 1994 to 1997). [3]

[2] https://en.wikipedia.org/wiki/Time_in_Ukraine

[3] https://lists.iana.org/hyperkitty/list/tz@iana.org/thread/DS...; https://github.com/eggert/tz/commit/bb203f1bb0b6cd4bb2b08f25...; https://github.com/eggert/tz/blob/bb203f1bb0b6cd4bb2b08f2560...

The issue is that if you have a timestamp (e.g., `2025-06-20T17:00:00+02:00`) and a time zone (e.g., `Europe/Paris`) and you go to serialize it, are you explicitly including the time zone in that serialization? And when you deserialize it, are you checking that the offset is still valid for that time zone at that time?

Temporal fixes this by using RFC 9557[1], which includes the time zone in the serialized representation. RFC 9557 is a superset of RFC 3339. So where as previously you might just emit `2025-06-20T17:00:00+02:00`, using RFC 9557, you would emit `2025-06-20T17:00:00+02:00[Europe/Paris]`. For example, using Temporal:

    >> instant = Temporal.Instant.from('2025-06-20T17:00:00+02')
    >> zdt = instant.toZonedDateTimeISO("Europe/Paris")
    >> zdt.toJSON()
    "2025-06-20T17:00:00+02:00[Europe/Paris]"
And when you go to deserialize an RFC 9557 timestamp, Temporal will do some validation to help ensure it's still correct. For example, you might serialize a RFC 9557 timestamp that is in the future, but at some later point, that region might abolish DST. At which point, your RFC 9557 timestamp might or might not resolve to the intended time. If it was in DST, Temporal will reject it at parsing time.

You can read more about this at https://tc39.es/proposal-temporal/docs/zoneddatetime.html and search for "conflict". There's an example about Brazil abolishing DST in 2019 that should lay it out for you.

Separately from even this, there are other concerns. If you forget to include the time zone in your serialization and then just deserialize it as a simple timestamp, then it makes it very easy for arithmetic on that value to be wrong because it won't be DST safe (unless you're careful to reconstitute its time zone somehow). With Temporal and RFC 9557, all of that is handled for you automatically.

[1]: https://datatracker.ietf.org/doc/rfc9557/

I get that it’s more correct, but it assumes that Europe/Paris is a constant representation of how to apply the timezone-specific stuff but that’s incorrect. For example, ‘2025-06-20T17:00:00+02[Europe/Dublin]’ is a very different time if it’s created today vs if it were created in 1760 [1]. That’s a very extreme example, but timezone rules change and dates created from before the change was announced would be interpreted differently from ones created after they were announced. It’s interesting to me the standard doesn’t embed the creation time of the timestamp in UTC as well.

https://en.wikipedia.org/wiki/Time_in_the_Republic_of_Irelan...

It does not assume that -- implementing libraries need to -- and do, in practice right now -- implement the rules for jurisdictions change the rules for offsets.

That's part of why they are tied to a certain city -- time zone rules are unlikely to bisect a city, although if they did I guess they'd have to deprecate it as a timezone name and use something else! Not sure if this has ever happened.

All of this is kept track of in the IANA Time Zone database, and labels like `Europe/Paris` are from keys into that database, not arbitrary. https://www.iana.org/time-zones

Sometimes when jurisdictions do weird stuff like changing their rules for (say) when Daylight Savings starts with no notice (effective tomorrow!), the libraries can take a bit of time to catch up and be correct again (and have the new version be distributed to all users).

But keeping track of (say) that America/New York on March 25 2024 is UTC-4 but March 2025 1990 is UTC-5 hours (they changed when Daylight Savings started in between those years) is absolutely something current (eg OS) time libraries do.

As well as keeping track of the gregorian correction in (depending on country) October 1582 (skipping over 10 days in the calendar!), when calculating historical intervals. They really do this, already!

That's why you say "Europe/Paris" or "America/New York" instead of "UTC-5", to let the library figure out the rules for offsets at that location on the time specified.

I assume Temporal will do the same. JS environments are usually running on OS's that will already provide this service, the browser or other execution environment won't have to implement it from scratch. Although I think moment.js did it from scratch, and distributes a timezone database with moment.js packages.

> As well as keeping track of the gregorian correction in (depending on country) October 1582 (skipping over 10 days in the calendar!), when calculating historical intervals. They really do this, already!

Which libraries do this? Libraries usually implement proleptic calendars, including Temporal[1], which specifically do not account for shifts like this. And indeed, the Temporal docs even call out this specific example.

(I agree with the rest of your comment!)

[1]: https://tc39.es/proposal-temporal/docs/calendars.html#handli...

Ah, I guess not as many as I thought!

Ruby DateTime does it, I hadn't realized it was unusual, if it is!

Here it is particularly called out in documentation with example taking account that April 23rd 1616 in England was not the same day as April 23rd 1616 in Italy, and DateTime knows that! https://ruby-doc.org/stdlib-2.6.1/libdoc/date/rdoc/DateTime....

(That class however is using weird "Date::ENGLAND" constants for locale instead of ISO timezone locales, which is a legacy mistake!)

(I work in archiving cultural history, so probably deal with pre-19th century dates a lot more than the average coder)

update: I was curious what would happen if I gave DateTime a non-existent date...

DateTime.iso8601('1752-09-05', Date::ENGLAND) => invalid date (Date::Error)

it knows! (England skipped some days in September 1752 as part of Gregorian adjustment)

Ah yeah, those look like opt-in calendars to me I think. Definitely other datetime libraries do that. They just also require opt-in AFAIK.
> That's part of why they are tied to a certain city -- time zone rules are unlikely to bisect a city, although if they did I guess they'd have to deprecate it as a timezone name and use something else! Not sure if this has ever happened.

It's actually easier to create this problem than by bisecting a city, and the easier way is even more complex than bisecting a city.

You obviously can't put every hamlet, town and village into tzdb, for a lot of reasons. So, if you're trying to represent a time in a place that isn't in tzdb, you have to pick the nearest location that is in tzdb. And it's quite possible that between when you enter your time and when that time comes to pass, the location you were specifying for changes it's rules in a way that's different from the original place you chose.

If you bisect a city, you could create two new names, so that if you encountered the old name you'd know that something needed to be reconciled. But if you chose the nearest place and then your rules changed, you'd have no way to know automatically that it needed to be revisited.

For example, parts of Chile decided not to do DST any more. To support this, a new timezone, America/Punta_Arenas, was added to tzdb. Before this, if you were in Punta Arenas, you would just put all your times as America/Santiago. And now you have no way of knowing if those times are really supposed to be Santiago or if they were Punta Arenas and Santiago was just the best you could do at the time.

Location-based tz's are the best we can do right now but even still they have intractable problems when things change.

Yep. That said, for end users it's a fairly good story because consumer OSs have gotten very good at automatically adjusting clients' time zone based on geo-location. Just like when you fly to another country and the first thing that happens when you connect your laptop to the internet is you're offered to change your time zone to the local time zone. I assume that the same thing happens when you're in a place whose IANA time zone changes like your America/Punta_Arenas case or many others like it.
On re-read, I understand the other aspects of what you mean better. True!

For dates in the past it isn't much of a problem. `America/[city in chile]` in the past (created before the change, refering to times before the change) still has a specific point-in-time meaning even when things change.

The problem is dates records created in the past but referring to times in the future. Which could now be ambiguous or wrong... and this is the first time I'm thinking about it, I'm not sure how easy it is to detect, I guess it should be detectable which dates may be ambiguous/wrong if you know the date of their creation (before the change was known), but it would take caring to write guards about it and having access to databases with sufficient info.

> For dates in the past it isn't much of a problem. `America/[city in chile]` in the past (created before the change, refering to times before the change) still has a specific point-in-time meaning even when things change.

Right - date-times in the past are always easy (at least until you have to take relativity into account). An event happened at some instant in the universe and you just need an agreed upon representation of that instant. UTC works fine for this - record the UTC-based instant at which the event happened and you can always translate it into any other representation without losing information. Recording it in your local timezone is fine too, as long as you also record the UTC offset or timezone along with the instant.

> I guess it should be detectable which dates may be ambiguous/wrong if you know the date of their creation (before the change was known)

Yeah - in theory, when a timezone is added, you could probably link it to timezones that users of the new timezone might have previously used. And then any future times that were saved using that timezone, you ask someone if they are still correct or if the timezone needs to be adjusted to the new one

For example, if a new timezone was added for southeast Colorado, you might ask someone about all times scheduled in both the Denver & Phoenix timezones, because you don't know which one people might have picked.

It gets complicated though because you need to keep track of which entries have been double checked and which ones haven't, and you need to keep track of the version of tzdb that you reconciled against, because there could be another change in the future.

Right, I mean that if in 2030 for some odd reason half of Punta Arenas does DST and half does not, then 'America/Punta_Arenas' would not work as a timezone designator anymore! Obviously unlikely, I probably should not have mentioned it.
Can you provide a concrete example? Ideally using Temporal to show where it's going wrong. Like, if you created `2025-06-20T17:00:00+02[Europe/Dublin]` (the instant) in 1760, then its civil representation would be different, to account for the rules in place at the time. And then if you went to deserialize it today, Temporal would reject it. You'd get an error, because the rules in place when the string was serialized don't match the rules in place today for that instant.

To be clear, I might be misunderstanding what you're saying. So that's why I'm asking for a concrete example. That will cut through everything. And if you didn't, I would strongly suggest you take a look at https://tc39.es/proposal-temporal/docs/zoneddatetime.html and search for "conflict". I think that will help explain things.

> I get that it’s more correct

We can chase perfection, but perfection isn't the goal. All models are wrong, but some are useful. In other words, the question isn't whether Temporal's model of interaction with time is wrong (it is!), it's how wrong it is and what its impact is that matters.

I must be misunderstanding what you’re saying. How does Temporal know to reject something serialized from before a rule was changed when the creation time of the serialized representation isn’t encoded? You’re saying in 1760 [Europe/Dublin] would be a different string vs today? A more concrete example is normal time-zone rule changes - `2025-06-20T17:00:00+02[Europe/Dublin]` would represent different instants if Dubling passed a new DST rule adjusting the clock back by 15 minutes at 2025-06-20T16:50:00+02[Europe/Dublin] - then the meaning of the instant is different because now there’s 2 different `2025-06-20T17:00:00+02[Europe/Dublin]` and which one you get will depend on when you deserialize.
Can you check out the Brazil example in the docs I linked? That really should clear everything up. It explains how a datetime in the future gets serialized, but after a DST change, that serialized datetime becomes invalid.

The way this works is by looking at offsets. Think of a time zone as a function mapping between civil time and physical time. Or, another way to think about it is a mapping from a civil time to an offset (which may not exist for gaps or may not be unique for folds) and a mapping from physical time to an offset (of which there is a bijection). With RFC 9557, you encode both the offset and the time zone into the serialized representation. Consider a case when you encode an offset corresponding to DST, and then DST is abolished. Your tzdb is updated. Then you go to deserialize this datetime. Its offset no longer matches the rules. This can be detected and Temporal reports an error. This is all explained in the docs.

Note that DST is only an example here, because it's a common manifestation of how this error arises. But it can happen with any change in offset.

So if you're in 1760 and you write down a datetime in the future using the rules of that time (of which, I'm not even sure that's a sensible question to ask), then you'd have a particular offset encoded in that future datetime. Now fast forward to the future and the rules have changed and are no longer consistent with the offset encoded in the datetime. Thus, you get an error.

Here's another take on the idea that I wrote for Jiff: https://docs.rs/jiff/latest/jiff/tz/enum.OffsetConflict.html

Think of it like this. In an RFC 9557 timestamp, you have an offset and you have a time zone. Those two pieces of information may be inconsistent with one another. For example, `2025-01-30T17:00+10[US/Eastern]`. When that happens, you can report an error. That's pretty much it.

> then the meaning of the instant is different because now there’s 2 different `2025-06-20T17:00:00+02[Europe/Dublin]` and which one you get will depend on when you deserialize.

If the region adjusted their close back by 15 minutes, then the offset would change. As for `2025-06-20T17:00:00+02[Europe/Dublin]`, it can only feasibly have two different interpretations: you can either interpret it as the instant `2025-06-20T17:00:00+02`, or you can interpret it as the civil time `2025-06-20T17:00:00` in the time zone `Europe/Dublin`, ignoring the offset. Or you can reject it. Temporal supports all of these modes, and it will by default reject such strings when the offset and time zone are inconsistent.

(There's a fourth mode, "prefer offset," that I won't get into here... It's not usually used for deserialization/parsing.)

So in my use cases, there's three types of dates that matter:

1. Past dates. These can be stored UTC, and just rendered in the appropriate timezone as a matter of formatting.

2. Future non-human dates: e.g. the next execution time of a job that runs every hour. These can just be UTC

3. Future human dates: I care about the human selected timezone so that events happen on the wall clock time the user expects. The UTC time and UTC offset are meaningless.

So in cases 1 and 2, having a non-UTC date is not required, while for case 3, the only thing that UTC offset is doing is adding information that could be inconsistent or confusing.

e.g. If the concert is on at 2026-01-31T18:00:00[Europe/Dublin] , that's all that matters, whether that ends up being 2026-01-31T18:00:00+00:00 or 2026-01-31T18:00:00+01:00 is unimportant for whether the building is going to be open at the time. So the system failing to give customers on the day of the concert a countdown because `2026-01-31T18:00:00+00:00[Europe/Dublin]` has become inconsistent because e.g. the EU actually did go ahead and abolish DST is suboptimal.

Places like Europe/Paris or America/Los Angeles can participate in daylight savings, and if they do, they may have different schedules, and those schedules may change at different points in history due to the passing of laws. Europe and the us already have a different schedule for daylight savings, and the whole west coast is trying to opt into permanent daylight savings for the last several years. But that would require an act of congress.

Programmatically it mostly means you have to ship metadata about how the offsets change over time, which needs to be updated periodically. Browsers already ship that metadata, so it is really nice to finally have a way to access it from JavaScript without shipping the extra few tens of kilobytes.

Europe/Paris might change between now and the referenced time.
In other words, 2025-01-01T00:00+02:00 was NOT Europe/Paris (as it was CET at that time, GMT+1), 2024-08-01T00:00+02:00 could have been Europe/Paris (CEST, GMT+2), 2030-08-01T00:00+02:00 may be Europe/Paris (CEST, GMT+2), or perhaps not (CET, GMT+1). Or it may be a completely different TZ that incidentally shares the same offset at that time.
One represents a time in a specific place, and one represents merely a shift from UTC. A specific time zone can have different offsets depending on the time of year, for example with Daylight Savings Time.