PEP 615: Support for the IANA Time Zone Database in the Standard Library

I posted this thread to the mailing list to gather feedback from the IANA time zone maintainers, and I thought I would forward on some of the comments from Paul Eggert’s response, along with my responses:

I am going to update this to clarify, but I think this is mostly covered by the caching behavior described in the section on constructors, once I make explicit the assumption that the full time zone data must be eagerly read into memory at construction (rather than being implemented in terms of system calls or something of that nature). With that assumption in place, the answer is that the data is updated whenever a cache miss occurs - the first time any given zone is constructed or, depending on the implementation, the first time it is constructed after a previous version has been ejected from the cache (in the reference implementation, we use a “strong” LRU cache of 8 zones and an unbound “weak” cache, so if you construct 9 zones and hold no references to any of them, constructing the first one again will be a cache miss, and the other 8 will be a cache hit).

This does mean that if you call ZoneInfo("America/New_York") when your installed zoneinfo data is 2019c and then you upgrade to 2020a and call ZoneInfo("US/Eastern"), the two objects may have different behaviors, but I think this is mainly unavoidable without a pretty significant performance impact.

I have made some minor changes to the wording of the constructors text and added a section to clarify this.

Beyond the fact that I plan to ship non-“zone” files in the tzdata fallback package (and thus include the leap seconds), leap seconds are out of scope for this proposal. Python’s datetime type has no support for leap seconds currently, and other than being tracked in the same database, I think they’re at least somewhat orthogonal to the primary problem we’re solving here (a tzinfo implementation).

Leap second support is on my long list of improvements for the datetime module, so I’ll probably get around to it at some point in the future.

I have added a subsection on leap seconds to the “Rejected ideas” section

Yes, I will have to look into this. My main concern is that my hope is to try to use a time zone data source that can be managed at the system level, independent of language stack. I will admit to never having looked into the details, but I was under the impression that tzdist was something that the system would consume, rather than individual programs, is that wrong?

I also am not clear - are there public tzdist servers, or is the suggestion that we would have a Python-specific tzdist service and end users would subscribe to it for updates?

I’m mainly asking because I decided early on (on some very good advice) that effectively distributing the data is a big enough task on its own that it would bog down the initial implementation to try and handle both at once, so my goal with this is to get something that will work if you have the data, and provide a reasonable way to get the data and handle the data distribution in a separate proposal. If tzdist is consistent with a backwards compatible upgrade from a version using TZif files at some point in the future, I’m happy to put it off as, “We should look into this when we try to solve the distribution problem.” It sorta seems like it should be possible to seamlessly transition from system files to tzdist (at least depending on how strong our promises are about the tz search path, anyway).

Note: This is an open action item and I am waiting for either a response or to do a bit more digging and get the answers to this question, but I suspect that we will want to hold off on TZDIST until a later PEP.

Additionally, Matt Johnson-Pint (who works at Microsoft, though he gave me no indication that he was speaking as a representative of Microsoft) pointed me at the new ICU Time Zone API in Windows 10, and so I’ve removed the rejected “Windows native support” and added a new section under “Open issues” detailing a path forward on Windows and the remaining open questions there.

Good point, you were right on in what I was planning - I’ve made it explicit.

This issue of the semantics of datetimes recovered from pickles is a very good point, and not one I had thought of, but you are definitely right that it poses a major problem. I am inclined to agree that this makes for a strong case in favor of pickling only the key and expecting it to be reconstructed on deserialization.

To play devil’s advocate, one possible option would be to have the serialization behavior remain the same (all transition information is serialized along with the key name), but to have deserialization go through the cache: if the key is in the cache, use the existing object rather than one built from a pickle, otherwise populate the cache with the unpickled object.

I personally feel like the behavior at that point is getting a lot harder to keep track of, though, and I’d rather just go with serializing the key. The one thing I’m hesitant about is this:

I don’t love the idea of .nocache()-constructed ZoneInfo instances being unpickleable, because they do have a valid key. One possible way around this would be for nocache time zones to carry a nocache flag or something, so that they can be serialized and deserialized by key, but the deserialized objects maintain the same relative semantics.

For from_file() I’m somewhat more comfortable having those throw an error on pickling, though there is still the option to have the ones that have been passed an explicit key value serialize by key. It would not be terribly difficult to roll a ZoneInfo wrapper that uses custom files but serializes by key, and any such use case would necessarily be obscure.

