python-peps/pep-0615/index.html

1058 lines
100 KiB
HTML
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta name="color-scheme" content="light dark">
<title>PEP 615 Support for the IANA Time Zone Database in the Standard Library | peps.python.org</title>
<link rel="shortcut icon" href="../_static/py.png">
<link rel="canonical" href="https://peps.python.org/pep-0615/">
<link rel="stylesheet" href="../_static/style.css" type="text/css">
<link rel="stylesheet" href="../_static/mq.css" type="text/css">
<link rel="stylesheet" href="../_static/pygments.css" type="text/css" media="(prefers-color-scheme: light)" id="pyg-light">
<link rel="stylesheet" href="../_static/pygments_dark.css" type="text/css" media="(prefers-color-scheme: dark)" id="pyg-dark">
<link rel="alternate" type="application/rss+xml" title="Latest PEPs" href="https://peps.python.org/peps.rss">
<meta property="og:title" content='PEP 615 Support for the IANA Time Zone Database in the Standard Library | peps.python.org'>
<meta property="og:description" content="This proposes adding a module, zoneinfo, to provide a concrete time zone implementation supporting the IANA time zone database. By default, zoneinfo will use the systems time zone data if available; if no system time zone data is available, the librar...">
<meta property="og:type" content="website">
<meta property="og:url" content="https://peps.python.org/pep-0615/">
<meta property="og:site_name" content="Python Enhancement Proposals (PEPs)">
<meta property="og:image" content="https://peps.python.org/_static/og-image.png">
<meta property="og:image:alt" content="Python PEPs">
<meta property="og:image:width" content="200">
<meta property="og:image:height" content="200">
<meta name="description" content="This proposes adding a module, zoneinfo, to provide a concrete time zone implementation supporting the IANA time zone database. By default, zoneinfo will use the systems time zone data if available; if no system time zone data is available, the librar...">
<meta name="theme-color" content="#3776ab">
</head>
<body>
<svg xmlns="http://www.w3.org/2000/svg" style="display: none;">
<symbol id="svg-sun-half" viewBox="0 0 24 24" pointer-events="all">
<title>Following system colour scheme</title>
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none"
stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<circle cx="12" cy="12" r="9"></circle>
<path d="M12 3v18m0-12l4.65-4.65M12 14.3l7.37-7.37M12 19.6l8.85-8.85"></path>
</svg>
</symbol>
<symbol id="svg-moon" viewBox="0 0 24 24" pointer-events="all">
<title>Selected dark colour scheme</title>
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none"
stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<path stroke="none" d="M0 0h24v24H0z" fill="none"></path>
<path d="M12 3c.132 0 .263 0 .393 0a7.5 7.5 0 0 0 7.92 12.446a9 9 0 1 1 -8.313 -12.454z"></path>
</svg>
</symbol>
<symbol id="svg-sun" viewBox="0 0 24 24" pointer-events="all">
<title>Selected light colour scheme</title>
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none"
stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<circle cx="12" cy="12" r="5"></circle>
<line x1="12" y1="1" x2="12" y2="3"></line>
<line x1="12" y1="21" x2="12" y2="23"></line>
<line x1="4.22" y1="4.22" x2="5.64" y2="5.64"></line>
<line x1="18.36" y1="18.36" x2="19.78" y2="19.78"></line>
<line x1="1" y1="12" x2="3" y2="12"></line>
<line x1="21" y1="12" x2="23" y2="12"></line>
<line x1="4.22" y1="19.78" x2="5.64" y2="18.36"></line>
<line x1="18.36" y1="5.64" x2="19.78" y2="4.22"></line>
</svg>
</symbol>
</svg>
<script>
document.documentElement.dataset.colour_scheme = localStorage.getItem("colour_scheme") || "auto"
</script>
<section id="pep-page-section">
<header>
<h1>Python Enhancement Proposals</h1>
<ul class="breadcrumbs">
<li><a href="https://www.python.org/" title="The Python Programming Language">Python</a> &raquo; </li>
<li><a href="../pep-0000/">PEP Index</a> &raquo; </li>
<li>PEP 615</li>
</ul>
<button id="colour-scheme-cycler" onClick="setColourScheme(nextColourScheme())">
<svg aria-hidden="true" class="colour-scheme-icon-when-auto"><use href="#svg-sun-half"></use></svg>
<svg aria-hidden="true" class="colour-scheme-icon-when-dark"><use href="#svg-moon"></use></svg>
<svg aria-hidden="true" class="colour-scheme-icon-when-light"><use href="#svg-sun"></use></svg>
<span class="visually-hidden">Toggle light / dark / auto colour theme</span>
</button>
</header>
<article>
<section id="pep-content">
<h1 class="page-title">PEP 615 Support for the IANA Time Zone Database in the Standard Library</h1>
<dl class="rfc2822 field-list simple">
<dt class="field-odd">Author<span class="colon">:</span></dt>
<dd class="field-odd">Paul Ganssle &lt;paul at ganssle.io&gt;</dd>
<dt class="field-even">Discussions-To<span class="colon">:</span></dt>
<dd class="field-even"><a class="reference external" href="https://discuss.python.org/t/3468">Discourse thread</a></dd>
<dt class="field-odd">Status<span class="colon">:</span></dt>
<dd class="field-odd"><abbr title="Accepted and implementation complete, or no longer active">Final</abbr></dd>
<dt class="field-even">Type<span class="colon">:</span></dt>
<dd class="field-even"><abbr title="Normative PEP with a new feature for Python, implementation change for CPython or interoperability standard for the ecosystem">Standards Track</abbr></dd>
<dt class="field-odd">Created<span class="colon">:</span></dt>
<dd class="field-odd">22-Feb-2020</dd>
<dt class="field-even">Python-Version<span class="colon">:</span></dt>
<dd class="field-even">3.9</dd>
<dt class="field-odd">Post-History<span class="colon">:</span></dt>
<dd class="field-odd">25-Feb-2020, 29-Mar-2020</dd>
<dt class="field-even">Replaces<span class="colon">:</span></dt>
<dd class="field-even"><a class="reference external" href="../pep-0431/">431</a></dd>
</dl>
<hr class="docutils" />
<section id="contents">
<details><summary>Table of Contents</summary><ul class="simple">
<li><a class="reference internal" href="#abstract">Abstract</a></li>
<li><a class="reference internal" href="#motivation">Motivation</a></li>
<li><a class="reference internal" href="#proposal">Proposal</a><ul>
<li><a class="reference internal" href="#the-zoneinfo-zoneinfo-class">The <code class="docutils literal notranslate"><span class="pre">zoneinfo.ZoneInfo</span></code> class</a><ul>
<li><a class="reference internal" href="#constructors">Constructors</a></li>
<li><a class="reference internal" href="#behavior-during-data-updates">Behavior during data updates</a></li>
<li><a class="reference internal" href="#deliberate-cache-invalidation">Deliberate cache invalidation</a></li>
<li><a class="reference internal" href="#string-representation">String representation</a></li>
<li><a class="reference internal" href="#pickle-serialization">Pickle serialization</a></li>
</ul>
</li>
<li><a class="reference internal" href="#sources-for-time-zone-data">Sources for time zone data</a><ul>
<li><a class="reference internal" href="#system-time-zone-information">System time zone information</a></li>
<li><a class="reference internal" href="#the-tzdata-python-package">The <code class="docutils literal notranslate"><span class="pre">tzdata</span></code> Python package</a></li>
</ul>
</li>
<li><a class="reference internal" href="#search-path-configuration">Search path configuration</a><ul>
<li><a class="reference internal" href="#compile-time-options">Compile-time options</a></li>
<li><a class="reference internal" href="#environment-variables">Environment variables</a></li>
<li><a class="reference internal" href="#reset-tzpath-function"><code class="docutils literal notranslate"><span class="pre">reset_tzpath</span></code> function</a></li>
</ul>
</li>
</ul>
</li>
<li><a class="reference internal" href="#backwards-compatibility">Backwards Compatibility</a></li>
<li><a class="reference internal" href="#security-implications">Security Implications</a></li>
<li><a class="reference internal" href="#reference-implementation">Reference Implementation</a></li>
<li><a class="reference internal" href="#rejected-ideas">Rejected Ideas</a><ul>
<li><a class="reference internal" href="#building-a-custom-tzdb-compiler">Building a custom tzdb compiler</a></li>
<li><a class="reference internal" href="#including-tzdata-in-the-standard-library-by-default">Including <code class="docutils literal notranslate"><span class="pre">tzdata</span></code> in the standard library by default</a></li>
<li><a class="reference internal" href="#support-for-leap-seconds">Support for leap seconds</a></li>
<li><a class="reference internal" href="#using-a-pytz-like-interface">Using a <code class="docutils literal notranslate"><span class="pre">pytz</span></code>-like interface</a></li>
<li><a class="reference internal" href="#windows-support-via-microsoft-s-icu-api">Windows support via Microsofts ICU API</a></li>
<li><a class="reference internal" href="#alternative-environment-variable-configurations">Alternative environment variable configurations</a></li>
<li><a class="reference internal" href="#using-the-datetime-module">Using the <code class="docutils literal notranslate"><span class="pre">datetime</span></code> module</a><ul>
<li><a class="reference internal" href="#arguments-against-putting-zoneinfo-directly-into-datetime">Arguments against putting <code class="docutils literal notranslate"><span class="pre">ZoneInfo</span></code> directly into <code class="docutils literal notranslate"><span class="pre">datetime</span></code></a></li>
<li><a class="reference internal" href="#using-datetime-zoneinfo-instead-of-zoneinfo">Using <code class="docutils literal notranslate"><span class="pre">datetime.zoneinfo</span></code> instead of <code class="docutils literal notranslate"><span class="pre">zoneinfo</span></code></a></li>
</ul>
</li>
</ul>
</li>
<li><a class="reference internal" href="#footnotes">Footnotes</a></li>
<li><a class="reference internal" href="#references">References</a><ul>
<li><a class="reference internal" href="#other-time-zone-implementations">Other time zone implementations:</a></li>
</ul>
</li>
<li><a class="reference internal" href="#copyright">Copyright</a></li>
</ul>
</details></section>
<div class="pep-banner canonical-doc sticky-banner admonition important">
<p class="admonition-title">Important</p>
<p>This PEP is a historical document. The up-to-date, canonical documentation can now be found at <a class="reference external" href="https://docs.python.org/3/library/zoneinfo.html#module-zoneinfo" title="(in Python v3.12)"><code class="xref py py-mod docutils literal notranslate"><span class="pre">zoneinfo</span></code></a>.</p>
<p class="close-button">×</p>
<p>See <a class="pep reference internal" href="../pep-0001/" title="PEP 1 PEP Purpose and Guidelines">PEP 1</a> for how to propose changes.</p>
</div>
<section id="abstract">
<h2><a class="toc-backref" href="#abstract" role="doc-backlink">Abstract</a></h2>
<p>This proposes adding a module, <code class="docutils literal notranslate"><span class="pre">zoneinfo</span></code>, to provide a concrete time zone
implementation supporting the IANA time zone database. By default,
<code class="docutils literal notranslate"><span class="pre">zoneinfo</span></code> will use the systems time zone data if available; if no system
time zone data is available, the library will fall back to using the
first-party package <code class="docutils literal notranslate"><span class="pre">tzdata</span></code>, deployed on PyPI. <a class="reference internal" href="#d" id="id1"><span>[d]</span></a></p>
</section>
<section id="motivation">
<h2><a class="toc-backref" href="#motivation" role="doc-backlink">Motivation</a></h2>
<p>The <code class="docutils literal notranslate"><span class="pre">datetime</span></code> library uses a flexible mechanism to handle time zones: all
conversions and time zone information queries are delegated to an instance of a
subclass of the abstract <code class="docutils literal notranslate"><span class="pre">datetime.tzinfo</span></code> base class. <a class="footnote-reference brackets" href="#tzinfo" id="id2">[10]</a> This allows
users to implement arbitrarily complex time zone rules, but in practice the
majority of users want support for just three types of time zone: <a class="reference internal" href="#a" id="id3"><span>[a]</span></a></p>
<ol class="arabic simple">
<li>UTC and fixed offsets thereof</li>
<li>The system local time zone</li>
<li>IANA time zones</li>
</ol>
<p>In Python 3.2, the <code class="docutils literal notranslate"><span class="pre">datetime.timezone</span></code> class was introduced to support the
first class of time zone (with a special <code class="docutils literal notranslate"><span class="pre">datetime.timezone.utc</span></code> singleton
for UTC).</p>
<p>While there is still no “local” time zone, in Python 3.0 the semantics of naïve
time zones was changed to support many “local time” operations, and it is now
possible to get a fixed time zone offset from a local time:</p>
<div class="highlight-default notranslate"><div class="highlight"><pre><span></span><span class="gp">&gt;&gt;&gt; </span><span class="nb">print</span><span class="p">(</span><span class="n">datetime</span><span class="p">(</span><span class="mi">2020</span><span class="p">,</span> <span class="mi">2</span><span class="p">,</span> <span class="mi">22</span><span class="p">,</span> <span class="mi">12</span><span class="p">,</span> <span class="mi">0</span><span class="p">)</span><span class="o">.</span><span class="n">astimezone</span><span class="p">())</span>
<span class="go">2020-02-22 12:00:00-05:00</span>
<span class="gp">&gt;&gt;&gt; </span><span class="nb">print</span><span class="p">(</span><span class="n">datetime</span><span class="p">(</span><span class="mi">2020</span><span class="p">,</span> <span class="mi">2</span><span class="p">,</span> <span class="mi">22</span><span class="p">,</span> <span class="mi">12</span><span class="p">,</span> <span class="mi">0</span><span class="p">)</span><span class="o">.</span><span class="n">astimezone</span><span class="p">()</span>
<span class="gp">... </span> <span class="o">.</span><span class="n">strftime</span><span class="p">(</span><span class="s2">&quot;%Y-%m-</span><span class="si">%d</span><span class="s2"> %H:%M:%S %Z&quot;</span><span class="p">))</span>
<span class="go">2020-02-22 12:00:00 EST</span>
<span class="gp">&gt;&gt;&gt; </span><span class="nb">print</span><span class="p">(</span><span class="n">datetime</span><span class="p">(</span><span class="mi">2020</span><span class="p">,</span> <span class="mi">2</span><span class="p">,</span> <span class="mi">22</span><span class="p">,</span> <span class="mi">12</span><span class="p">,</span> <span class="mi">0</span><span class="p">)</span><span class="o">.</span><span class="n">astimezone</span><span class="p">(</span><span class="n">timezone</span><span class="o">.</span><span class="n">utc</span><span class="p">))</span>
<span class="go">2020-02-22 17:00:00+00:00</span>
</pre></div>
</div>
<p>However, there is still no support for the time zones described in the IANA
time zone database (also called the “tz” database or the Olson database
<a class="footnote-reference brackets" href="#tzdb-wiki" id="id4">[6]</a>). The time zone database is in the public domain and is widely
distributed — it is present by default on many Unix-like operating systems.
Great care goes into the stability of the database: there are IETF RFCs both
for the maintenance procedures (<span class="target" id="index-0"></span><a class="rfc reference external" href="https://datatracker.ietf.org/doc/html/rfc6557.html"><strong>RFC 6557</strong></a>) and for the compiled
binary (TZif) format (<span class="target" id="index-1"></span><a class="rfc reference external" href="https://datatracker.ietf.org/doc/html/rfc8536.html"><strong>RFC 8536</strong></a>). As such, it is likely that adding
support for the compiled outputs of the IANA database will add great value to
end users even with the relatively long cadence of standard library releases.</p>
</section>
<section id="proposal">
<h2><a class="toc-backref" href="#proposal" role="doc-backlink">Proposal</a></h2>
<p>This PEP has three main concerns:</p>
<ol class="arabic simple">
<li>The semantics of the <code class="docutils literal notranslate"><span class="pre">zoneinfo.ZoneInfo</span></code> class (<a class="reference internal" href="#zoneinfo-class">zoneinfo-class</a>)</li>
<li>Time zone data sources used (<a class="reference internal" href="#data-sources">data-sources</a>)</li>
<li>Options for configuration of the time zone search path (<a class="reference internal" href="#search-path-config">search-path-config</a>)</li>
</ol>
<p>Because of the complexity of the proposal, rather than having separate
“specification” and “rationale” sections the design decisions and rationales
are grouped together by subject.</p>
<section id="the-zoneinfo-zoneinfo-class">
<span id="zoneinfo-class"></span><h3><a class="toc-backref" href="#the-zoneinfo-zoneinfo-class" role="doc-backlink">The <code class="docutils literal notranslate"><span class="pre">zoneinfo.ZoneInfo</span></code> class</a></h3>
<section id="constructors">
<h4><a class="toc-backref" href="#constructors" role="doc-backlink">Constructors</a></h4>
<p>The initial design of the <code class="docutils literal notranslate"><span class="pre">zoneinfo.ZoneInfo</span></code> class has several constructors.</p>
<div class="highlight-default notranslate"><div class="highlight"><pre><span></span><span class="n">ZoneInfo</span><span class="p">(</span><span class="n">key</span><span class="p">:</span> <span class="nb">str</span><span class="p">)</span>
</pre></div>
</div>
<p>The primary constructor takes a single argument, <code class="docutils literal notranslate"><span class="pre">key</span></code>, which is a string
indicating the name of a zone file in the system time zone database (e.g.
<code class="docutils literal notranslate"><span class="pre">&quot;America/New_York&quot;</span></code>, <code class="docutils literal notranslate"><span class="pre">&quot;Europe/London&quot;</span></code>), and returns a <code class="docutils literal notranslate"><span class="pre">ZoneInfo</span></code>
constructed from the first matching data source on search path (see the
<a class="reference internal" href="#data-sources">data-sources</a> section for more details). All zone information must be eagerly
read from the data source (usually a TZif file) upon construction, and may
not change during the lifetime of the object (this restriction applies to all
<code class="docutils literal notranslate"><span class="pre">ZoneInfo</span></code> constructors).</p>
<p>In the event that no matching file is found on the search path (either because
the system does not supply time zone data or because the key is invalid), the
constructor will raise a <code class="docutils literal notranslate"><span class="pre">zoneinfo.ZoneInfoNotFoundError</span></code>, which will be a
subclass of <code class="docutils literal notranslate"><span class="pre">KeyError</span></code>.</p>
<p>One somewhat unusual guarantee made by this constructor is that calls with
identical arguments must return <em>identical</em> objects. Specifically, for all
values of <code class="docutils literal notranslate"><span class="pre">key</span></code>, the following assertion must always be valid <a class="reference internal" href="#b" id="id5"><span>[b]</span></a>:</p>
<div class="highlight-default notranslate"><div class="highlight"><pre><span></span><span class="n">a</span> <span class="o">=</span> <span class="n">ZoneInfo</span><span class="p">(</span><span class="n">key</span><span class="p">)</span>
<span class="n">b</span> <span class="o">=</span> <span class="n">ZoneInfo</span><span class="p">(</span><span class="n">key</span><span class="p">)</span>
<span class="k">assert</span> <span class="n">a</span> <span class="ow">is</span> <span class="n">b</span>
</pre></div>
</div>
<p>The reason for this comes from the fact that the semantics of datetime
operations (e.g. comparison, arithmetic) depend on whether the datetimes
involved represent the same or different zones; two datetimes are in the same
zone only if <code class="docutils literal notranslate"><span class="pre">dt1.tzinfo</span> <span class="pre">is</span> <span class="pre">dt2.tzinfo</span></code>. <a class="footnote-reference brackets" href="#nontransitive-comp" id="id6">[1]</a> In addition
to the modest performance benefit from avoiding unnecessary proliferation of
<code class="docutils literal notranslate"><span class="pre">ZoneInfo</span></code> objects, providing this guarantee should minimize surprising
behavior for end users.</p>
<p><code class="docutils literal notranslate"><span class="pre">dateutil.tz.gettz</span></code> has provided a similar guarantee since version 2.7.0
(release March 2018). <a class="footnote-reference brackets" href="#dateutil-tz" id="id7">[16]</a></p>
<div class="admonition note">
<p class="admonition-title">Note</p>
<p>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
<code class="docutils literal notranslate"><span class="pre">ZoneInfo</span></code> objects are ejected when no references to them exist (for
example, a cache implemented with a <code class="docutils literal notranslate"><span class="pre">weakref.WeakValueDictionary</span></code>) — it is
allowed but not required or recommended to implement this with a “strong”
cache, where all <code class="docutils literal notranslate"><span class="pre">ZoneInfo</span></code> objects are kept alive indefinitely.</p>
</div>
<div class="highlight-default notranslate"><div class="highlight"><pre><span></span><span class="n">ZoneInfo</span><span class="o">.</span><span class="n">no_cache</span><span class="p">(</span><span class="n">key</span><span class="p">:</span> <span class="nb">str</span><span class="p">)</span>
</pre></div>
</div>
<p>This is an alternate constructor that bypasses the constructors cache. It is
identical 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.</p>
<p>Even if an object constructed by this method would have been a cache miss, it
must not be entered into the cache; in other words, the following assertion
should always be true:</p>
<div class="highlight-default notranslate"><div class="highlight"><pre><span></span><span class="gp">&gt;&gt;&gt; </span><span class="n">a</span> <span class="o">=</span> <span class="n">ZoneInfo</span><span class="o">.</span><span class="n">no_cache</span><span class="p">(</span><span class="n">key</span><span class="p">)</span>
<span class="gp">&gt;&gt;&gt; </span><span class="n">b</span> <span class="o">=</span> <span class="n">ZoneInfo</span><span class="p">(</span><span class="n">key</span><span class="p">)</span>
<span class="gp">&gt;&gt;&gt; </span><span class="n">a</span> <span class="ow">is</span> <span class="ow">not</span> <span class="n">b</span>
</pre></div>
</div>
<div class="highlight-default notranslate"><div class="highlight"><pre><span></span><span class="n">ZoneInfo</span><span class="o">.</span><span class="n">from_file</span><span class="p">(</span><span class="n">fobj</span><span class="p">:</span> <span class="n">IO</span><span class="p">[</span><span class="nb">bytes</span><span class="p">],</span> <span class="o">/</span><span class="p">,</span> <span class="n">key</span><span class="p">:</span> <span class="nb">str</span> <span class="o">=</span> <span class="kc">None</span><span class="p">)</span>
</pre></div>
</div>
<p>This is an alternate constructor that allows the construction of a <code class="docutils literal notranslate"><span class="pre">ZoneInfo</span></code>
object from any TZif byte stream. This constructor takes an optional
parameter, <code class="docutils literal notranslate"><span class="pre">key</span></code>, which sets the name of the zone, for the purposes of
<code class="docutils literal notranslate"><span class="pre">__str__</span></code> and <code class="docutils literal notranslate"><span class="pre">__repr__</span></code> (see <a class="reference internal" href="#string-representation">Representations</a>).</p>
<p>Unlike the primary constructor, this always constructs a new object. There are
two reasons that this deviates from the primary constructors caching behavior:
stream objects have mutable state and so determining whether two inputs are
identical is difficult or impossible, and it is likely that users constructing
from a file specifically want to load from that file and not a cache.</p>
<p>As with <code class="docutils literal notranslate"><span class="pre">ZoneInfo.no_cache</span></code>, objects constructed by this method must not be
added to the cache.</p>
</section>
<section id="behavior-during-data-updates">
<h4><a class="toc-backref" href="#behavior-during-data-updates" role="doc-backlink">Behavior during data updates</a></h4>
<p>It is important that a given <code class="docutils literal notranslate"><span class="pre">ZoneInfo</span></code> objects behavior not change during
its lifetime, because a <code class="docutils literal notranslate"><span class="pre">datetime</span></code>s <code class="docutils literal notranslate"><span class="pre">utcoffset()</span></code> method is used in both
its equality and hash calculations, and if the result were to change during the
<code class="docutils literal notranslate"><span class="pre">datetime</span></code>s lifetime, it could break the invariant for all hashable objects
<a class="footnote-reference brackets" href="#hashable-def" id="id8">[3]</a> <a class="footnote-reference brackets" href="#hashes-equality" id="id9">[4]</a> that if <code class="docutils literal notranslate"><span class="pre">x</span> <span class="pre">==</span> <span class="pre">y</span></code>, it must also be true
that <code class="docutils literal notranslate"><span class="pre">hash(x)</span> <span class="pre">==</span> <span class="pre">hash(y)</span></code> <a class="reference internal" href="#c" id="id10"><span>[c]</span></a> .</p>
<p>Considering both the preservation of <code class="docutils literal notranslate"><span class="pre">datetime</span></code>s invariants and the
primary constructors contract to always return the same object when called
with identical arguments, if a source of time zone data is updated during a run
of the interpreter, it must not invalidate any caches or modify any
existing <code class="docutils literal notranslate"><span class="pre">ZoneInfo</span></code> objects. Newly constructed <code class="docutils literal notranslate"><span class="pre">ZoneInfo</span></code> objects, however,
should come from the updated data source.</p>
<p>This means that the point at which the data source is updated for new
invocations of the <code class="docutils literal notranslate"><span class="pre">ZoneInfo</span></code> constructor depends primarily on the semantics
of the caching behavior. The only guaranteed way to get a <code class="docutils literal notranslate"><span class="pre">ZoneInfo</span></code> object
from an updated data source is to induce a cache miss, either by bypassing the
cache and using <code class="docutils literal notranslate"><span class="pre">ZoneInfo.no_cache</span></code> or by clearing the cache.</p>
<div class="admonition note">
<p class="admonition-title">Note</p>
<p>The specified cache behavior does not require that the cache be lazily
populated — it is consistent with the specification (though not
recommended) to eagerly pre-populate the cache with time zones that have
never been constructed.</p>
</div>
</section>
<section id="deliberate-cache-invalidation">
<h4><a class="toc-backref" href="#deliberate-cache-invalidation" role="doc-backlink">Deliberate cache invalidation</a></h4>
<p>In addition to <code class="docutils literal notranslate"><span class="pre">ZoneInfo.no_cache</span></code>, which allows a user to <em>bypass</em> the
cache, <code class="docutils literal notranslate"><span class="pre">ZoneInfo</span></code> also exposes a <code class="docutils literal notranslate"><span class="pre">clear_cache</span></code> method to deliberately
invalidate either the entire cache or selective portions of the cache:</p>
<div class="highlight-default notranslate"><div class="highlight"><pre><span></span><span class="n">ZoneInfo</span><span class="o">.</span><span class="n">clear_cache</span><span class="p">(</span><span class="o">*</span><span class="p">,</span> <span class="n">only_keys</span><span class="p">:</span> <span class="n">Iterable</span><span class="p">[</span><span class="nb">str</span><span class="p">]</span><span class="o">=</span><span class="kc">None</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="kc">None</span>
</pre></div>
</div>
<p>If no arguments are passed, all caches are invalidated and the first call for
each key to the primary <code class="docutils literal notranslate"><span class="pre">ZoneInfo</span></code> constructor after the cache has been
cleared will return a new instance.</p>
<div class="highlight-default notranslate"><div class="highlight"><pre><span></span><span class="gp">&gt;&gt;&gt; </span><span class="n">NYC0</span> <span class="o">=</span> <span class="n">ZoneInfo</span><span class="p">(</span><span class="s2">&quot;America/New_York&quot;</span><span class="p">)</span>
<span class="gp">&gt;&gt;&gt; </span><span class="n">NYC0</span> <span class="ow">is</span> <span class="n">ZoneInfo</span><span class="p">(</span><span class="s2">&quot;America/New_York&quot;</span><span class="p">)</span>
<span class="go">True</span>
<span class="gp">&gt;&gt;&gt; </span><span class="n">ZoneInfo</span><span class="o">.</span><span class="n">clear_cache</span><span class="p">()</span>
<span class="gp">&gt;&gt;&gt; </span><span class="n">NYC1</span> <span class="o">=</span> <span class="n">ZoneInfo</span><span class="p">(</span><span class="s2">&quot;America/New_York&quot;</span><span class="p">)</span>
<span class="gp">&gt;&gt;&gt; </span><span class="n">NYC0</span> <span class="ow">is</span> <span class="n">NYC1</span>
<span class="go">False</span>
<span class="gp">&gt;&gt;&gt; </span><span class="n">NYC1</span> <span class="ow">is</span> <span class="n">ZoneInfo</span><span class="p">(</span><span class="s2">&quot;America/New_York&quot;</span><span class="p">)</span>
<span class="go">True</span>
</pre></div>
</div>
<p>An optional parameter, <code class="docutils literal notranslate"><span class="pre">only_keys</span></code>, takes an iterable of keys to clear from
the cache, otherwise leaving the cache intact.</p>
<div class="highlight-default notranslate"><div class="highlight"><pre><span></span><span class="gp">&gt;&gt;&gt; </span><span class="n">NYC0</span> <span class="o">=</span> <span class="n">ZoneInfo</span><span class="p">(</span><span class="s2">&quot;America/New_York&quot;</span><span class="p">)</span>
<span class="gp">&gt;&gt;&gt; </span><span class="n">LA0</span> <span class="o">=</span> <span class="n">ZoneInfo</span><span class="p">(</span><span class="s2">&quot;America/Los_Angeles&quot;</span><span class="p">)</span>
<span class="gp">&gt;&gt;&gt; </span><span class="n">ZoneInfo</span><span class="o">.</span><span class="n">clear_cache</span><span class="p">(</span><span class="n">only_keys</span><span class="o">=</span><span class="p">[</span><span class="s2">&quot;America/New_York&quot;</span><span class="p">])</span>
<span class="gp">&gt;&gt;&gt; </span><span class="n">NYC1</span> <span class="o">=</span> <span class="n">ZoneInfo</span><span class="p">(</span><span class="s2">&quot;America/New_York&quot;</span><span class="p">)</span>
<span class="gp">&gt;&gt;&gt; </span><span class="n">LA0</span> <span class="o">=</span> <span class="n">ZoneInfo</span><span class="p">(</span><span class="s2">&quot;America/Los_Angeles&quot;</span><span class="p">)</span>
<span class="gp">&gt;&gt;&gt; </span><span class="n">NYC0</span> <span class="ow">is</span> <span class="n">NYC1</span>
<span class="go">False</span>
<span class="gp">&gt;&gt;&gt; </span><span class="n">LA0</span> <span class="ow">is</span> <span class="n">LA1</span>
<span class="go">True</span>
</pre></div>
</div>
<p>Manipulation of the cache behavior is expected to be a niche use case; this
function is primarily provided to facilitate testing, and to allow users with
unusual requirements to tune the cache invalidation behavior to their needs.</p>
</section>
<section id="string-representation">
<h4><a class="toc-backref" href="#string-representation" role="doc-backlink">String representation</a></h4>
<p>The <code class="docutils literal notranslate"><span class="pre">ZoneInfo</span></code> classs <code class="docutils literal notranslate"><span class="pre">__str__</span></code> representation will be drawn from the
<code class="docutils literal notranslate"><span class="pre">key</span></code> parameter. This is partially because the <code class="docutils literal notranslate"><span class="pre">key</span></code> represents a
human-readable “name” of the string, but also because it is a useful parameter
that users will want exposed. It is necessary to provide a mechanism to expose
the key for serialization between languages and because it is also a primary
key for localization projects like CLDR (the Unicode Common Locale Data
Repository <a class="footnote-reference brackets" href="#cldr" id="id11">[5]</a>).</p>
<p>An example:</p>
<div class="highlight-default notranslate"><div class="highlight"><pre><span></span><span class="gp">&gt;&gt;&gt; </span><span class="n">zone</span> <span class="o">=</span> <span class="n">ZoneInfo</span><span class="p">(</span><span class="s2">&quot;Pacific/Kwajalein&quot;</span><span class="p">)</span>
<span class="gp">&gt;&gt;&gt; </span><span class="nb">str</span><span class="p">(</span><span class="n">zone</span><span class="p">)</span>
<span class="go">&#39;Pacific/Kwajalein&#39;</span>
<span class="gp">&gt;&gt;&gt; </span><span class="n">dt</span> <span class="o">=</span> <span class="n">datetime</span><span class="p">(</span><span class="mi">2020</span><span class="p">,</span> <span class="mi">4</span><span class="p">,</span> <span class="mi">1</span><span class="p">,</span> <span class="mi">3</span><span class="p">,</span> <span class="mi">15</span><span class="p">,</span> <span class="n">tzinfo</span><span class="o">=</span><span class="n">zone</span><span class="p">)</span>
<span class="gp">&gt;&gt;&gt; </span><span class="sa">f</span><span class="s2">&quot;</span><span class="si">{</span><span class="n">dt</span><span class="o">.</span><span class="n">isoformat</span><span class="p">()</span><span class="si">}</span><span class="s2"> [</span><span class="si">{</span><span class="n">dt</span><span class="o">.</span><span class="n">tzinfo</span><span class="si">}</span><span class="s2">]&quot;</span>
<span class="go">&#39;2020-04-01T03:15:00+12:00 [Pacific/Kwajalein]&#39;</span>
</pre></div>
</div>
<p>When a <code class="docutils literal notranslate"><span class="pre">key</span></code> is not specified, the <code class="docutils literal notranslate"><span class="pre">str</span></code> operation should not fail, but
should return the objectss <code class="docutils literal notranslate"><span class="pre">__repr__</span></code>:</p>
<div class="highlight-default notranslate"><div class="highlight"><pre><span></span><span class="gp">&gt;&gt;&gt; </span><span class="n">zone</span> <span class="o">=</span> <span class="n">ZoneInfo</span><span class="o">.</span><span class="n">from_file</span><span class="p">(</span><span class="n">f</span><span class="p">)</span>
<span class="gp">&gt;&gt;&gt; </span><span class="nb">str</span><span class="p">(</span><span class="n">zone</span><span class="p">)</span>
<span class="go">&#39;ZoneInfo.from_file(&lt;_io.BytesIO object at ...&gt;)&#39;</span>
</pre></div>
</div>
<p>The <code class="docutils literal notranslate"><span class="pre">__repr__</span></code> for a <code class="docutils literal notranslate"><span class="pre">ZoneInfo</span></code> is implementation-defined and not
necessarily stable between versions, but it must not be a valid <code class="docutils literal notranslate"><span class="pre">ZoneInfo</span></code>
key, to avoid confusion between a key-derived <code class="docutils literal notranslate"><span class="pre">ZoneInfo</span></code> with a valid
<code class="docutils literal notranslate"><span class="pre">__str__</span></code> and a file-derived <code class="docutils literal notranslate"><span class="pre">ZoneInfo</span></code> which has fallen through to the
<code class="docutils literal notranslate"><span class="pre">__repr__</span></code>.</p>
<p>Since the use of <code class="docutils literal notranslate"><span class="pre">str()</span></code> to access the key provides no easy way to check
for the <em>presence</em> of a key (the only way is to try constructing a <code class="docutils literal notranslate"><span class="pre">ZoneInfo</span></code>
from it and detect whether it raises an exception), <code class="docutils literal notranslate"><span class="pre">ZoneInfo</span></code> objects will
also expose a read-only <code class="docutils literal notranslate"><span class="pre">key</span></code> attribute, which will be <code class="docutils literal notranslate"><span class="pre">None</span></code> in the event
that no key was supplied.</p>
</section>
<section id="pickle-serialization">
<h4><a class="toc-backref" href="#pickle-serialization" role="doc-backlink">Pickle serialization</a></h4>
<p>Rather than serializing all transition data, <code class="docutils literal notranslate"><span class="pre">ZoneInfo</span></code> objects will be
serialized by key, and <code class="docutils literal notranslate"><span class="pre">ZoneInfo</span></code> objects constructed from raw files (even
those with a value for <code class="docutils literal notranslate"><span class="pre">key</span></code> specified) cannot be pickled.</p>
<p>The behavior of a <code class="docutils literal notranslate"><span class="pre">ZoneInfo</span></code> object depends on how it was constructed:</p>
<ol class="arabic">
<li><code class="docutils literal notranslate"><span class="pre">ZoneInfo(key)</span></code>: When constructed with the primary constructor, a
<code class="docutils literal notranslate"><span class="pre">ZoneInfo</span></code> object will be serialized by key, and when deserialized the
will use the primary constructor in the deserializing process, and thus be
expected to be the same object as other references to the same time zone.
For example, if <code class="docutils literal notranslate"><span class="pre">europe_berlin_pkl</span></code> is a string containing a pickle
constructed from <code class="docutils literal notranslate"><span class="pre">ZoneInfo(&quot;Europe/Berlin&quot;)</span></code>, one would expect the
following behavior:<div class="highlight-default notranslate"><div class="highlight"><pre><span></span><span class="gp">&gt;&gt;&gt; </span><span class="n">a</span> <span class="o">=</span> <span class="n">ZoneInfo</span><span class="p">(</span><span class="s2">&quot;Europe/Berlin&quot;</span><span class="p">)</span>
<span class="gp">&gt;&gt;&gt; </span><span class="n">b</span> <span class="o">=</span> <span class="n">pickle</span><span class="o">.</span><span class="n">loads</span><span class="p">(</span><span class="n">europe_berlin_pkl</span><span class="p">)</span>
<span class="gp">&gt;&gt;&gt; </span><span class="n">a</span> <span class="ow">is</span> <span class="n">b</span>
<span class="go">True</span>
</pre></div>
</div>
</li>
<li><code class="docutils literal notranslate"><span class="pre">ZoneInfo.no_cache(key)</span></code>: When constructed from the cache-bypassing
constructor, the <code class="docutils literal notranslate"><span class="pre">ZoneInfo</span></code> object will still be serialized by key, but
when deserialized, it will use the cache bypassing constructor. If
<code class="docutils literal notranslate"><span class="pre">europe_berlin_pkl_nc</span></code> is a string containing a pickle constructed from
<code class="docutils literal notranslate"><span class="pre">ZoneInfo.no_cache(&quot;Europe/Berlin&quot;)</span></code>, one would expect the following
behavior:<div class="highlight-default notranslate"><div class="highlight"><pre><span></span><span class="gp">&gt;&gt;&gt; </span><span class="n">a</span> <span class="o">=</span> <span class="n">ZoneInfo</span><span class="p">(</span><span class="s2">&quot;Europe/Berlin&quot;</span><span class="p">)</span>
<span class="gp">&gt;&gt;&gt; </span><span class="n">b</span> <span class="o">=</span> <span class="n">pickle</span><span class="o">.</span><span class="n">loads</span><span class="p">(</span><span class="n">europe_berlin_pkl_nc</span><span class="p">)</span>
<span class="gp">&gt;&gt;&gt; </span><span class="n">a</span> <span class="ow">is</span> <span class="n">b</span>
<span class="go">False</span>
</pre></div>
</div>
</li>
<li><code class="docutils literal notranslate"><span class="pre">ZoneInfo.from_file(fobj,</span> <span class="pre">/,</span> <span class="pre">key=None)</span></code>: When constructed from a file, the
<code class="docutils literal notranslate"><span class="pre">ZoneInfo</span></code> object will raise an exception on pickling. If an end user
wants to pickle a <code class="docutils literal notranslate"><span class="pre">ZoneInfo</span></code> constructed from a file, it is recommended
that they use a wrapper type or a custom serialization function: either
serializing by key or storing the contents of the file object and
serializing that.</li>
</ol>
<p>This method of serialization requires that the time zone data for the required
key be available on both the serializing and deserializing side, similar to the
way that references to classes and functions are expected to exist in both the
serializing and deserializing environments. It also means that no guarantees
are made about the consistency of results when unpickling a <code class="docutils literal notranslate"><span class="pre">ZoneInfo</span></code>
pickled in an environment with a different version of the time zone data.</p>
</section>
</section>
<section id="sources-for-time-zone-data">
<span id="data-sources"></span><h3><a class="toc-backref" href="#sources-for-time-zone-data" role="doc-backlink">Sources for time zone data</a></h3>
<p>One of the hardest challenges for IANA time zone support is keeping the data up
to date; between 1997 and 2020, there have been between 3 and 21 releases per
year, often in response to changes in time zone rules with little to no notice
(see <a class="footnote-reference brackets" href="#timing-of-tz-changes" id="id12">[7]</a> for more details). In order to keep up to date,
and to give the system administrator control over the data source, we propose
to use system-deployed time zone data wherever possible. However, not all
systems ship a publicly accessible time zone database — notably Windows uses a
different system for managing time zones — and so if available <code class="docutils literal notranslate"><span class="pre">zoneinfo</span></code>
falls back to an installable first-party package, <code class="docutils literal notranslate"><span class="pre">tzdata</span></code>, available on
PyPI. <a class="reference internal" href="#d" id="id13"><span>[d]</span></a> If no system zoneinfo files are found but <code class="docutils literal notranslate"><span class="pre">tzdata</span></code> is installed, the
primary <code class="docutils literal notranslate"><span class="pre">ZoneInfo</span></code> constructor will use <code class="docutils literal notranslate"><span class="pre">tzdata</span></code> as the time zone source.</p>
<section id="system-time-zone-information">
<h4><a class="toc-backref" href="#system-time-zone-information" role="doc-backlink">System time zone information</a></h4>
<p>Many Unix-like systems deploy time zone data by default, or provide a canonical
time zone data package (often called <code class="docutils literal notranslate"><span class="pre">tzdata</span></code>, as it is on Arch Linux, Fedora,
and Debian). Whenever possible, it would be preferable to defer to the system
time zone information, because this allows time zone information for all
language stacks to be updated and maintained in one place. Python distributors
are encouraged to ensure that time zone data is installed alongside Python
whenever possible (e.g. by declaring <code class="docutils literal notranslate"><span class="pre">tzdata</span></code> as a dependency for the
<code class="docutils literal notranslate"><span class="pre">python</span></code> package).</p>
<p>The <code class="docutils literal notranslate"><span class="pre">zoneinfo</span></code> module will use a “search path” strategy analogous to the
<code class="docutils literal notranslate"><span class="pre">PATH</span></code> environment variable or the <code class="docutils literal notranslate"><span class="pre">sys.path</span></code> variable in Python; the
<code class="docutils literal notranslate"><span class="pre">zoneinfo.TZPATH</span></code> variable will be read-only (see <a class="reference internal" href="#search-path-config">search-path-config</a> for
more details), ordered list of time zone data locations to search. When
creating a <code class="docutils literal notranslate"><span class="pre">ZoneInfo</span></code> instance from a key, the zone file will be constructed
from the first data source on the path in which the key exists, so for example,
if <code class="docutils literal notranslate"><span class="pre">TZPATH</span></code> were:</p>
<div class="highlight-default notranslate"><div class="highlight"><pre><span></span><span class="n">TZPATH</span> <span class="o">=</span> <span class="p">(</span>
<span class="s2">&quot;/usr/share/zoneinfo&quot;</span><span class="p">,</span>
<span class="s2">&quot;/etc/zoneinfo&quot;</span>
<span class="p">)</span>
</pre></div>
</div>
<p>and (although this would be very unusual) <code class="docutils literal notranslate"><span class="pre">/usr/share/zoneinfo</span></code> contained
only <code class="docutils literal notranslate"><span class="pre">America/New_York</span></code> and <code class="docutils literal notranslate"><span class="pre">/etc/zoneinfo</span></code> contained both
<code class="docutils literal notranslate"><span class="pre">America/New_York</span></code> and <code class="docutils literal notranslate"><span class="pre">Europe/Moscow</span></code>, then
<code class="docutils literal notranslate"><span class="pre">ZoneInfo(&quot;America/New_York&quot;)</span></code> would be satisfied by
<code class="docutils literal notranslate"><span class="pre">/usr/share/zoneinfo/America/New_York</span></code>, while <code class="docutils literal notranslate"><span class="pre">ZoneInfo(&quot;Europe/Moscow&quot;)</span></code>
would be satisfied by <code class="docutils literal notranslate"><span class="pre">/etc/zoneinfo/Europe/Moscow</span></code>.</p>
<p>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. On
non-Windows systems, the search path will default to a list of the most
commonly observed search paths. Although this is subject to change in future
versions, at launch the default search path will be:</p>
<div class="highlight-default notranslate"><div class="highlight"><pre><span></span><span class="n">TZPATH</span> <span class="o">=</span> <span class="p">(</span>
<span class="s2">&quot;/usr/share/zoneinfo&quot;</span><span class="p">,</span>
<span class="s2">&quot;/usr/lib/zoneinfo&quot;</span><span class="p">,</span>
<span class="s2">&quot;/usr/share/lib/zoneinfo&quot;</span><span class="p">,</span>
<span class="s2">&quot;/etc/zoneinfo&quot;</span><span class="p">,</span>
<span class="p">)</span>
</pre></div>
</div>
<p>This may be configured both at compile time or at runtime; more information on
configuration options at <a class="reference internal" href="#search-path-config">search-path-config</a>.</p>
</section>
<section id="the-tzdata-python-package">
<h4><a class="toc-backref" href="#the-tzdata-python-package" role="doc-backlink">The <code class="docutils literal notranslate"><span class="pre">tzdata</span></code> Python package</a></h4>
<p>In order to ensure easy access to time zone data for all end users, this PEP
proposes to create a data-only package <code class="docutils literal notranslate"><span class="pre">tzdata</span></code> as a fallback for when system
data is not available. The <code class="docutils literal notranslate"><span class="pre">tzdata</span></code> package would be distributed on PyPI as
a “first party” package <a class="reference internal" href="#d" id="id14"><span>[d]</span></a>, maintained by the CPython development team.</p>
<p>The <code class="docutils literal notranslate"><span class="pre">tzdata</span></code> package contains only data and metadata, with no public-facing
functions or classes. It will be designed to be compatible with both newer
<code class="docutils literal notranslate"><span class="pre">importlib.resources</span></code> <a class="footnote-reference brackets" href="#importlib-resources" id="id15">[11]</a> access patterns and older
access patterns like <code class="docutils literal notranslate"><span class="pre">pkgutil.get_data</span></code> <a class="footnote-reference brackets" href="#pkgutil-data" id="id16">[12]</a> .</p>
<p>While it is designed explicitly for the use of CPython, the <code class="docutils literal notranslate"><span class="pre">tzdata</span></code> package
is 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.</p>
</section>
</section>
<section id="search-path-configuration">
<span id="search-path-config"></span><h3><a class="toc-backref" href="#search-path-configuration" role="doc-backlink">Search path configuration</a></h3>
<p>The time zone search path is very system-dependent, and sometimes even
application-dependent, and as such it makes sense to provide options to
customize it. This PEP provides for three such avenues for customization:</p>
<ol class="arabic simple">
<li>Global configuration via a compile-time option</li>
<li>Per-run configuration via environment variables</li>
<li>Runtime configuration change via a <code class="docutils literal notranslate"><span class="pre">reset_tzpath</span></code> function</li>
</ol>
<p>In all methods of configuration, the search path must consist of only absolute,
rather than relative paths. Implementations may choose to ignore, warn or raise
an exception if a string other than an absolute path is found (and may make
different choices depending on the context — e.g. raising an exception when an
invalid path is passed to <code class="docutils literal notranslate"><span class="pre">reset_tzpath</span></code> but warning when one is included in
the environment variable). If an exception is not raised, any strings other
than an absolute path must not be included in the time zone search path.</p>
<section id="compile-time-options">
<h4><a class="toc-backref" href="#compile-time-options" role="doc-backlink">Compile-time options</a></h4>
<p>It is most likely that downstream distributors will know exactly where their
system time zone data is deployed, and so a compile-time option
<code class="docutils literal notranslate"><span class="pre">PYTHONTZPATH</span></code> will be provided to set the default search path.</p>
<p>The <code class="docutils literal notranslate"><span class="pre">PYTHONTZPATH</span></code> option should be a string delimited by <code class="docutils literal notranslate"><span class="pre">os.pathsep</span></code>,
listing possible locations for the time zone data to be deployed (e.g.
<code class="docutils literal notranslate"><span class="pre">/usr/share/zoneinfo</span></code>).</p>
</section>
<section id="environment-variables">
<h4><a class="toc-backref" href="#environment-variables" role="doc-backlink">Environment variables</a></h4>
<p>When initializing <code class="docutils literal notranslate"><span class="pre">TZPATH</span></code> (and whenever <code class="docutils literal notranslate"><span class="pre">reset_tzpath</span></code> is called with no
arguments), the <code class="docutils literal notranslate"><span class="pre">zoneinfo</span></code> module will use the environment variable
<code class="docutils literal notranslate"><span class="pre">PYTHONTZPATH</span></code>, if it exists, to set the search path.</p>
<p><code class="docutils literal notranslate"><span class="pre">PYTHONTZPATH</span></code> is an <code class="docutils literal notranslate"><span class="pre">os.pathsep</span></code>-delimited string which <em>replaces</em> (rather
than augments) the default time zone path. Some examples of the proposed
semantics:</p>
<div class="highlight-console notranslate"><div class="highlight"><pre><span></span><span class="gp">$ </span>python<span class="w"> </span>print_tzpath.py
<span class="go">(&quot;/usr/share/zoneinfo&quot;,</span>
<span class="go"> &quot;/usr/lib/zoneinfo&quot;,</span>
<span class="go"> &quot;/usr/share/lib/zoneinfo&quot;,</span>
<span class="go"> &quot;/etc/zoneinfo&quot;)</span>
<span class="gp">$ </span><span class="nv">PYTHONTZPATH</span><span class="o">=</span><span class="s2">&quot;/etc/zoneinfo:/usr/share/zoneinfo&quot;</span><span class="w"> </span>python<span class="w"> </span>print_tzpath.py
<span class="go">(&quot;/etc/zoneinfo&quot;,</span>
<span class="go"> &quot;/usr/share/zoneinfo&quot;)</span>
<span class="gp">$ </span><span class="nv">PYTHONTZPATH</span><span class="o">=</span><span class="s2">&quot;&quot;</span><span class="w"> </span>python<span class="w"> </span>print_tzpath.py
<span class="gp gp-VirtualEnv">()</span>
</pre></div>
</div>
<p>This provides no built-in mechanism for prepending or appending to the default
search path, as these use cases are likely to be somewhat more niche. It should
be possible to populate an environment variable with the default search path
fairly easily:</p>
<div class="highlight-console notranslate"><div class="highlight"><pre><span></span><span class="gp">$ </span><span class="nb">export</span><span class="w"> </span><span class="nv">DEFAULT_TZPATH</span><span class="o">=</span><span class="k">$(</span>python<span class="w"> </span>-c<span class="w"> </span><span class="se">\</span>
<span class="w"> </span><span class="s2">&quot;import os, zoneinfo; print(os.pathsep.join(zoneinfo.TZPATH))&quot;</span><span class="k">)</span>
</pre></div>
</div>
</section>
<section id="reset-tzpath-function">
<h4><a class="toc-backref" href="#reset-tzpath-function" role="doc-backlink"><code class="docutils literal notranslate"><span class="pre">reset_tzpath</span></code> function</a></h4>
<p><code class="docutils literal notranslate"><span class="pre">zoneinfo</span></code> provides a <code class="docutils literal notranslate"><span class="pre">reset_tzpath</span></code> function that allows for changing the
search path at runtime.</p>
<div class="highlight-default notranslate"><div class="highlight"><pre><span></span><span class="k">def</span> <span class="nf">reset_tzpath</span><span class="p">(</span>
<span class="n">to</span><span class="p">:</span> <span class="n">Optional</span><span class="p">[</span><span class="n">Sequence</span><span class="p">[</span><span class="n">Union</span><span class="p">[</span><span class="nb">str</span><span class="p">,</span> <span class="n">os</span><span class="o">.</span><span class="n">PathLike</span><span class="p">]]]</span> <span class="o">=</span> <span class="kc">None</span>
<span class="p">)</span> <span class="o">-&gt;</span> <span class="kc">None</span><span class="p">:</span>
<span class="o">...</span>
</pre></div>
</div>
<p>When called with a sequence of paths, this function sets <code class="docutils literal notranslate"><span class="pre">zoneinfo.TZPATH</span></code> to
a tuple constructed from the desired value. When called with no arguments or
<code class="docutils literal notranslate"><span class="pre">None</span></code>, this function resets <code class="docutils literal notranslate"><span class="pre">zoneinfo.TZPATH</span></code> to the default
configuration.</p>
<p>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 the
<code class="docutils literal notranslate"><span class="pre">tzdata</span></code> package. It is not likely that <code class="docutils literal notranslate"><span class="pre">reset_tzpath</span></code> will be a common
operation, save perhaps in test functions sensitive to time zone configuration,
but it seems preferable to provide an official mechanism for changing this
rather than allowing a proliferation of hacks around the immutability of
<code class="docutils literal notranslate"><span class="pre">TZPATH</span></code>.</p>
<div class="admonition caution">
<p class="admonition-title">Caution</p>
<p>Although changing <code class="docutils literal notranslate"><span class="pre">TZPATH</span></code> during a run is a supported operation, users
should be advised that doing so may occasionally lead to unusual semantics,
and when making design trade-offs greater weight will be afforded to using
a static <code class="docutils literal notranslate"><span class="pre">TZPATH</span></code>, which is the much more common use case.</p>
</div>
<p>As noted in <a class="reference internal" href="#constructors">Constructors</a>, the primary <code class="docutils literal notranslate"><span class="pre">ZoneInfo</span></code> constructor employs a cache
to ensure that two identically-constructed <code class="docutils literal notranslate"><span class="pre">ZoneInfo</span></code> objects always compare
as identical (i.e. <code class="docutils literal notranslate"><span class="pre">ZoneInfo(key)</span> <span class="pre">is</span> <span class="pre">ZoneInfo(key)</span></code>), and the nature of this
cache is implementation-defined. This means that the behavior of the
<code class="docutils literal notranslate"><span class="pre">ZoneInfo</span></code> constructor may be unpredictably inconsistent in some situations
when used with the same <code class="docutils literal notranslate"><span class="pre">key</span></code> under different values of <code class="docutils literal notranslate"><span class="pre">TZPATH</span></code>. For
example:</p>
<div class="highlight-default notranslate"><div class="highlight"><pre><span></span><span class="gp">&gt;&gt;&gt; </span><span class="n">reset_tzpath</span><span class="p">(</span><span class="n">to</span><span class="o">=</span><span class="p">[</span><span class="s2">&quot;/my/custom/tzdb&quot;</span><span class="p">])</span>
<span class="gp">&gt;&gt;&gt; </span><span class="n">a</span> <span class="o">=</span> <span class="n">ZoneInfo</span><span class="p">(</span><span class="s2">&quot;My/Custom/Zone&quot;</span><span class="p">)</span>
<span class="gp">&gt;&gt;&gt; </span><span class="n">reset_tzpath</span><span class="p">()</span>
<span class="gp">&gt;&gt;&gt; </span><span class="n">b</span> <span class="o">=</span> <span class="n">ZoneInfo</span><span class="p">(</span><span class="s2">&quot;My/Custom/Zone&quot;</span><span class="p">)</span>
<span class="gp">&gt;&gt;&gt; </span><span class="k">del</span> <span class="n">a</span>
<span class="gp">&gt;&gt;&gt; </span><span class="k">del</span> <span class="n">b</span>
<span class="gp">&gt;&gt;&gt; </span><span class="n">c</span> <span class="o">=</span> <span class="n">ZoneInfo</span><span class="p">(</span><span class="s2">&quot;My/Custom/Zone&quot;</span><span class="p">)</span>
</pre></div>
</div>
<p>In this example, <code class="docutils literal notranslate"><span class="pre">My/Custom/Zone</span></code> exists only in the <code class="docutils literal notranslate"><span class="pre">/my/custom/tzdb</span></code> and
not on the default search path. In all implementations the constructor for
<code class="docutils literal notranslate"><span class="pre">a</span></code> must succeed. It is implementation-defined whether the constructor for
<code class="docutils literal notranslate"><span class="pre">b</span></code> succeeds, but if it does, it must be true that <code class="docutils literal notranslate"><span class="pre">a</span> <span class="pre">is</span> <span class="pre">b</span></code>, because both
<code class="docutils literal notranslate"><span class="pre">a</span></code> and <code class="docutils literal notranslate"><span class="pre">b</span></code> are references to the same key. It is also
implementation-defined whether the constructor for <code class="docutils literal notranslate"><span class="pre">c</span></code> succeeds.
Implementations of <code class="docutils literal notranslate"><span class="pre">zoneinfo</span></code> <em>may</em> return the object constructed in previous
constructor calls, or they may fail with an exception.</p>
</section>
</section>
</section>
<section id="backwards-compatibility">
<h2><a class="toc-backref" href="#backwards-compatibility" role="doc-backlink">Backwards Compatibility</a></h2>
<p>This will have no backwards compatibility issues as it will create a new API.</p>
<p>With only minor modification, a backport with support for Python 3.6+ of the
<code class="docutils literal notranslate"><span class="pre">zoneinfo</span></code> module could be created.</p>
<p>The <code class="docutils literal notranslate"><span class="pre">tzdata</span></code> package is designed to be “data only”, and should support any
version of Python that it can be built for (including Python 2.7).</p>
</section>
<section id="security-implications">
<h2><a class="toc-backref" href="#security-implications" role="doc-backlink">Security Implications</a></h2>
<p>This will require parsing zoneinfo data from disk, mostly from system locations
but potentially from user-supplied data. Errors in the implementation
(particularly the C code) could cause potential security issues, but there is
no special risk relative to parsing other file types.</p>
<p>Because the time zone data keys are essentially paths relative to some time
zone root, implementations should take care to avoid path traversal attacks.
Requesting keys such as <code class="docutils literal notranslate"><span class="pre">../../../path/to/something</span></code> should not reveal
anything about the state of the file system outside of the time zone path.</p>
</section>
<section id="reference-implementation">
<h2><a class="toc-backref" href="#reference-implementation" role="doc-backlink">Reference Implementation</a></h2>
<p>An initial reference implementation is available at
<a class="reference external" href="https://github.com/pganssle/zoneinfo">https://github.com/pganssle/zoneinfo</a></p>
<p>This may eventually be converted into a backport for 3.6+.</p>
</section>
<section id="rejected-ideas">
<h2><a class="toc-backref" href="#rejected-ideas" role="doc-backlink">Rejected Ideas</a></h2>
<section id="building-a-custom-tzdb-compiler">
<h3><a class="toc-backref" href="#building-a-custom-tzdb-compiler" role="doc-backlink">Building a custom tzdb compiler</a></h3>
<p>One major concern with the use of the TZif format is that it does not actually
contain enough information to always correctly determine the value to return
for <code class="docutils literal notranslate"><span class="pre">tzinfo.dst()</span></code>. This is because for any given time zone offset, TZif
only marks the UTC offset and whether or not it represents a DST offset, but
<code class="docutils literal notranslate"><span class="pre">tzinfo.dst()</span></code> returns the total amount of the DST shift, so that the
“standard” offset can be reconstructed from <code class="docutils literal notranslate"><span class="pre">datetime.utcoffset()</span> <span class="pre">-</span>
<span class="pre">datetime.dst()</span></code>. The value to use for <code class="docutils literal notranslate"><span class="pre">dst()</span></code> can be determined by finding
the equivalent STD offset and calculating the difference, but the TZif format
does not specify which offsets form STD/DST pairs, and so heuristics must be
used to determine this.</p>
<p>One common heuristic — looking at the most recent standard offset — notably
fails in the case of the time zone changes in Portugal in 1992 and 1996, where
the “standard” offset was shifted by 1 hour during a DST transition, leading to
a transition from STD to DST status with no change in offset. In fact, it is
possible (though it has never happened) for a time zone to be created that is
permanently DST and has no standard offsets.</p>
<p>Although this information is missing in the compiled TZif binaries, it is
present in the raw tzdb files, and it would be possible to parse this
information ourselves and create a more suitable binary format.</p>
<p>This idea was rejected for several reasons:</p>
<ol class="arabic simple">
<li>It precludes the use of any system-deployed time zone information, which is
usually present only in TZif format.</li>
<li>The raw tzdb format, while stable, is <em>less</em> stable than the TZif format;
some downstream tzdb parsers have already run into problems with old
deployments of their custom parsers becoming incompatible with recent tzdb
releases, leading to the creation of a “rearguard” format to ease the
transition. <a class="footnote-reference brackets" href="#rearguard" id="id17">[8]</a></li>
<li>Heuristics currently suffice in <code class="docutils literal notranslate"><span class="pre">dateutil</span></code> and <code class="docutils literal notranslate"><span class="pre">pytz</span></code> for all known time
zones, historical and present, and it is not very likely that new time zones
will appear that cannot be captured by heuristics — though it is somewhat
more likely that new rules that are not captured by the <em>current</em> generation
of heuristics will appear; in that case, bugfixes would be required to
accommodate the changed situation.</li>
<li>The <code class="docutils literal notranslate"><span class="pre">dst()</span></code> methods utility (and in fact the <code class="docutils literal notranslate"><span class="pre">isdst</span></code> parameter in TZif)
is somewhat questionable to start with, as almost all the useful information
is contained in the <code class="docutils literal notranslate"><span class="pre">utcoffset()</span></code> and <code class="docutils literal notranslate"><span class="pre">tzname()</span></code> methods, which are not
subject to the same problems.</li>
</ol>
<p>In short, maintaining a custom tzdb compiler or compiled package adds
maintenance burdens to both the CPython dev team and system administrators, and
its main benefit is to address a hypothetical failure that would likely have
minimal real world effects were it to occur.</p>
</section>
<section id="including-tzdata-in-the-standard-library-by-default">
<span id="why-no-default-tzdata"></span><h3><a class="toc-backref" href="#including-tzdata-in-the-standard-library-by-default" role="doc-backlink">Including <code class="docutils literal notranslate"><span class="pre">tzdata</span></code> in the standard library by default</a></h3>
<p>Although <a class="pep reference internal" href="../pep-0453/" title="PEP 453 Explicit bootstrapping of pip in Python installations">PEP 453</a>, which introduced the <code class="docutils literal notranslate"><span class="pre">ensurepip</span></code>
mechanism to CPython, provides a convenient template for a standard library
module maintained on PyPI, a potentially similar <code class="docutils literal notranslate"><span class="pre">ensuretzdata</span></code> mechanism is
somewhat less necessary, and would be complicated enough that it is considered
out of scope for this PEP.</p>
<p>Because the <code class="docutils literal notranslate"><span class="pre">zoneinfo</span></code> module is designed to use the system time zone data
wherever possible, the <code class="docutils literal notranslate"><span class="pre">tzdata</span></code> package is unnecessary (and may be
undesirable) on systems that deploy time zone data, and so it does not seem
critical to ship <code class="docutils literal notranslate"><span class="pre">tzdata</span></code> with CPython.</p>
<p>It is also not yet clear how these hybrid standard library / PyPI modules
should be updated, (other than <code class="docutils literal notranslate"><span class="pre">pip</span></code>, which has a natural mechanism for
updates and notifications) and since it is not critical to the operation of the
module, it seems prudent to defer any such proposal.</p>
</section>
<section id="support-for-leap-seconds">
<h3><a class="toc-backref" href="#support-for-leap-seconds" role="doc-backlink">Support for leap seconds</a></h3>
<p>In addition to time zone offset and name rules, the IANA time zone database
also provides a source of leap second data. This is deemed out of scope because
<code class="docutils literal notranslate"><span class="pre">datetime.datetime</span></code> currently has no support for leap seconds, and the
question of leap second data can be deferred until leap second support is
added.</p>
<p>The first-party <code class="docutils literal notranslate"><span class="pre">tzdata</span></code> package should ship the leap second data, even if it
is not used by the <code class="docutils literal notranslate"><span class="pre">zoneinfo</span></code> module.</p>
</section>
<section id="using-a-pytz-like-interface">
<h3><a class="toc-backref" href="#using-a-pytz-like-interface" role="doc-backlink">Using a <code class="docutils literal notranslate"><span class="pre">pytz</span></code>-like interface</a></h3>
<p>A <code class="docutils literal notranslate"><span class="pre">pytz</span></code>-like (<a class="footnote-reference brackets" href="#pytz" id="id18">[18]</a>) interface was proposed in <a class="pep reference internal" href="../pep-0431/" title="PEP 431 Time zone support improvements">PEP 431</a>, but
was ultimately withdrawn / rejected for lack of ambiguous datetime support.
<a class="pep reference internal" href="../pep-0495/" title="PEP 495 Local Time Disambiguation">PEP 495</a> added the <code class="docutils literal notranslate"><span class="pre">fold</span></code> attribute to address this problem, but
<code class="docutils literal notranslate"><span class="pre">fold</span></code> obviates the need for <code class="docutils literal notranslate"><span class="pre">pytz</span></code>s non-standard <code class="docutils literal notranslate"><span class="pre">tzinfo</span></code> classes, and
so a <code class="docutils literal notranslate"><span class="pre">pytz</span></code>-like interface is no longer necessary. <a class="footnote-reference brackets" href="#fastest-footgun" id="id19">[2]</a></p>
<p>The <code class="docutils literal notranslate"><span class="pre">zoneinfo</span></code> approach is more closely based on <code class="docutils literal notranslate"><span class="pre">dateutil.tz</span></code>, which
implemented support for <code class="docutils literal notranslate"><span class="pre">fold</span></code> (including a backport to older versions) just
before the release of Python 3.6.</p>
</section>
<section id="windows-support-via-microsoft-s-icu-api">
<h3><a class="toc-backref" href="#windows-support-via-microsoft-s-icu-api" role="doc-backlink">Windows support via Microsofts ICU API</a></h3>
<p>Windows does not ship the time zone database as TZif files, but as of Windows
10s 2017 Creators Update, Microsoft has provided an API for interacting with
the International Components for Unicode (ICU) project <a class="footnote-reference brackets" href="#icu-project" id="id20">[13]</a>
<a class="footnote-reference brackets" href="#ms-icu-documentation" id="id21">[14]</a> , which includes an API for accessing time zone data —
sourced from the IANA time zone database. <a class="footnote-reference brackets" href="#icu-timezone-api" id="id22">[15]</a></p>
<p>Providing bindings for this would allow us to support Windows “out of the box”
without the need to install the <code class="docutils literal notranslate"><span class="pre">tzdata</span></code> package, but unfortunately the C
headers provided by Windows do not provide any access to the underlying time
zone data — only an API to query the system for transition and offset
information is available. This would constrain the semantics of any ICU-based
implementation in ways that may not be compatible with a non-ICU-based
implementation — particularly around the behavior of the cache.</p>
<p>Since it seems like ICU cannot be used as simply an additional data source for
<code class="docutils literal notranslate"><span class="pre">ZoneInfo</span></code> objects, this PEP considers the ICU support to be out of scope, and
probably better supported by a third-party library.</p>
</section>
<section id="alternative-environment-variable-configurations">
<h3><a class="toc-backref" href="#alternative-environment-variable-configurations" role="doc-backlink">Alternative environment variable configurations</a></h3>
<p>This PEP proposes to use a single environment variable: <code class="docutils literal notranslate"><span class="pre">PYTHONTZPATH</span></code>.
This is based on the assumption that the majority of users who would want to
manipulate the time zone path would want to fully replace it (e.g. “I know
exactly where my time zone data is”), and other use cases like prepending to
the existing search path would be less common.</p>
<p>There are several other schemes that were considered and rejected:</p>
<ol class="arabic">
<li>Separate <code class="docutils literal notranslate"><span class="pre">PYTHON_TZPATH</span></code> into two environment variables:
<code class="docutils literal notranslate"><span class="pre">DEFAULT_PYTHONTZPATH</span></code> and <code class="docutils literal notranslate"><span class="pre">PYTHONTZPATH</span></code>, where <code class="docutils literal notranslate"><span class="pre">PYTHONTZPATH</span></code> would
contain values to append (or prepend) to the default time zone path, and
<code class="docutils literal notranslate"><span class="pre">DEFAULT_PYTHONTZPATH</span></code> would <em>replace</em> the default time zone path. This
was rejected because it would likely lead to user confusion if the primary
use case is to replace rather than augment.</li>
<li>Adding either <code class="docutils literal notranslate"><span class="pre">PYTHONTZPATH_PREPEND</span></code>, <code class="docutils literal notranslate"><span class="pre">PYTHONTZPATH_APPEND</span></code> or both, so
that users can augment the search path on either end without attempting to
determine what the default time zone path is. This was rejected as likely to
be unnecessary, and because it could easily be added in a
backwards-compatible manner in future updates if there is much demand for
such a feature.</li>
<li>Use only the <code class="docutils literal notranslate"><span class="pre">PYTHONTZPATH</span></code> variable, but provide a custom special value
that represents the default time zone path, e.g. <code class="docutils literal notranslate"><span class="pre">&lt;&lt;DEFAULT_TZPATH&gt;&gt;</span></code>, so
users could append to the time zone path with, e.g.
<code class="docutils literal notranslate"><span class="pre">PYTHONTZPATH=&lt;&lt;DEFAULT_TZPATH&gt;&gt;:/my/path</span></code> could be used to append
<code class="docutils literal notranslate"><span class="pre">/my/path</span></code> to the end of the time zone path.<p>One advantage to this scheme would be that it would add a natural extension
point for specifying non-file-based elements on the search path, such as
changing the priority of <code class="docutils literal notranslate"><span class="pre">tzdata</span></code> if it exists, or if native support for
<span class="target" id="index-2"></span><a class="rfc reference external" href="https://datatracker.ietf.org/doc/html/rfc7808.html"><strong>TZDIST</strong></a> were to be added to the library in the future.</p>
<p>This was rejected mainly because these sort of special values are not
usually found in <code class="docutils literal notranslate"><span class="pre">PATH</span></code>-like variables and the only currently proposed use
case is a stand-in for the default <code class="docutils literal notranslate"><span class="pre">TZPATH</span></code>, which can be acquired by
executing a Python program to query for the default value. An additional
factor in rejecting this is that because <code class="docutils literal notranslate"><span class="pre">PYTHONTZPATH</span></code> accepts only
absolute paths, any string that does not represent a valid absolute path is
implicitly reserved for future use, so it would be possible to introduce
these special values as necessary in a backwards-compatible way in future
versions of the library.</p>
</li>
</ol>
</section>
<section id="using-the-datetime-module">
<h3><a class="toc-backref" href="#using-the-datetime-module" role="doc-backlink">Using the <code class="docutils literal notranslate"><span class="pre">datetime</span></code> module</a></h3>
<p>One possible idea would be to add <code class="docutils literal notranslate"><span class="pre">ZoneInfo</span></code> to the <code class="docutils literal notranslate"><span class="pre">datetime</span></code> module,
rather than giving it its own separate module. This PEP favors the use of
a separate <code class="docutils literal notranslate"><span class="pre">zoneinfo</span></code> module,though a nested <code class="docutils literal notranslate"><span class="pre">datetime.zoneinfo</span></code> module
was also under consideration.</p>
<section id="arguments-against-putting-zoneinfo-directly-into-datetime">
<h4><a class="toc-backref" href="#arguments-against-putting-zoneinfo-directly-into-datetime" role="doc-backlink">Arguments against putting <code class="docutils literal notranslate"><span class="pre">ZoneInfo</span></code> directly into <code class="docutils literal notranslate"><span class="pre">datetime</span></code></a></h4>
<p>The <code class="docutils literal notranslate"><span class="pre">datetime</span></code> module is already somewhat crowded, as it has many classes
with somewhat complex behavior — <code class="docutils literal notranslate"><span class="pre">datetime.datetime</span></code>, <code class="docutils literal notranslate"><span class="pre">datetime.date</span></code>,
<code class="docutils literal notranslate"><span class="pre">datetime.time</span></code>, <code class="docutils literal notranslate"><span class="pre">datetime.timedelta</span></code>, <code class="docutils literal notranslate"><span class="pre">datetime.timezone</span></code> and
<code class="docutils literal notranslate"><span class="pre">datetime.tzinfo</span></code>. The modules implementation and documentation are already
quite complicated, and it is probably beneficial to try to not to compound the
problem if it can be helped.</p>
<p>The <code class="docutils literal notranslate"><span class="pre">ZoneInfo</span></code> class is also in some ways different from all the other
classes provided by <code class="docutils literal notranslate"><span class="pre">datetime</span></code>; the other classes are all intended to be
lean, simple data types, whereas the <code class="docutils literal notranslate"><span class="pre">ZoneInfo</span></code> class is more complex: it is
a parser for a specific format (TZif), a representation for the information
stored in that format and a mechanism to look up the information in well-known
locations in the system.</p>
<p>Finally, while it is true that someone who needs the <code class="docutils literal notranslate"><span class="pre">zoneinfo</span></code> module also
needs the <code class="docutils literal notranslate"><span class="pre">datetime</span></code> module, the reverse is not necessarily true: many people
will want to use <code class="docutils literal notranslate"><span class="pre">datetime</span></code> without <code class="docutils literal notranslate"><span class="pre">zoneinfo</span></code>. Considering that
<code class="docutils literal notranslate"><span class="pre">zoneinfo</span></code> will likely pull in additional, possibly more heavy-weight
standard library modules, it would be preferable to allow the two to be
imported separately — particularly if potential “tree shaking” distributions
are in Pythons future. <a class="footnote-reference brackets" href="#tree-shaking" id="id23">[9]</a></p>
<p>In the final analysis, it makes sense to keep <code class="docutils literal notranslate"><span class="pre">zoneinfo</span></code> a separate module
with a separate documentation page rather than to put its classes and functions
directly into <code class="docutils literal notranslate"><span class="pre">datetime</span></code>.</p>
</section>
<section id="using-datetime-zoneinfo-instead-of-zoneinfo">
<h4><a class="toc-backref" href="#using-datetime-zoneinfo-instead-of-zoneinfo" role="doc-backlink">Using <code class="docutils literal notranslate"><span class="pre">datetime.zoneinfo</span></code> instead of <code class="docutils literal notranslate"><span class="pre">zoneinfo</span></code></a></h4>
<p>A more palatable configuration may be to nest <code class="docutils literal notranslate"><span class="pre">zoneinfo</span></code> as a module under
<code class="docutils literal notranslate"><span class="pre">datetime</span></code>, as <code class="docutils literal notranslate"><span class="pre">datetime.zoneinfo</span></code>.</p>
<p>Arguments in favor of this:</p>
<ol class="arabic simple">
<li>It neatly namespaces <code class="docutils literal notranslate"><span class="pre">zoneinfo</span></code> together with <code class="docutils literal notranslate"><span class="pre">datetime</span></code></li>
<li>The <code class="docutils literal notranslate"><span class="pre">timezone</span></code> class is already in <code class="docutils literal notranslate"><span class="pre">datetime</span></code>, and it may seem strange
that some time zones are in <code class="docutils literal notranslate"><span class="pre">datetime</span></code> and others are in a top-level
module.</li>
<li>As mentioned earlier, importing <code class="docutils literal notranslate"><span class="pre">zoneinfo</span></code> necessarily requires importing
<code class="docutils literal notranslate"><span class="pre">datetime</span></code>, so it is no imposition to require importing the parent module.</li>
</ol>
<p>Arguments against this:</p>
<ol class="arabic">
<li>In order to avoid forcing all <code class="docutils literal notranslate"><span class="pre">datetime</span></code> users to import <code class="docutils literal notranslate"><span class="pre">zoneinfo</span></code>, the
<code class="docutils literal notranslate"><span class="pre">zoneinfo</span></code> module would need to be lazily imported, which means that
end-users would need to explicitly import <code class="docutils literal notranslate"><span class="pre">datetime.zoneinfo</span></code> (as opposed
to importing <code class="docutils literal notranslate"><span class="pre">datetime</span></code> and accessing the <code class="docutils literal notranslate"><span class="pre">zoneinfo</span></code> attribute on the
module). This is the way <code class="docutils literal notranslate"><span class="pre">dateutil</span></code> works (all submodules are lazily
imported), and it is a perennial source of confusion for end users.<p>This confusing requirement from end-users can be avoided using a
module-level <code class="docutils literal notranslate"><span class="pre">__getattr__</span></code> and <code class="docutils literal notranslate"><span class="pre">__dir__</span></code> per <a class="pep reference internal" href="../pep-0562/" title="PEP 562 Module __getattr__ and __dir__">PEP 562</a>, but this would
add some complexity to the implementation of the <code class="docutils literal notranslate"><span class="pre">datetime</span></code> module. This
sort of behavior in modules or classes tends to confuse static analysis
tools, which may not be desirable for a library as widely used and critical
as <code class="docutils literal notranslate"><span class="pre">datetime</span></code>.</p>
</li>
<li>Nesting the implementation under <code class="docutils literal notranslate"><span class="pre">datetime</span></code> would likely require
<code class="docutils literal notranslate"><span class="pre">datetime</span></code> to be reorganized from a single-file module (<code class="docutils literal notranslate"><span class="pre">datetime.py</span></code>)
to a directory with an <code class="docutils literal notranslate"><span class="pre">__init__.py</span></code>. This is a minor concern, but the
structure of the <code class="docutils literal notranslate"><span class="pre">datetime</span></code> module has been stable for many years, and it
would be preferable to avoid churn if possible.<p>This concern <em>could</em> be alleviated by implementing <code class="docutils literal notranslate"><span class="pre">zoneinfo</span></code> as
<code class="docutils literal notranslate"><span class="pre">_zoneinfo.py</span></code> and importing it as <code class="docutils literal notranslate"><span class="pre">zoneinfo</span></code> from within <code class="docutils literal notranslate"><span class="pre">datetime</span></code>,
but this does not seem desirable from an aesthetic or code organization
standpoint, and it would preclude the version of nesting where end users are
required to explicitly import <code class="docutils literal notranslate"><span class="pre">datetime.zoneinfo</span></code>.</p>
</li>
</ol>
<p>This PEP takes the position that on balance it would be best to use a separate
top-level <code class="docutils literal notranslate"><span class="pre">zoneinfo</span></code> module because the benefits of nesting are not so great
that it overwhelms the practical implementation concerns.</p>
</section>
</section>
</section>
<section id="footnotes">
<h2><a class="toc-backref" href="#footnotes" role="doc-backlink">Footnotes</a></h2>
<div role="list" class="citation-list">
<div class="citation" id="a" role="doc-biblioentry">
<dt class="label" id="a">[<a href="#id3">a</a>]</dt>
<dd>The claim that the vast majority of users only want a few types of time
zone is based on anecdotal impressions rather than anything remotely
scientific. As one data point, <code class="docutils literal notranslate"><span class="pre">dateutil</span></code> provides many time zone types,
but user support mostly focuses on these three types.</div>
<div class="citation" id="b" role="doc-biblioentry">
<dt class="label" id="b">[<a href="#id5">b</a>]</dt>
<dd>The statement that identically constructed <code class="docutils literal notranslate"><span class="pre">ZoneInfo</span></code> objects should be
identical objects may be violated if the user deliberately clears the time
zone cache.</div>
<div class="citation" id="c" role="doc-biblioentry">
<dt class="label" id="c">[<a href="#id10">c</a>]</dt>
<dd>The hash value for a given <code class="docutils literal notranslate"><span class="pre">datetime</span></code> is cached on first calculation, so
we do not need to worry about the possibly more serious issue that a given
<code class="docutils literal notranslate"><span class="pre">datetime</span></code> objects hash would change during its lifetime.</div>
<div class="citation" id="d" role="doc-biblioentry">
<dt class="label" id="d">[d]<em> (<a href='#id1'>1</a>, <a href='#id13'>2</a>, <a href='#id14'>3</a>) </em></dt>
<dd>The term “first party” here is distinguished from “third party” in that,
although it is distributed via PyPI and is not currently included in
Python by default, it is to be considered an official sub-project of
CPython rather than a “blessed” third-party package.</div>
</div>
</section>
<section id="references">
<h2><a class="toc-backref" href="#references" role="doc-backlink">References</a></h2>
<aside class="footnote-list brackets">
<aside class="footnote brackets" id="nontransitive-comp" role="doc-footnote">
<dt class="label" id="nontransitive-comp">[<a href="#id6">1</a>]</dt>
<dd>Paul Ganssle: “A curious case of non-transitive datetime comparison”
(Published 15 February 2018)
<a class="reference external" href="https://blog.ganssle.io/articles/2018/02/a-curious-case-datetimes.html">https://blog.ganssle.io/articles/2018/02/a-curious-case-datetimes.html</a></aside>
<aside class="footnote brackets" id="fastest-footgun" role="doc-footnote">
<dt class="label" id="fastest-footgun">[<a href="#id19">2</a>]</dt>
<dd>Paul Ganssle: “pytz: The Fastest Footgun in the West” (Published 19 March
2018) <a class="reference external" href="https://blog.ganssle.io/articles/2018/03/pytz-fastest-footgun.html">https://blog.ganssle.io/articles/2018/03/pytz-fastest-footgun.html</a></aside>
<aside class="footnote brackets" id="hashable-def" role="doc-footnote">
<dt class="label" id="hashable-def">[<a href="#id8">3</a>]</dt>
<dd>Python documentation: “Glossary” (Version 3.8.2)
<a class="reference external" href="https://docs.python.org/3/glossary.html#term-hashable">https://docs.python.org/3/glossary.html#term-hashable</a></aside>
<aside class="footnote brackets" id="hashes-equality" role="doc-footnote">
<dt class="label" id="hashes-equality">[<a href="#id9">4</a>]</dt>
<dd>Hynek Schlawack: “Python Hashes and Equality” (Published 20 November 2017)
<a class="reference external" href="https://hynek.me/articles/hashes-and-equality/">https://hynek.me/articles/hashes-and-equality/</a></aside>
<aside class="footnote brackets" id="cldr" role="doc-footnote">
<dt class="label" id="cldr">[<a href="#id11">5</a>]</dt>
<dd>CLDR: Unicode Common Locale Data Repository
<a class="reference external" href="http://cldr.unicode.org/#TOC-How-to-Use">http://cldr.unicode.org/#TOC-How-to-Use</a>-</aside>
<aside class="footnote brackets" id="tzdb-wiki" role="doc-footnote">
<dt class="label" id="tzdb-wiki">[<a href="#id4">6</a>]</dt>
<dd>Wikipedia page for Tz database:
<a class="reference external" href="https://en.wikipedia.org/wiki/Tz_database">https://en.wikipedia.org/wiki/Tz_database</a></aside>
<aside class="footnote brackets" id="timing-of-tz-changes" role="doc-footnote">
<dt class="label" id="timing-of-tz-changes">[<a href="#id12">7</a>]</dt>
<dd>Code of Matt: “On the Timing of Time Zone Changes” (Matt Johnson-Pint, 23
April 2016) <a class="reference external" href="https://codeofmatt.com/on-the-timing-of-time-zone-changes/">https://codeofmatt.com/on-the-timing-of-time-zone-changes/</a></aside>
<aside class="footnote brackets" id="rearguard" role="doc-footnote">
<dt class="label" id="rearguard">[<a href="#id17">8</a>]</dt>
<dd>tz mailing list: [PROPOSED] Support zi parsers that mishandle negative DST
offsets (Paul Eggert, 23 April 2018)
<a class="reference external" href="https://mm.icann.org/pipermail/tz/2018-April/026421.html">https://mm.icann.org/pipermail/tz/2018-April/026421.html</a></aside>
<aside class="footnote brackets" id="tree-shaking" role="doc-footnote">
<dt class="label" id="tree-shaking">[<a href="#id23">9</a>]</dt>
<dd>“Russell Keith-Magee: Python On Other Platforms” (15 May 2019, Jesse Jiryu
Davis)
<a class="reference external" href="https://pyfound.blogspot.com/2019/05/russell-keith-magee-python-on-other.html">https://pyfound.blogspot.com/2019/05/russell-keith-magee-python-on-other.html</a></aside>
<aside class="footnote brackets" id="tzinfo" role="doc-footnote">
<dt class="label" id="tzinfo">[<a href="#id2">10</a>]</dt>
<dd><code class="docutils literal notranslate"><span class="pre">datetime.tzinfo</span></code> documentation
<a class="reference external" href="https://docs.python.org/3/library/datetime.html#datetime.tzinfo">https://docs.python.org/3/library/datetime.html#datetime.tzinfo</a></aside>
<aside class="footnote brackets" id="importlib-resources" role="doc-footnote">
<dt class="label" id="importlib-resources">[<a href="#id15">11</a>]</dt>
<dd><code class="docutils literal notranslate"><span class="pre">importlib.resources</span></code> documentation
<a class="reference external" href="https://docs.python.org/3/library/importlib.html#module-importlib.resources">https://docs.python.org/3/library/importlib.html#module-importlib.resources</a></aside>
<aside class="footnote brackets" id="pkgutil-data" role="doc-footnote">
<dt class="label" id="pkgutil-data">[<a href="#id16">12</a>]</dt>
<dd><code class="docutils literal notranslate"><span class="pre">pkgutil.get_data</span></code> documentation
<a class="reference external" href="https://docs.python.org/3/library/pkgutil.html#pkgutil.get_data">https://docs.python.org/3/library/pkgutil.html#pkgutil.get_data</a></aside>
<aside class="footnote brackets" id="icu-project" role="doc-footnote">
<dt class="label" id="icu-project">[<a href="#id20">13</a>]</dt>
<dd>ICU TimeZone classes
<a class="reference external" href="http://userguide.icu-project.org/datetime/timezone">http://userguide.icu-project.org/datetime/timezone</a></aside>
<aside class="footnote brackets" id="ms-icu-documentation" role="doc-footnote">
<dt class="label" id="ms-icu-documentation">[<a href="#id21">14</a>]</dt>
<dd>Microsoft documentation for International Components for Unicode (ICU)
<a class="reference external" href="https://docs.microsoft.com/en-us/windows/win32/intl/international-components-for-unicode--icu-">https://docs.microsoft.com/en-us/windows/win32/intl/international-components-for-unicodeicu-</a></aside>
<aside class="footnote brackets" id="icu-timezone-api" role="doc-footnote">
<dt class="label" id="icu-timezone-api">[<a href="#id22">15</a>]</dt>
<dd><code class="docutils literal notranslate"><span class="pre">icu::TimeZone</span></code> class documentation
<a class="reference external" href="https://unicode-org.github.io/icu-docs/apidoc/released/icu4c/classicu_1_1TimeZone.html">https://unicode-org.github.io/icu-docs/apidoc/released/icu4c/classicu_1_1TimeZone.html</a></aside>
</aside>
<section id="other-time-zone-implementations">
<h3><a class="toc-backref" href="#other-time-zone-implementations" role="doc-backlink">Other time zone implementations:</a></h3>
<aside class="footnote-list brackets">
<aside class="footnote brackets" id="dateutil-tz" role="doc-footnote">
<dt class="label" id="dateutil-tz">[<a href="#id7">16</a>]</dt>
<dd><code class="docutils literal notranslate"><span class="pre">dateutil.tz</span></code>
<a class="reference external" href="https://dateutil.readthedocs.io/en/stable/tz.html">https://dateutil.readthedocs.io/en/stable/tz.html</a></aside>
<aside class="footnote brackets" id="dateutil-tzwin" role="doc-footnote">
<dt class="label" id="dateutil-tzwin">[17]</dt>
<dd><code class="docutils literal notranslate"><span class="pre">dateutil.tz.win</span></code>: Concrete time zone implementations wrapping Windows
time zones
<a class="reference external" href="https://dateutil.readthedocs.io/en/stable/tzwin.html">https://dateutil.readthedocs.io/en/stable/tzwin.html</a></aside>
<aside class="footnote brackets" id="pytz" role="doc-footnote">
<dt class="label" id="pytz">[<a href="#id18">18</a>]</dt>
<dd><code class="docutils literal notranslate"><span class="pre">pytz</span></code>
<a class="reference external" href="http://pytz.sourceforge.net/">http://pytz.sourceforge.net/</a></aside>
</aside>
</section>
</section>
<section id="copyright">
<h2><a class="toc-backref" href="#copyright" role="doc-backlink">Copyright</a></h2>
<p>This document is placed in the public domain or under the
CC0-1.0-Universal license, whichever is more permissive.</p>
</section>
</section>
<hr class="docutils" />
<p>Source: <a class="reference external" href="https://github.com/python/peps/blob/main/peps/pep-0615.rst">https://github.com/python/peps/blob/main/peps/pep-0615.rst</a></p>
<p>Last modified: <a class="reference external" href="https://github.com/python/peps/commits/main/peps/pep-0615.rst">2024-06-01 20:10:03 GMT</a></p>
</article>
<nav id="pep-sidebar">
<h2>Contents</h2>
<ul>
<li><a class="reference internal" href="#abstract">Abstract</a></li>
<li><a class="reference internal" href="#motivation">Motivation</a></li>
<li><a class="reference internal" href="#proposal">Proposal</a><ul>
<li><a class="reference internal" href="#the-zoneinfo-zoneinfo-class">The <code class="docutils literal notranslate"><span class="pre">zoneinfo.ZoneInfo</span></code> class</a><ul>
<li><a class="reference internal" href="#constructors">Constructors</a></li>
<li><a class="reference internal" href="#behavior-during-data-updates">Behavior during data updates</a></li>
<li><a class="reference internal" href="#deliberate-cache-invalidation">Deliberate cache invalidation</a></li>
<li><a class="reference internal" href="#string-representation">String representation</a></li>
<li><a class="reference internal" href="#pickle-serialization">Pickle serialization</a></li>
</ul>
</li>
<li><a class="reference internal" href="#sources-for-time-zone-data">Sources for time zone data</a><ul>
<li><a class="reference internal" href="#system-time-zone-information">System time zone information</a></li>
<li><a class="reference internal" href="#the-tzdata-python-package">The <code class="docutils literal notranslate"><span class="pre">tzdata</span></code> Python package</a></li>
</ul>
</li>
<li><a class="reference internal" href="#search-path-configuration">Search path configuration</a><ul>
<li><a class="reference internal" href="#compile-time-options">Compile-time options</a></li>
<li><a class="reference internal" href="#environment-variables">Environment variables</a></li>
<li><a class="reference internal" href="#reset-tzpath-function"><code class="docutils literal notranslate"><span class="pre">reset_tzpath</span></code> function</a></li>
</ul>
</li>
</ul>
</li>
<li><a class="reference internal" href="#backwards-compatibility">Backwards Compatibility</a></li>
<li><a class="reference internal" href="#security-implications">Security Implications</a></li>
<li><a class="reference internal" href="#reference-implementation">Reference Implementation</a></li>
<li><a class="reference internal" href="#rejected-ideas">Rejected Ideas</a><ul>
<li><a class="reference internal" href="#building-a-custom-tzdb-compiler">Building a custom tzdb compiler</a></li>
<li><a class="reference internal" href="#including-tzdata-in-the-standard-library-by-default">Including <code class="docutils literal notranslate"><span class="pre">tzdata</span></code> in the standard library by default</a></li>
<li><a class="reference internal" href="#support-for-leap-seconds">Support for leap seconds</a></li>
<li><a class="reference internal" href="#using-a-pytz-like-interface">Using a <code class="docutils literal notranslate"><span class="pre">pytz</span></code>-like interface</a></li>
<li><a class="reference internal" href="#windows-support-via-microsoft-s-icu-api">Windows support via Microsofts ICU API</a></li>
<li><a class="reference internal" href="#alternative-environment-variable-configurations">Alternative environment variable configurations</a></li>
<li><a class="reference internal" href="#using-the-datetime-module">Using the <code class="docutils literal notranslate"><span class="pre">datetime</span></code> module</a><ul>
<li><a class="reference internal" href="#arguments-against-putting-zoneinfo-directly-into-datetime">Arguments against putting <code class="docutils literal notranslate"><span class="pre">ZoneInfo</span></code> directly into <code class="docutils literal notranslate"><span class="pre">datetime</span></code></a></li>
<li><a class="reference internal" href="#using-datetime-zoneinfo-instead-of-zoneinfo">Using <code class="docutils literal notranslate"><span class="pre">datetime.zoneinfo</span></code> instead of <code class="docutils literal notranslate"><span class="pre">zoneinfo</span></code></a></li>
</ul>
</li>
</ul>
</li>
<li><a class="reference internal" href="#footnotes">Footnotes</a></li>
<li><a class="reference internal" href="#references">References</a><ul>
<li><a class="reference internal" href="#other-time-zone-implementations">Other time zone implementations:</a></li>
</ul>
</li>
<li><a class="reference internal" href="#copyright">Copyright</a></li>
</ul>
<br>
<a id="source" href="https://github.com/python/peps/blob/main/peps/pep-0615.rst">Page Source (GitHub)</a>
</nav>
</section>
<script src="../_static/colour_scheme.js"></script>
<script src="../_static/wrap_tables.js"></script>
<script src="../_static/sticky_banner.js"></script>
</body>
</html>