Important
This PEP is a historical document. The up-to-date, canonical documentation can now be found atzoneinfo.
×
SeePEP 1 for how to propose changes.
This proposes adding a module,zoneinfo, to provide a concrete time zoneimplementation supporting the IANA time zone database. By default,zoneinfo will use the system’s time zone data if available; if no systemtime zone data is available, the library will fall back to using thefirst-party packagetzdata, deployed on PyPI.[d]
Thedatetime library uses a flexible mechanism to handle time zones: allconversions and time zone information queries are delegated to an instance of asubclass of the abstractdatetime.tzinfo base class.[10] This allowsusers to implement arbitrarily complex time zone rules, but in practice themajority of users want support for just three types of time zone:[a]
In Python 3.2, thedatetime.timezone class was introduced to support thefirst class of time zone (with a specialdatetime.timezone.utc singletonfor UTC).
While there is still no “local” time zone, in Python 3.0 the semantics of naïvetime zones was changed to support many “local time” operations, and it is nowpossible to get a fixed time zone offset from a local time:
>>>print(datetime(2020,2,22,12,0).astimezone())2020-02-22 12:00:00-05:00>>>print(datetime(2020,2,22,12,0).astimezone()....strftime("%Y-%m-%d %H:%M:%S %Z"))2020-02-22 12:00:00 EST>>>print(datetime(2020,2,22,12,0).astimezone(timezone.utc))2020-02-22 17:00:00+00:00
However, there is still no support for the time zones described in the IANAtime zone database (also called the “tz” database or the Olson database[6]). The time zone database is in the public domain and is widelydistributed — it is present by default on many Unix-like operating systems.Great care goes into the stability of the database: there are IETF RFCs bothfor the maintenance procedures (RFC 6557) and for the compiledbinary (TZif) format (RFC 8536). As such, it is likely that addingsupport for the compiled outputs of the IANA database will add great value toend users even with the relatively long cadence of standard library releases.
This PEP has three main concerns:
zoneinfo.ZoneInfo class (zoneinfo-class)Because of the complexity of the proposal, rather than having separate“specification” and “rationale” sections the design decisions and rationalesare grouped together by subject.
zoneinfo.ZoneInfo classThe initial design of thezoneinfo.ZoneInfo class has several constructors.
ZoneInfo(key:str)
The primary constructor takes a single argument,key, which is a stringindicating the name of a zone file in the system time zone database (e.g."America/New_York","Europe/London"), and returns aZoneInfoconstructed from the first matching data source on search path (see thedata-sources section for more details). All zone information must be eagerlyread from the data source (usually a TZif file) upon construction, and maynot change during the lifetime of the object (this restriction applies to allZoneInfo constructors).
In the event that no matching file is found on the search path (either becausethe system does not supply time zone data or because the key is invalid), theconstructor will raise azoneinfo.ZoneInfoNotFoundError, which will be asubclass ofKeyError.
One somewhat unusual guarantee made by this constructor is that calls withidentical arguments must returnidentical objects. Specifically, for allvalues ofkey, the following assertion must always be valid[b]:
a=ZoneInfo(key)b=ZoneInfo(key)assertaisb
The reason for this comes from the fact that the semantics of datetimeoperations (e.g. comparison, arithmetic) depend on whether the datetimesinvolved represent the same or different zones; two datetimes are in the samezone only ifdt1.tzinfoisdt2.tzinfo.[1] In additionto the modest performance benefit from avoiding unnecessary proliferation ofZoneInfo objects, providing this guarantee should minimize surprisingbehavior for end users.
dateutil.tz.gettz has provided a similar guarantee since version 2.7.0(release March 2018).[16]
Note
The implementation may decide how to implement the cache behavior, but theguarantee made here only requires that as long as two references exist tothe result of identical constructor calls, they must be references to thesame object. This is consistent with a reference counted cache whereZoneInfo objects are ejected when no references to them exist (forexample, a cache implemented with aweakref.WeakValueDictionary) — it isallowed but not required or recommended to implement this with a “strong”cache, where allZoneInfo objects are kept alive indefinitely.
ZoneInfo.no_cache(key:str)
This is an alternate constructor that bypasses the constructor’s cache. It isidentical to the primary constructor, but returns a new object on each call.This is likely most useful for testing purposes, or to deliberately induce“different zone” semantics between datetimes with the same nominal time zone.
Even if an object constructed by this method would have been a cache miss, itmust not be entered into the cache; in other words, the following assertionshould always be true:
>>>a=ZoneInfo.no_cache(key)>>>b=ZoneInfo(key)>>>aisnotb
ZoneInfo.from_file(fobj:IO[bytes],/,key:str=None)
This is an alternate constructor that allows the construction of aZoneInfoobject from any TZif byte stream. This constructor takes an optionalparameter,key, which sets the name of the zone, for the purposes of__str__ and__repr__ (seeRepresentations).
Unlike the primary constructor, this always constructs a new object. There aretwo reasons that this deviates from the primary constructor’s caching behavior:stream objects have mutable state and so determining whether two inputs areidentical is difficult or impossible, and it is likely that users constructingfrom a file specifically want to load from that file and not a cache.
As withZoneInfo.no_cache, objects constructed by this method must not beadded to the cache.
It is important that a givenZoneInfo object’s behavior not change duringits lifetime, because adatetime’sutcoffset() method is used in bothits equality and hash calculations, and if the result were to change during thedatetime’s lifetime, it could break the invariant for all hashable objects[3][4] that ifx==y, it must also be truethathash(x)==hash(y)[c] .
Considering both the preservation ofdatetime’s invariants and theprimary constructor’s contract to always return the same object when calledwith identical arguments, if a source of time zone data is updated during a runof the interpreter, it must not invalidate any caches or modify anyexistingZoneInfo objects. Newly constructedZoneInfo objects, however,should come from the updated data source.
This means that the point at which the data source is updated for newinvocations of theZoneInfo constructor depends primarily on the semanticsof the caching behavior. The only guaranteed way to get aZoneInfo objectfrom an updated data source is to induce a cache miss, either by bypassing thecache and usingZoneInfo.no_cache or by clearing the cache.
Note
The specified cache behavior does not require that the cache be lazilypopulated — it is consistent with the specification (though notrecommended) to eagerly pre-populate the cache with time zones that havenever been constructed.
In addition toZoneInfo.no_cache, which allows a user tobypass thecache,ZoneInfo also exposes aclear_cache method to deliberatelyinvalidate either the entire cache or selective portions of the cache:
ZoneInfo.clear_cache(*,only_keys:Iterable[str]=None)->None
If no arguments are passed, all caches are invalidated and the first call foreach key to the primaryZoneInfo constructor after the cache has beencleared will return a new instance.
>>>NYC0=ZoneInfo("America/New_York")>>>NYC0isZoneInfo("America/New_York")True>>>ZoneInfo.clear_cache()>>>NYC1=ZoneInfo("America/New_York")>>>NYC0isNYC1False>>>NYC1isZoneInfo("America/New_York")True
An optional parameter,only_keys, takes an iterable of keys to clear fromthe cache, otherwise leaving the cache intact.
>>>NYC0=ZoneInfo("America/New_York")>>>LA0=ZoneInfo("America/Los_Angeles")>>>ZoneInfo.clear_cache(only_keys=["America/New_York"])>>>NYC1=ZoneInfo("America/New_York")>>>LA0=ZoneInfo("America/Los_Angeles")>>>NYC0isNYC1False>>>LA0isLA1True
Manipulation of the cache behavior is expected to be a niche use case; thisfunction is primarily provided to facilitate testing, and to allow users withunusual requirements to tune the cache invalidation behavior to their needs.
TheZoneInfo class’s__str__ representation will be drawn from thekey parameter. This is partially because thekey represents ahuman-readable “name” of the string, but also because it is a useful parameterthat users will want exposed. It is necessary to provide a mechanism to exposethe key for serialization between languages and because it is also a primarykey for localization projects like CLDR (the Unicode Common Locale DataRepository[5]).
An example:
>>>zone=ZoneInfo("Pacific/Kwajalein")>>>str(zone)'Pacific/Kwajalein'>>>dt=datetime(2020,4,1,3,15,tzinfo=zone)>>>f"{dt.isoformat()} [{dt.tzinfo}]"'2020-04-01T03:15:00+12:00 [Pacific/Kwajalein]'
When akey is not specified, thestr operation should not fail, butshould return the objects’s__repr__:
>>>zone=ZoneInfo.from_file(f)>>>str(zone)'ZoneInfo.from_file(<_io.BytesIO object at ...>)'
The__repr__ for aZoneInfo is implementation-defined and notnecessarily stable between versions, but it must not be a validZoneInfokey, to avoid confusion between a key-derivedZoneInfo with a valid__str__ and a file-derivedZoneInfo which has fallen through to the__repr__.
Since the use ofstr() to access the key provides no easy way to checkfor thepresence of a key (the only way is to try constructing aZoneInfofrom it and detect whether it raises an exception),ZoneInfo objects willalso expose a read-onlykey attribute, which will beNone in the eventthat no key was supplied.
Rather than serializing all transition data,ZoneInfo objects will beserialized by key, andZoneInfo objects constructed from raw files (eventhose with a value forkey specified) cannot be pickled.
The behavior of aZoneInfo object depends on how it was constructed:
ZoneInfo(key): When constructed with the primary constructor, aZoneInfo object will be serialized by key, and when deserialized thewill use the primary constructor in the deserializing process, and thus beexpected to be the same object as other references to the same time zone.For example, ifeurope_berlin_pkl is a string containing a pickleconstructed fromZoneInfo("Europe/Berlin"), one would expect thefollowing behavior:>>>a=ZoneInfo("Europe/Berlin")>>>b=pickle.loads(europe_berlin_pkl)>>>aisbTrue
ZoneInfo.no_cache(key): When constructed from the cache-bypassingconstructor, theZoneInfo object will still be serialized by key, butwhen deserialized, it will use the cache bypassing constructor. Ifeurope_berlin_pkl_nc is a string containing a pickle constructed fromZoneInfo.no_cache("Europe/Berlin"), one would expect the followingbehavior:>>>a=ZoneInfo("Europe/Berlin")>>>b=pickle.loads(europe_berlin_pkl_nc)>>>aisbFalse
ZoneInfo.from_file(fobj,/,key=None): When constructed from a file, theZoneInfo object will raise an exception on pickling. If an end userwants to pickle aZoneInfo constructed from a file, it is recommendedthat they use a wrapper type or a custom serialization function: eitherserializing by key or storing the contents of the file object andserializing that.This method of serialization requires that the time zone data for the requiredkey be available on both the serializing and deserializing side, similar to theway that references to classes and functions are expected to exist in both theserializing and deserializing environments. It also means that no guaranteesare made about the consistency of results when unpickling aZoneInfopickled in an environment with a different version of the time zone data.
One of the hardest challenges for IANA time zone support is keeping the data upto date; between 1997 and 2020, there have been between 3 and 21 releases peryear, often in response to changes in time zone rules with little to no notice(see[7] for more details). In order to keep up to date,and to give the system administrator control over the data source, we proposeto use system-deployed time zone data wherever possible. However, not allsystems ship a publicly accessible time zone database — notably Windows uses adifferent system for managing time zones — and so if availablezoneinfofalls back to an installable first-party package,tzdata, available onPyPI.[d] If no system zoneinfo files are found buttzdata is installed, theprimaryZoneInfo constructor will usetzdata as the time zone source.
Many Unix-like systems deploy time zone data by default, or provide a canonicaltime zone data package (often calledtzdata, as it is on Arch Linux, Fedora,and Debian). Whenever possible, it would be preferable to defer to the systemtime zone information, because this allows time zone information for alllanguage stacks to be updated and maintained in one place. Python distributorsare encouraged to ensure that time zone data is installed alongside Pythonwhenever possible (e.g. by declaringtzdata as a dependency for thepython package).
Thezoneinfo module will use a “search path” strategy analogous to thePATH environment variable or thesys.path variable in Python; thezoneinfo.TZPATH variable will be read-only (seesearch-path-config formore details), ordered list of time zone data locations to search. Whencreating aZoneInfo instance from a key, the zone file will be constructedfrom the first data source on the path in which the key exists, so for example,ifTZPATH were:
TZPATH=("/usr/share/zoneinfo","/etc/zoneinfo")
and (although this would be very unusual)/usr/share/zoneinfo containedonlyAmerica/New_York and/etc/zoneinfo contained bothAmerica/New_York andEurope/Moscow, thenZoneInfo("America/New_York") would be satisfied by/usr/share/zoneinfo/America/New_York, whileZoneInfo("Europe/Moscow")would be satisfied by/etc/zoneinfo/Europe/Moscow.
At the moment, on Windows systems, the search path will default to empty,because Windows does not officially ship a copy of the time zone database. Onnon-Windows systems, the search path will default to a list of the mostcommonly observed search paths. Although this is subject to change in futureversions, at launch the default search path will be:
TZPATH=("/usr/share/zoneinfo","/usr/lib/zoneinfo","/usr/share/lib/zoneinfo","/etc/zoneinfo",)
This may be configured both at compile time or at runtime; more information onconfiguration options atsearch-path-config.
tzdata Python packageIn order to ensure easy access to time zone data for all end users, this PEPproposes to create a data-only packagetzdata as a fallback for when systemdata is not available. Thetzdata package would be distributed on PyPI asa “first party” package[d], maintained by the CPython development team.
Thetzdata package contains only data and metadata, with no public-facingfunctions or classes. It will be designed to be compatible with both newerimportlib.resources[11] access patterns and olderaccess patterns likepkgutil.get_data[12] .
While it is designed explicitly for the use of CPython, thetzdata packageis intended as a public package in its own right, and it may be used as an“official” source of time zone data for third party Python packages.
The time zone search path is very system-dependent, and sometimes evenapplication-dependent, and as such it makes sense to provide options tocustomize it. This PEP provides for three such avenues for customization:
reset_tzpath functionIn all methods of configuration, the search path must consist of only absolute,rather than relative paths. Implementations may choose to ignore, warn or raisean exception if a string other than an absolute path is found (and may makedifferent choices depending on the context — e.g. raising an exception when aninvalid path is passed toreset_tzpath but warning when one is included inthe environment variable). If an exception is not raised, any strings otherthan an absolute path must not be included in the time zone search path.
It is most likely that downstream distributors will know exactly where theirsystem time zone data is deployed, and so a compile-time optionPYTHONTZPATH will be provided to set the default search path.
ThePYTHONTZPATH option should be a string delimited byos.pathsep,listing possible locations for the time zone data to be deployed (e.g./usr/share/zoneinfo).
When initializingTZPATH (and wheneverreset_tzpath is called with noarguments), thezoneinfo module will use the environment variablePYTHONTZPATH, if it exists, to set the search path.
PYTHONTZPATH is anos.pathsep-delimited string whichreplaces (ratherthan augments) the default time zone path. Some examples of the proposedsemantics:
$pythonprint_tzpath.py("/usr/share/zoneinfo", "/usr/lib/zoneinfo", "/usr/share/lib/zoneinfo", "/etc/zoneinfo")$PYTHONTZPATH="/etc/zoneinfo:/usr/share/zoneinfo"pythonprint_tzpath.py("/etc/zoneinfo", "/usr/share/zoneinfo")$PYTHONTZPATH=""pythonprint_tzpath.py()
This provides no built-in mechanism for prepending or appending to the defaultsearch path, as these use cases are likely to be somewhat more niche. It shouldbe possible to populate an environment variable with the default search pathfairly easily:
$exportDEFAULT_TZPATH=$(python-c\"import os, zoneinfo; print(os.pathsep.join(zoneinfo.TZPATH))")
reset_tzpath functionzoneinfo provides areset_tzpath function that allows for changing thesearch path at runtime.
defreset_tzpath(to:Optional[Sequence[Union[str,os.PathLike]]]=None)->None:...
When called with a sequence of paths, this function setszoneinfo.TZPATH toa tuple constructed from the desired value. When called with no arguments orNone, this function resetszoneinfo.TZPATH to the defaultconfiguration.
This is likely to be primarily useful for (permanently or temporarily)disabling the use of system time zone paths and forcing the module to use thetzdata package. It is not likely thatreset_tzpath will be a commonoperation, save perhaps in test functions sensitive to time zone configuration,but it seems preferable to provide an official mechanism for changing thisrather than allowing a proliferation of hacks around the immutability ofTZPATH.
Caution
Although changingTZPATH during a run is a supported operation, usersshould be advised that doing so may occasionally lead to unusual semantics,and when making design trade-offs greater weight will be afforded to usinga staticTZPATH, which is the much more common use case.
As noted inConstructors, the primaryZoneInfo constructor employs a cacheto ensure that two identically-constructedZoneInfo objects always compareas identical (i.e.ZoneInfo(key)isZoneInfo(key)), and the nature of thiscache is implementation-defined. This means that the behavior of theZoneInfo constructor may be unpredictably inconsistent in some situationswhen used with the samekey under different values ofTZPATH. Forexample:
>>>reset_tzpath(to=["/my/custom/tzdb"])>>>a=ZoneInfo("My/Custom/Zone")>>>reset_tzpath()>>>b=ZoneInfo("My/Custom/Zone")>>>dela>>>delb>>>c=ZoneInfo("My/Custom/Zone")
In this example,My/Custom/Zone exists only in the/my/custom/tzdb andnot on the default search path. In all implementations the constructor fora must succeed. It is implementation-defined whether the constructor forb succeeds, but if it does, it must be true thataisb, because botha andb are references to the same key. It is alsoimplementation-defined whether the constructor forc succeeds.Implementations ofzoneinfomay return the object constructed in previousconstructor calls, or they may fail with an exception.
This will have no backwards compatibility issues as it will create a new API.
With only minor modification, a backport with support for Python 3.6+ of thezoneinfo module could be created.
Thetzdata package is designed to be “data only”, and should support anyversion of Python that it can be built for (including Python 2.7).
This will require parsing zoneinfo data from disk, mostly from system locationsbut potentially from user-supplied data. Errors in the implementation(particularly the C code) could cause potential security issues, but there isno special risk relative to parsing other file types.
Because the time zone data keys are essentially paths relative to some timezone root, implementations should take care to avoid path traversal attacks.Requesting keys such as../../../path/to/something should not revealanything about the state of the file system outside of the time zone path.
An initial reference implementation is available athttps://github.com/pganssle/zoneinfo
This may eventually be converted into a backport for 3.6+.
One major concern with the use of the TZif format is that it does not actuallycontain enough information to always correctly determine the value to returnfortzinfo.dst(). This is because for any given time zone offset, TZifonly marks the UTC offset and whether or not it represents a DST offset, buttzinfo.dst() returns the total amount of the DST shift, so that the“standard” offset can be reconstructed fromdatetime.utcoffset()-datetime.dst(). The value to use fordst() can be determined by findingthe equivalent STD offset and calculating the difference, but the TZif formatdoes not specify which offsets form STD/DST pairs, and so heuristics must beused to determine this.
One common heuristic — looking at the most recent standard offset — notablyfails in the case of the time zone changes in Portugal in 1992 and 1996, wherethe “standard” offset was shifted by 1 hour during a DST transition, leading toa transition from STD to DST status with no change in offset. In fact, it ispossible (though it has never happened) for a time zone to be created that ispermanently DST and has no standard offsets.
Although this information is missing in the compiled TZif binaries, it ispresent in the raw tzdb files, and it would be possible to parse thisinformation ourselves and create a more suitable binary format.
This idea was rejected for several reasons:
dateutil andpytz for all known timezones, historical and present, and it is not very likely that new time zoneswill appear that cannot be captured by heuristics — though it is somewhatmore likely that new rules that are not captured by thecurrent generationof heuristics will appear; in that case, bugfixes would be required toaccommodate the changed situation.dst() method’s utility (and in fact theisdst parameter in TZif)is somewhat questionable to start with, as almost all the useful informationis contained in theutcoffset() andtzname() methods, which are notsubject to the same problems.In short, maintaining a custom tzdb compiler or compiled package addsmaintenance burdens to both the CPython dev team and system administrators, andits main benefit is to address a hypothetical failure that would likely haveminimal real world effects were it to occur.
tzdata in the standard library by defaultAlthoughPEP 453, which introduced theensurepipmechanism to CPython, provides a convenient template for a standard librarymodule maintained on PyPI, a potentially similarensuretzdata mechanism issomewhat less necessary, and would be complicated enough that it is consideredout of scope for this PEP.
Because thezoneinfo module is designed to use the system time zone datawherever possible, thetzdata package is unnecessary (and may beundesirable) on systems that deploy time zone data, and so it does not seemcritical to shiptzdata with CPython.
It is also not yet clear how these hybrid standard library / PyPI modulesshould be updated, (other thanpip, which has a natural mechanism forupdates and notifications) and since it is not critical to the operation of themodule, it seems prudent to defer any such proposal.
In addition to time zone offset and name rules, the IANA time zone databasealso provides a source of leap second data. This is deemed out of scope becausedatetime.datetime currently has no support for leap seconds, and thequestion of leap second data can be deferred until leap second support isadded.
The first-partytzdata package should ship the leap second data, even if itis not used by thezoneinfo module.
pytz-like interfaceApytz-like ([18]) interface was proposed inPEP 431, butwas ultimately withdrawn / rejected for lack of ambiguous datetime support.PEP 495 added thefold attribute to address this problem, butfold obviates the need forpytz’s non-standardtzinfo classes, andso apytz-like interface is no longer necessary.[2]
Thezoneinfo approach is more closely based ondateutil.tz, whichimplemented support forfold (including a backport to older versions) justbefore the release of Python 3.6.
Windows does not ship the time zone database as TZif files, but as of Windows10’s 2017 Creators Update, Microsoft has provided an API for interacting withthe International Components for Unicode (ICU) project[13][14] , which includes an API for accessing time zone data —sourced from the IANA time zone database.[15]
Providing bindings for this would allow us to support Windows “out of the box”without the need to install thetzdata package, but unfortunately the Cheaders provided by Windows do not provide any access to the underlying timezone data — only an API to query the system for transition and offsetinformation is available. This would constrain the semantics of any ICU-basedimplementation in ways that may not be compatible with a non-ICU-basedimplementation — particularly around the behavior of the cache.
Since it seems like ICU cannot be used as simply an additional data source forZoneInfo objects, this PEP considers the ICU support to be out of scope, andprobably better supported by a third-party library.
This PEP proposes to use a single environment variable:PYTHONTZPATH.This is based on the assumption that the majority of users who would want tomanipulate the time zone path would want to fully replace it (e.g. “I knowexactly where my time zone data is”), and other use cases like prepending tothe existing search path would be less common.
There are several other schemes that were considered and rejected:
PYTHON_TZPATH into two environment variables:DEFAULT_PYTHONTZPATH andPYTHONTZPATH, wherePYTHONTZPATH wouldcontain values to append (or prepend) to the default time zone path, andDEFAULT_PYTHONTZPATH wouldreplace the default time zone path. Thiswas rejected because it would likely lead to user confusion if the primaryuse case is to replace rather than augment.PYTHONTZPATH_PREPEND,PYTHONTZPATH_APPEND or both, sothat users can augment the search path on either end without attempting todetermine what the default time zone path is. This was rejected as likely tobe unnecessary, and because it could easily be added in abackwards-compatible manner in future updates if there is much demand forsuch a feature.PYTHONTZPATH variable, but provide a custom special valuethat represents the default time zone path, e.g.<<DEFAULT_TZPATH>>, sousers could append to the time zone path with, e.g.PYTHONTZPATH=<<DEFAULT_TZPATH>>:/my/path could be used to append/my/path to the end of the time zone path.One advantage to this scheme would be that it would add a natural extensionpoint for specifying non-file-based elements on the search path, such aschanging the priority oftzdata if it exists, or if native support forTZDIST were to be added to the library in the future.
This was rejected mainly because these sort of special values are notusually found inPATH-like variables and the only currently proposed usecase is a stand-in for the defaultTZPATH, which can be acquired byexecuting a Python program to query for the default value. An additionalfactor in rejecting this is that becausePYTHONTZPATH accepts onlyabsolute paths, any string that does not represent a valid absolute path isimplicitly reserved for future use, so it would be possible to introducethese special values as necessary in a backwards-compatible way in futureversions of the library.
datetime moduleOne possible idea would be to addZoneInfo to thedatetime module,rather than giving it its own separate module. This PEP favors the use ofa separatezoneinfo module,though a nesteddatetime.zoneinfo modulewas also under consideration.
ZoneInfo directly intodatetimeThedatetime module is already somewhat crowded, as it has many classeswith somewhat complex behavior —datetime.datetime,datetime.date,datetime.time,datetime.timedelta,datetime.timezone anddatetime.tzinfo. The module’s implementation and documentation are alreadyquite complicated, and it is probably beneficial to try to not to compound theproblem if it can be helped.
TheZoneInfo class is also in some ways different from all the otherclasses provided bydatetime; the other classes are all intended to belean, simple data types, whereas theZoneInfo class is more complex: it isa parser for a specific format (TZif), a representation for the informationstored in that format and a mechanism to look up the information in well-knownlocations in the system.
Finally, while it is true that someone who needs thezoneinfo module alsoneeds thedatetime module, the reverse is not necessarily true: many peoplewill want to usedatetime withoutzoneinfo. Considering thatzoneinfo will likely pull in additional, possibly more heavy-weightstandard library modules, it would be preferable to allow the two to beimported separately — particularly if potential “tree shaking” distributionsare in Python’s future.[9]
In the final analysis, it makes sense to keepzoneinfo a separate modulewith a separate documentation page rather than to put its classes and functionsdirectly intodatetime.
datetime.zoneinfo instead ofzoneinfoA more palatable configuration may be to nestzoneinfo as a module underdatetime, asdatetime.zoneinfo.
Arguments in favor of this:
zoneinfo together withdatetimetimezone class is already indatetime, and it may seem strangethat some time zones are indatetime and others are in a top-levelmodule.zoneinfo necessarily requires importingdatetime, so it is no imposition to require importing the parent module.Arguments against this:
datetime users to importzoneinfo, thezoneinfo module would need to be lazily imported, which means thatend-users would need to explicitly importdatetime.zoneinfo (as opposedto importingdatetime and accessing thezoneinfo attribute on themodule). This is the waydateutil works (all submodules are lazilyimported), and it is a perennial source of confusion for end users.This confusing requirement from end-users can be avoided using amodule-level__getattr__ and__dir__ perPEP 562, but this wouldadd some complexity to the implementation of thedatetime module. Thissort of behavior in modules or classes tends to confuse static analysistools, which may not be desirable for a library as widely used and criticalasdatetime.
datetime would likely requiredatetime to be reorganized from a single-file module (datetime.py)to a directory with an__init__.py. This is a minor concern, but thestructure of thedatetime module has been stable for many years, and itwould be preferable to avoid churn if possible.This concerncould be alleviated by implementingzoneinfo as_zoneinfo.py and importing it aszoneinfo from withindatetime,but this does not seem desirable from an aesthetic or code organizationstandpoint, and it would preclude the version of nesting where end users arerequired to explicitly importdatetime.zoneinfo.
This PEP takes the position that on balance it would be best to use a separatetop-levelzoneinfo module because the benefits of nesting are not so greatthat it overwhelms the practical implementation concerns.
dateutil provides many time zone types,but user support mostly focuses on these three types.ZoneInfo objects should beidentical objects may be violated if the user deliberately clears the timezone cache.datetime is cached on first calculation, sowe do not need to worry about the possibly more serious issue that a givendatetime object’s hash would change during its lifetime.datetime.tzinfo documentationhttps://docs.python.org/3/library/datetime.html#datetime.tzinfoimportlib.resources documentationhttps://docs.python.org/3/library/importlib.html#module-importlib.resourcespkgutil.get_data documentationhttps://docs.python.org/3/library/pkgutil.html#pkgutil.get_dataicu::TimeZone class documentationhttps://unicode-org.github.io/icu-docs/apidoc/released/icu4c/classicu_1_1TimeZone.htmldateutil.tzhttps://dateutil.readthedocs.io/en/stable/tz.htmldateutil.tz.win: Concrete time zone implementations wrapping Windowstime zoneshttps://dateutil.readthedocs.io/en/stable/tzwin.htmlpytzhttp://pytz.sourceforge.net/This document is placed in the public domain or under theCC0-1.0-Universal license, whichever is more permissive.
Source:https://github.com/python/peps/blob/main/peps/pep-0615.rst
Last modified:2025-02-01 07:28:42 GMT