One last concern before I go all in on the “serialize by key” mechanism: I intend for these things to be opaque data structures, so none of the transition data or even the location of the file that the transitions were read from will be exposed to the end user. This introduces an asymmetry between the two options because the end user can create a simple function to serialize these things by key:

KeyedDatetime = Tuple[datetime, Optional[str]]

def to_keyed_datetime(dt: datetime) -> KeyedDatetime:
    if isinstance(dt.tzinfo, ZoneInfo):
        return (dt.replace(tzinfo=None), str(dt.tzinfo))
    return (dt, None)

def from_keyed_datetime(keyed_dt: KeyedDatetime) -> datetime:
    dt, key = keyed_dt
    if key is not None:
        dt = dt.replace(tzinfo=ZoneInfo(key))
    return dt

But using the serialize-by-key method, it’s not possible for end users to manually get the other behavior, so we are essentially foreclosing that option for them.

I don’t know exactly what the use cases for nocache and from_file are, so it’s hard to know whether it’d ever be a problem if these were unpicklable.

I wonder if you could have an opaque RawZoneInfo object that behaves like a nocache ZoneInfo and is pickled by value, and have regular ZoneInfo be a very thin wrapper for that (maybe a subclass with no extra fields) but with by-key pickle behavior?


The implementation may decide how to implement the cache behavior, but the guarantee made here only requires that as long as two references exist to the result of identical constructor calls, they must be references to the same object. This is consistent with a reference counted cache where ZoneInfo objects are ejected when no references to them exist — it is allowed but not required or recommended to implement this with a “strong” cache, where all ZoneInfo files are kept alive indefinitely.

This can’t be true if the database is updated between subsequent calls to the constructor with the same arguments, right?

Would it be better to have the interface to have a function to get a ZoneInfo instance, retrieving from cache or otherwise creating, similar to the logging module? ie

>>> tz1 = ZoneInfo("Australia/Brisbane")
>>> tz2 = get_zone_info("Australia/Brisbane")
>>> tz3 = get_zone_info("Australia/Brisbane")
>>> tz4 = ZoneInfo("Australia/Brisbane")
>>> tz1 is tz2
>>> tz2 is tz3
>>> tz1 == tz2 == tz3 == tz4

This would separate cache-control from the data class ZoneInfo to the module or a manager instance, allowing for easier user-extensibility of either.

It can be, this is how the reference implementation does it, and it’s how dateutil does it. Here’s the implementation of __new__. The database is never consulted except in the case of a cache miss. I clarify that a bit in this PR to the PEP.

In the end, always getting “the latest data” is fraught with edge cases anyway, and the fact that datetime semantics rely on object identity rather than object equality just adds to the edge cases that are possible.

I will note that there is some precedent in this very area: local time information is only updated in response to a call to time.tzset(), and even that doesn’t work on Windows. The equivalent to calling time.tzset() to get updated time zone information would be calling ZoneInfo.clear_cache() to force ZoneInfo to use the updated data (or to always bypass the main constructor and use the .nocache() constructor).

This is partially how dateutil does it, though the main reason dateutil does it is because tz.gettz() takes any kind of string and returns a time zone from it, so tz.gettz("GMT0BST") will return a tz.tzstr, tz.gettz("Europe/London") will return a tzfile, and tz.gettz() will return local time.

I’d be more open to it if we felt that there was some possibility that we wanted the primary interface to be something that might return any number of types, but I am not convinced of the utility of this function. People mostly know what kind of time zone they want to construct and are happy to select the right type, and in fact it leads to problems when they directly use the tz.tzfile constructor (which uses gettz() for caching).

What I like about ZoneInfo using the cache directly and having the functions bypassing the cache be the more obscure alternate constructors is that most of the time users would want this operation cached - it is much faster, it will make comparison operations cheaper and more consistent and you won’t run into obscure bugs like the one detailed in this blog post.

Hi. First, thanks for working on this. I’ve managed to put off similar work for about a decade now. I look forward being able to deprecate pytz, making it a thin wrapper around the standard library when run with a supported Python. This kind of needs to happen before 2038, as pytz dates from before the newest tzfile format and does not handle the later timestamps.

On the serialization section, what is really being discussed is the difference between timestamps (fixed instances in time), and wallclock times (time in a location, subject to changes made by politicians, bureaucrats and religious leaders). If I serialize ‘2022-06-05 14:00 Europe/Berlin’ today, and deserialize it in two years time after Berlin has ratified EU recommendations and abolished DST, then there are two possible results. If my application requires calendaring semantics, when deserializing I want to apply the current timezone definition, and my appointment at 2pm in Berlin is still at 2pm in Berlin. Because I need wallclock time (the time a clock hung on the wall in that location should show). If I wanted a fixed timestamp, best practice is to convert it to UTC to avoid all the potential traps, but it would also be ok to deserialize the time using the old, incorrect offset it was stored with and end up with 1pm wallclock time.

The PEP specifies that datetimes get serialized with all transition data. That seems unnecessary, as the transition data is reasonably likely to be wrong when it is de-serialized, and I can’t think of any use cases where you want to continue using the wrong data. To deserialize a local timestamp as a fixed point in time, you only need the local timestamp and the offset. Perpetuating the use of wrong data is going to end up with all sorts of problems and confusion, where you will end up with several versions of a timezone each with unique rules and giving different results. At some point, you are going to need to convert the data using the old timezone rules to the current timezone rules, which seems to be exactly the sort of problem we had with the pytz API. Failing to normalize the datetime will cause apps to spit out nonsense to end users, such as timestamps that no longer exist (skipped over by new DST transition rules), or ordering issues (wallclock timestamps using old rules compare earlier or later than wallclock timestamps using current rules).

I think better options are to either serialize as a) wallclock time (datetime + zoneinfo key), or b) local timestamp (datetime + offset + optional zoneinfo key), or c) UTC timestamp (utcdatetime + optional offset + optional zoneinfo key). Even if this means special casing custom zoneinfo datafiles, which I suspect will be rare or non-existent outside of the Python test suite.

While a) is often what you want for calendaring applications (and what you get with pytz), it could cause problems in general use because there is no fixed ordering. Data structures will fail if they rely on stable ordering of local timestamps, and I can’t see a way of forcing people to use fixed timestamps instead of wall time beyond hoping they read the documentation.

b) & c) store fixed timestamps, and let you round trip if all three components are included. With b) a question needing to be answered is if the fixed timestamp is corrected when deserialized (if the offset doesn’t match the current zoneinfo rules, it can be adjusted), or if the current zoneinfo rules only take affect when arithmetic starts happening. ie. is ‘repr(unpickle(d)) == repr(unpickle(d) + timedelta(0)’ true ? With c) timestamps would be adjusted to current rules when deserialized.

For comparision, PostgreSQL went with c). Storing a ‘timestamp with timezone’ just stores a UTC timestamp, and information about the source timezone and offset is lost. See

This all affects how ZoneInfo.nocache and arithmetic work too. As proposed, we can have multiple Europe/Berlin ZoneInfo with different rules. They are sticky, so a datetime referencing an obsolete ZoneInfo is going to keep doing calculations using the obsolete rules. I’m thinking that it would be better if ZoneInfo.nocache would replace the existing cached version, flagging the existing cached version as expired. Existing datetime instances would be unaffected, as their tzinfo would still reference the obsolete ZoneInfo data. But arithmetic would notice the ZoneInfo has been superseded and the result would be using the latest ZoneInfo.


In some ways, the debate over the proper serialization format is a strange one, because pickle is by its nature an ephemeral serialization format - you will have many problems if you try and use it to serialize between dissimilar environments, so in some ways I’m inclined to neglect the case where the data changes between serialization and deserialization anyway.

In the end, I am not sure what most users would want. I think @stub42 makes some solid points about thinking of it in terms of serializing civil times (I had neglected this because usually I only think of that problem when storing dates for the long term, but it’s a valid one, particularly with the cache behavior).

To me, the strongest argument in favor of serializing “by value” rather than “by reference” is that if we go with serializing “by value”, end users on either end of the equation have the option of getting the “by reference” behavior on their own, whereas if we go with “by reference”, end users can’t implement a “by value” solution on their own. I actually really like the idea of doing something like @guido’s solution:

Exposing some interface to get the raw data (RawZoneInfo base class seems like the most natural) would basically alleviate my qualms about this entirely, and give a nice, reasonable default behavior for everyone else.

Another option here is to just go with serialization by key in Python 3.9 and if there’s a lot of demand for the feature we can add RawZoneInfo in a later version. Doing so should be backwards-compatible. For people stuck in the middle, will keep the “serialize by value” behavior it’s always had, and people who need that can use dateutil as a stop gap.

So here’s my proposal for what to do with pickling:

  1. Normally-constructed ZoneInfo objects are serialized by reference to their key parameter.
  2. ZoneInfo.nocache objects are also serialized by reference to their key parameter, but with a flag indicating that they are not drawn from the cache, so they will bypass the cache in the deserialization step.
  3. ZoneInfo.from_file objects cannot be pickled. (End users can write wrapper types if they want to serialize them by key).
1 Like

That sounds very reasonable and removes my objections. I agree with @stub42’s analysis that users will most likely expect to be transmitting civil times. The "Asia/Qostanay" problem will be no different or worse than other problems caused by pickling.

I think we already have a serialization format for the full transition data: the “zone file” (such as passed to from_file()). Methinks only people engagen in maintaining zone info databases will be interested in that.

1 Like

Are you suggesting that ZoneInfo.nocache(key) should unconditionally eject key from the cache, or are you suggesting that it should eject key from the cache only when the data is updated? The second would make nocache(key) a much more expensive operation than I’d like for a cache-bypassing constructor, and the latter would mean that it would unconditionally mutate global state.

I was thinking that .nocache() would be a safe way to get a time zone that will always have “compare / subtract in UTC” behavior, or to deliberately induce a cache miss for a single call. I would want you to be able to use it safely alongside the primary conductor, which is a way to always get “compare / subtract in civil time” behavior when using the same nominal time zone.

There’s still the clear_cache() function, which is in the reference implementation but not described in the PEP. Right now it takes no arguments and clears the cache entirely. Another option would be to change it to something like this (simplified by ignoring thread-safety and ignoring the fact that there are two caches, not just one):

def clear_cache(self, *keys):
    if len(keys):
        for key in keys:
            self._cache.pop(key, None)
        self._cache = weakref.WeakValueDictionary()

This would allow them to decide independently if they want to invalidate the cache for a given key (and they can always create a wrapper around .nocache that does this).

I think the only issue is that there’s no easy way to determine if a cache has gone stale, since the transition data is stale. One option to allow this would be to provide some comparison function like .equivalent_to(self, other) that indicates when two ZoneInfo objects have all the same transition information, so that someone could write an auto-invalidating zoneinfo factory like so:

def latest_zone_info(key):
    new_zoneinfo = ZoneInfo.nocache(key)
    zi = ZoneInfo(key)

    if new_zoneinfo.equivalent_to(zi):

    return ZoneInfo(key)

I agree. If the goal is to unambiguously specify a point in time, only the offset corresponding to the specific moment should be transmitted with the local timestamp. On the other hand, more often we use local timestamps to specify the “wall time” without regard to any notion of absolute time. When we specify the opening time of the New York stock exchange, 09:30 means whatever time is in use in New York on the given day, be it EST, EDT, EWT or anything else. If and when New York state abandons daylight saving time transitions, the opening bell will continue to ring at 09:30 throughout the year.

I strongly believe that users want to reload time zone data transparently, without restarting their applications. The on-disk data is updated fairly frequently, see Red Hat Enterprise Linux Timezone Data (tzdata) - Development Status Page for an example of what one distribution does. I do not think that users would want to restart their application (with a scheduled downtime) just to apply one of those updates.

This means that the caching in the ZoneInfo constructor is very problematic.

Thank you for taking the time to comment on the proposal!

I understand where you are coming from here, but there are a lot of reasons to use the cache, and good reasons to believe that using the cache won’t be a problem.

The question of “reloading time zone data transparently” could mean that existing datetimes would be updated if the data on disk changes (which would be problematic from a datetimes-are-immutable point of view), or it could mean that newly-constructed datetimes are always pulled from the latest data. Assuming we can only do the second thing, that means that if you get time zone data updates during a run of your program, you will end up with a mixture of stale and non-stale time zones, which is also pretty non-ideal.

I think there’s also a lot of precedent for this kind of thing:

  1. It is already the case that if system local time changes, you must call time.tzset() in order to invalidate the cache, and this only works on some platforms. We’re basically already in a situation where you must actively take action to get the “latest time zone information” during a run of the interpreter.
  2. Right now more or less everyone uses a cache and there are not really any complaints. pytz and dateutil both use a similar caching behavior (and AFAIK pytz doesn’t even expose a way to opt out of it - everything is unconditionally cached). I don’t think I’ve heard of anyone complaining about this behavior or even noticing much.

And one of the main drivers for this cache behavior is that the semantics of datetime explicitly assume that time zones are singletons, and you can run into some weird situations if you don’t use singletons. Consider this case:

>>> from datetime import *
>>> from zoneinfo import ZoneInfo
>>> dt0 = datetime(2020, 3, 8, tzinfo=ZoneInfo.nocache("America/New_York"))
>>> dt1 = dt0 + timedelta(1)
>>> dt2 = dt1.replace(tzinfo=ZoneInfo.nocache("America/New_York"))
>>> dt2 == dt1
>>> print(dt2 - dt1)
>>> print(dt2 - dt0)
>>> print(dt1 - dt0)
1 day, 0:00:00

Note that this makes no use of the cache — I used the .nocache constructor to simulate what the semantics of a non-caching constructor would look like - it could cause strange path-dependencies where whether you got somewhere by arithmetic or by construction / replace operations, you’d get different answers.

So, to summarize my position:

  1. Cache by default because most people will want such a cache, even if they don’t know it.
  2. Document various strategies and their trade-offs for people with long-running applications - including the use of ZoneInfo.clear_cache() for tzset-like behavior and ZoneInfo.nocache for “always give me a fresh copy” behavior.

I do 't understand the example. Why does dt2-dt0 print a different value from dt1-dt0?

This is because there’s an STD->DST transition between 2020-03-08 and 2020-03-09, so the difference in wall time is 24 hours, but the absolute elapsed time is 23 hours. I wrote a blog post about datetime arithmetic semantics that goes into more detail about this, but basically, the way datetime's arithmetic and comparisons work is something like this:

def subtract(a, b):
    if a.tzinfo is b.tzinfo:
        return a.replace(tzinfo=None) - b.replace(tzinfo=None)
        UTC = timezone.utc
        return a.astimezone(UTC) - b.astimezone(UTC)

So dt2 - dt0 is treated as two different zones and the math is done in UTC, whereas dt1 - dt0 is treated as the same zone, and the math is done in local time.

dt1 will necessarily be the same zone as dt0, because it’s the result of an arithmetical operation on dt0. dt2 is a different zone because I bypassed the cache, but if it hit the cache, the two would be the same.


Thanks for the explanation.

1 Like

This seems reasonable to me, as given the combination of “tzinfo objects are immutable” and “tzinfo objects are compared by identity in date arithmetic operations”, there’s going to need to be application level logic to cope with a tzdb change without restarting.

Clarifying the logic for not bundling tzdata in the Windows installers in the absence of support for the Windows ICU API: keeping tzdata up to date is going to require separate package installation commands anyway, so it’s reasonable to have the obvious failure (being unable to construct named timezones) happen first, such that users learn the required update command for their system?

If I’ve understood it correctly, I don’t think that rationale fully holds, as there are cases where it would be nice to be able to rely on having tz info available without having to introduce the complexities of package installation, and minor date arithmetic errors here and there would be acceptable. (I’m mostly thinking “teaching Python learners about time zones”, so such errors could even be used to illustrate the importance of keeping timezone DBs up to date)

In a lot of ways “tzdata is installed but not up to date” is just another form of the caching problem in long-running processes, except it’s occurring at the Python environment level.

That said, if we were to ship tzdata initially on Windows (by default), with the intent of eventually removing it from the default package set once the ICU API was supported and support for Windows versions without that API had been dropped, the public top-level module name could be problematic. So perhaps “tzdata” should be imported as “_tzdata” instead, to help make it clear not all systems will provide that module?

Thanks for the thoughtful comments on this, I particularly like the idea of thinking of the “tzdata is installed but not up to date” as another form of the caching problem.

So I would say that that is not the logic for not bundling it, and also not even really something I had considered. The main reason I do not want to bundle tzdata together with Python on Windows or any other platform is that I started to think about the complexities of such a thing and realized that 1. this is a fairly hard packaging problem and 2. blocking the addition of any time zone support would be making the perfect the enemy of the good. Bundling tzdata with CPython is something I think we should tackle in the future (possibly, though not likely, as part of 3.9), but it’s something we can do in a perfectly backwards-compatible way, and it’s a very tricky packaging problem.

I also think that Windows may be something of a red herring here in that I’m not entirely sure that everything in the Python support matrix will necessarily have the system time zone data installed. I have recommended in the PEP that if you are a distributor of Python and it is possible for you to make tzdata a dependency of Python, you should do it, but it is not a requirement for compliance with the PEP (though I guess we could make it one).

From a practical perspective, I think "ship tzdata" leaves a lot to be desired:

  1. For system python, it is often required but undesirable to install packages as an administrator. A somewhat better solution is to use pip install --user, but this means that the base install will never actually get “upgraded”.
  2. A corollary to 1. is that I believe that virtual environments will ignore your --user-installed packages, which means that every time you create a virtual environment, you’ll need to update your tzdata even if you’ve already “globally” updated it.
  3. There is no pip update command, you need to explicitly select the packages you want to update. This is half the reason I almost exclusively work in virtual environments - it’s easier for me to say the requirements and create a new virtual environment from scratch rather than to try and track the state of my baseline environment.

The "tzdata unbundled" situation I’m imagining is that if you want to use time zone data, if you want something 100% pip installable, you just declare a dependency on tzdata in your application or library (possibly conditional on Windows, if we establish that Windows is the only platform where this is a problem). If you are only targeting platforms that you know will have the system time zone data or you can declare a dependency on "install system tzdata" (e.g. conda), then you can omit the declaration.

If people follow this strategy, then everything should work. pip install -U something-depending-on-tzdata will upgrade your tzdata if and only if you need it. When you create a virtual environment, the latest tzdata will be installed when you pip install tzdata.

And to re-iterate, this is not the situation I see going forward forever, just a reasonably tolerable version that is better than something we hack together at the last minute to get this in before feature freeze. At some point I expect those tzdata declarations to turn into tzdata; python_version < 3.10 or whatever.

After some talking with @steve.dower, I’m not nearly as confident with the ICU-based solution as I was in the past. It seems that the part of ICU that Windows exposes may not be suitable for eagerly populating ZoneInfo, and as a result I’m not confident that it is necessarily appropriate to attempt to transparently fall back to it. We are still exploring somewhat, which is why I have not yet updated the PEP.

That said, one of the values of exposing tzdata as a public module was to allow libraries that ship their own time zone data to depend on it. My plan was to have dateutil >= 3.0 start depending on tzdata, for example. The way it’s designed, it can be used in libraries that support older versions of Python (thus opening the way for a zoneinfo backport), so I definitely want to keep the PyPI version public.

We could theoretically have the default-installed version be _tzdata, or simply install the zoneinfo files somewhere other than site-packages with lower precedence than the tzdata module, but I think that gets into tricky packaging territory and people would find it hard to determine the source of their time zone data. Again, I think this is another reason to push the bundling question to a separate PEP.

Thanks for the explanation, and I agree that “deferring for now because it’s tricky and we can still make things better without it” is a good reason for leaving any form of tzdata bundling out of this initial iteration of the feature. (And since I forgot to actually write it down the first time: definite +1 on the overall proposal. Thank you for putting it together!)

As additional notes for a possible future bundling implementation:

  • ensurepip-style bundling of tzdata wouldn’t actually help much, for the reasons you gave:

    • user-level tzdata upgrades will be invisible to all venvs
    • users may not have permissions for Python installation level upgrades
    • even installation level upgrades will be invisible in venvs that aren’t using the system site-packages
  • given those limitations, any bundling would likely need to be as a regular stdlib module, so the fallback would be visible in all venvs. Being a regular stdlib module would restrict updates to Python maintenance releases rather than PyPI package releases. However, if the stdlib fallback used the name “_tzdata”, we could still set up the logic to prefer the system timezone db and the public tzdata module to the private stdlib fallback. The downside of having two copies of the time zone db around means we would probably restrict the fallback bundling to platforms with no usable system time zone db (e.g. Windows)

  • that approach would mean that anyone keeping current on their Python maintenance releases and/or system package updates would be getting reasonably up to date tzdata info (no more than a few months old), while anyone that needed to consistently get tz updates within days would need to install and use the public tzdata module


Maybe it’s too complicated to add an alternative API, but I suggest to take a look to the arrow API (which is different than Apache Arrow). It’s very simple and elegant.

And it’s also different from any other Amerindian arrow.