text:
PEP: 615 Title: Support for the IANA Time Zone Database in the Standard
Library Author: Paul Ganssle <paul at ganssle.io> Discussions-To:
https://discuss.python.org/t/3468 Status: Final Type: Standards Track
Created: 22-Feb-2020 Python-Version: 3.9 Post-History: 25-Feb-2020,
29-Mar-2020 Replaces: 431

zoneinfo

Abstract

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 system's time zone data if available; if no system
time zone data is available, the library will fall back to using the
first-party package tzdata, deployed on PyPI. [d]

Motivation

The datetime 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 datetime.tzinfo base class.[1]
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]

1.  UTC and fixed offsets thereof
2.  The system local time zone
3.  IANA time zones

In Python 3.2, the datetime.timezone class was introduced to support the
first class of time zone (with a special datetime.timezone.utc singleton
for UTC).

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:

    >>> print(datetime(2020, 2, 22, 12, 0).astimezone())
    2020-02-22 12:00:00-05:00
    >>> print(datetime(2020, 2, 22, 12, 0).astimezone()
    ...       .strftime("%Y-%m-%d %H:%M:%S %Z"))
    2020-02-22 12:00:00 EST
    >>> print(datetime(2020, 2, 22, 12, 0).astimezone(timezone.utc))
    2020-02-22 17:00:00+00:00

However, there is still no support for the time zones described in the
IANA time zone database (also called the "tz" database or the Olson
database [2]). 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 (6557) and for
the compiled binary (TZif) format (8536). 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.

Proposal

This PEP has three main concerns:

1.  The semantics of the zoneinfo.ZoneInfo class (zoneinfo-class)
2.  Time zone data sources used (data-sources)
3.  Options for configuration of the time zone search path
    (search-path-config)

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.

The zoneinfo.ZoneInfo class

Constructors

The initial design of the zoneinfo.ZoneInfo class has several
constructors.

    ZoneInfo(key: str)

The primary constructor takes a single argument, key, which is a string
indicating the name of a zone file in the system time zone database
(e.g. "America/New_York", "Europe/London"), and returns a ZoneInfo
constructed from the first matching data source on search path (see the
data-sources 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 ZoneInfo constructors).

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 zoneinfo.ZoneInfoNotFoundError,
which will be a subclass of KeyError.

One somewhat unusual guarantee made by this constructor is that calls
with identical arguments must return identical objects. Specifically,
for all values of key, the following assertion must always be valid [b]:

    a = ZoneInfo(key)
    b = ZoneInfo(key)
    assert a is b

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 dt1.tzinfo is dt2.tzinfo.[3] In addition to the modest
performance benefit from avoiding unnecessary proliferation of ZoneInfo
objects, providing this guarantee should minimize surprising behavior
for end users.

dateutil.tz.gettz has provided a similar guarantee since version 2.7.0
(release March 2018).[4]

Note

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

    ZoneInfo.no_cache(key: str)

This is an alternate constructor that bypasses the constructor's 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.

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:

    >>> a = ZoneInfo.no_cache(key)
    >>> b = ZoneInfo(key)
    >>> a is not b

    ZoneInfo.from_file(fobj: IO[bytes], /, key: str = None)

This is an alternate constructor that allows the construction of a
ZoneInfo object from any TZif byte stream. This constructor takes an
optional parameter, key, which sets the name of the zone, for the
purposes of __str__ and __repr__ (see Representations).

Unlike the primary constructor, this always constructs a new object.
There are two reasons that this deviates from the primary constructor's
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.

As with ZoneInfo.no_cache, objects constructed by this method must not
be added to the cache.

Behavior during data updates

It is important that a given ZoneInfo object's behavior not change
during its lifetime, because a datetime's utcoffset() method is used in
both its equality and hash calculations, and if the result were to
change during the datetime's lifetime, it could break the invariant for
all hashable objects [5][6] that if x == y, it must also be true that
hash(x) == hash(y) [c] .

Considering both the preservation of datetime's invariants and the
primary constructor's 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 ZoneInfo objects. Newly constructed
ZoneInfo objects, however, should come from the updated data source.

This means that the point at which the data source is updated for new
invocations of the ZoneInfo constructor depends primarily on the
semantics of the caching behavior. The only guaranteed way to get a
ZoneInfo object from an updated data source is to induce a cache miss,
either by bypassing the cache and using ZoneInfo.no_cache or by clearing
the cache.

Note

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.

Deliberate cache invalidation

In addition to ZoneInfo.no_cache, which allows a user to bypass the
cache, ZoneInfo also exposes a clear_cache method to deliberately
invalidate either the entire cache or selective portions of the cache:

    ZoneInfo.clear_cache(*, only_keys: Iterable[str]=None) -> None

If no arguments are passed, all caches are invalidated and the first
call for each key to the primary ZoneInfo constructor after the cache
has been cleared will return a new instance.

    >>> NYC0 = ZoneInfo("America/New_York")
    >>> NYC0 is ZoneInfo("America/New_York")
    True
    >>> ZoneInfo.clear_cache()
    >>> NYC1 = ZoneInfo("America/New_York")
    >>> NYC0 is NYC1
    False
    >>> NYC1 is ZoneInfo("America/New_York")
    True

An optional parameter, only_keys, takes an iterable of keys to clear
from the cache, otherwise leaving the cache intact.

    >>> NYC0 = ZoneInfo("America/New_York")
    >>> LA0 = ZoneInfo("America/Los_Angeles")
    >>> ZoneInfo.clear_cache(only_keys=["America/New_York"])
    >>> NYC1 = ZoneInfo("America/New_York")
    >>> LA0 = ZoneInfo("America/Los_Angeles")
    >>> NYC0 is NYC1
    False
    >>> LA0 is LA1
    True

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.

String representation

The ZoneInfo class's __str__ representation will be drawn from the key
parameter. This is partially because the key 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[7]).

An example:

    >>> zone = ZoneInfo("Pacific/Kwajalein")
    >>> str(zone)
    'Pacific/Kwajalein'

    >>> dt = datetime(2020, 4, 1, 3, 15, tzinfo=zone)
    >>> f"{dt.isoformat()} [{dt.tzinfo}]"
    '2020-04-01T03:15:00+12:00 [Pacific/Kwajalein]'

When a key is not specified, the str operation should not fail, but
should return the objects's __repr__:

    >>> zone = ZoneInfo.from_file(f)
    >>> str(zone)
    'ZoneInfo.from_file(<_io.BytesIO object at ...>)'

The __repr__ for a ZoneInfo is implementation-defined and not
necessarily stable between versions, but it must not be a valid ZoneInfo
key, to avoid confusion between a key-derived ZoneInfo with a valid
__str__ and a file-derived ZoneInfo which has fallen through to the
__repr__.

Since the use of str() to access the key provides no easy way to check
for the presence of a key (the only way is to try constructing a
ZoneInfo from it and detect whether it raises an exception), ZoneInfo
objects will also expose a read-only key attribute, which will be None
in the event that no key was supplied.

Pickle serialization

Rather than serializing all transition data, ZoneInfo objects will be
serialized by key, and ZoneInfo objects constructed from raw files (even
those with a value for key specified) cannot be pickled.

The behavior of a ZoneInfo object depends on how it was constructed:

1.  ZoneInfo(key): When constructed with the primary constructor, a
    ZoneInfo 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 europe_berlin_pkl is a string
    containing a pickle constructed from ZoneInfo("Europe/Berlin"), one
    would expect the following behavior:

        >>> a = ZoneInfo("Europe/Berlin")
        >>> b = pickle.loads(europe_berlin_pkl)
        >>> a is b
        True

2.  ZoneInfo.no_cache(key): When constructed from the cache-bypassing
    constructor, the ZoneInfo object will still be serialized by key,
    but when deserialized, it will use the cache bypassing constructor.
    If europe_berlin_pkl_nc is a string containing a pickle constructed
    from ZoneInfo.no_cache("Europe/Berlin"), one would expect the
    following behavior:

        >>> a = ZoneInfo("Europe/Berlin")
        >>> b = pickle.loads(europe_berlin_pkl_nc)
        >>> a is b
        False

3.  ZoneInfo.from_file(fobj, /, key=None): When constructed from a file,
    the ZoneInfo object will raise an exception on pickling. If an end
    user wants to pickle a ZoneInfo 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.

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 ZoneInfo pickled in an
environment with a different version of the time zone data.

Sources for time zone data

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[8] 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 zoneinfo falls back to an installable first-party package,
tzdata, available on PyPI. [d] If no system zoneinfo files are found but
tzdata is installed, the primary ZoneInfo constructor will use tzdata as
the time zone source.

System time zone information

Many Unix-like systems deploy time zone data by default, or provide a
canonical time zone data package (often called tzdata, 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
tzdata as a dependency for the python package).

The zoneinfo module will use a "search path" strategy analogous to the
PATH environment variable or the sys.path variable in Python; the
zoneinfo.TZPATH variable will be read-only (see search-path-config for
more details), ordered list of time zone data locations to search. When
creating a ZoneInfo 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 TZPATH were:

    TZPATH = (
        "/usr/share/zoneinfo",
        "/etc/zoneinfo"
        )

and (although this would be very unusual) /usr/share/zoneinfo contained
only America/New_York and /etc/zoneinfo contained both America/New_York
and Europe/Moscow, then ZoneInfo("America/New_York") would be satisfied
by /usr/share/zoneinfo/America/New_York, while ZoneInfo("Europe/Moscow")
would be satisfied by /etc/zoneinfo/Europe/Moscow.

At the moment, on Windows systems, the search path will default to
empty, because Windows does not officially ship a copy of the time zone
database. 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:

    TZPATH = (
        "/usr/share/zoneinfo",
        "/usr/lib/zoneinfo",
        "/usr/share/lib/zoneinfo",
        "/etc/zoneinfo",
    )

This may be configured both at compile time or at runtime; more
information on configuration options at search-path-config.

The tzdata Python package

In order to ensure easy access to time zone data for all end users, this
PEP proposes to create a data-only package tzdata as a fallback for when
system data is not available. The tzdata package would be distributed on
PyPI as a "first party" package [d], maintained by the CPython
development team.

The tzdata package contains only data and metadata, with no
public-facing functions or classes. It will be designed to be compatible
with both newer importlib.resources[9] access patterns and older access
patterns like pkgutil.get_data[10] .

While it is designed explicitly for the use of CPython, the tzdata
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.

Search path configuration

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:

1.  Global configuration via a compile-time option
2.  Per-run configuration via environment variables
3.  Runtime configuration change via a reset_tzpath function

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 reset_tzpath
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.

Compile-time options

It is most likely that downstream distributors will know exactly where
their system time zone data is deployed, and so a compile-time option
PYTHONTZPATH will be provided to set the default search path.

The PYTHONTZPATH option should be a string delimited by os.pathsep,
listing possible locations for the time zone data to be deployed (e.g.
/usr/share/zoneinfo).

Environment variables

When initializing TZPATH (and whenever reset_tzpath is called with no
arguments), the zoneinfo module will use the environment variable
PYTHONTZPATH, if it exists, to set the search path.

PYTHONTZPATH is an os.pathsep-delimited string which replaces (rather
than augments) the default time zone path. Some examples of the proposed
semantics:

    $ python print_tzpath.py
    ("/usr/share/zoneinfo",
     "/usr/lib/zoneinfo",
     "/usr/share/lib/zoneinfo",
     "/etc/zoneinfo")

    $ PYTHONTZPATH="/etc/zoneinfo:/usr/share/zoneinfo" python print_tzpath.py
    ("/etc/zoneinfo",
     "/usr/share/zoneinfo")

    $ PYTHONTZPATH="" python print_tzpath.py
    ()

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:

    $ export DEFAULT_TZPATH=$(python -c \
        "import os, zoneinfo; print(os.pathsep.join(zoneinfo.TZPATH))")

reset_tzpath function

zoneinfo provides a reset_tzpath function that allows for changing the
search path at runtime.

    def reset_tzpath(
        to: Optional[Sequence[Union[str, os.PathLike]]] = None
    ) -> None:
        ...

When called with a sequence of paths, this function sets zoneinfo.TZPATH
to a tuple constructed from the desired value. When called with no
arguments or None, this function resets zoneinfo.TZPATH to the default
configuration.

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 tzdata package. It is not likely that reset_tzpath 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 TZPATH.

Caution

Although changing TZPATH 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 TZPATH, which is the much more common use
case.

As noted in Constructors, the primary ZoneInfo constructor employs a
cache to ensure that two identically-constructed ZoneInfo objects always
compare as identical (i.e. ZoneInfo(key) is ZoneInfo(key)), and the
nature of this cache is implementation-defined. This means that the
behavior of the ZoneInfo constructor may be unpredictably inconsistent
in some situations when used with the same key under different values of
TZPATH. For example:

    >>> reset_tzpath(to=["/my/custom/tzdb"])
    >>> a = ZoneInfo("My/Custom/Zone")
    >>> reset_tzpath()
    >>> b = ZoneInfo("My/Custom/Zone")
    >>> del a
    >>> del b
    >>> c = ZoneInfo("My/Custom/Zone")

In this example, My/Custom/Zone exists only in the /my/custom/tzdb and
not on the default search path. In all implementations the constructor
for a must succeed. It is implementation-defined whether the constructor
for b succeeds, but if it does, it must be true that a is b, because
both a and b are references to the same key. It is also
implementation-defined whether the constructor for c succeeds.
Implementations of zoneinfo may return the object constructed in
previous constructor calls, or they may fail with an exception.

Backwards Compatibility

This will have no backwards compatibility issues as it will create a new
API.

With only minor modification, a backport with support for Python 3.6+ of
the zoneinfo module could be created.

The tzdata package is designed to be "data only", and should support any
version of Python that it can be built for (including Python 2.7).

Security Implications

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.

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 ../../../path/to/something should not
reveal anything about the state of the file system outside of the time
zone path.

Reference Implementation

An initial reference implementation is available at
https://github.com/pganssle/zoneinfo

This may eventually be converted into a backport for 3.6+.

Rejected Ideas

Building a custom tzdb compiler

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 tzinfo.dst(). 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 tzinfo.dst() returns the total amount of
the DST shift, so that the "standard" offset can be reconstructed from
datetime.utcoffset() - datetime.dst(). The value to use for dst() 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.

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.

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.

This idea was rejected for several reasons:

1.  It precludes the use of any system-deployed time zone information,
    which is usually present only in TZif format.
2.  The raw tzdb format, while stable, is less 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.[11]
3.  Heuristics currently suffice in dateutil and pytz 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 current generation of heuristics will appear; in
    that case, bugfixes would be required to accommodate the changed
    situation.
4.  The dst() method's utility (and in fact the isdst parameter in TZif)
    is somewhat questionable to start with, as almost all the useful
    information is contained in the utcoffset() and tzname() methods,
    which are not subject to the same problems.

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.

Including tzdata in the standard library by default

Although PEP 453, which introduced the ensurepip mechanism to CPython,
provides a convenient template for a standard library module maintained
on PyPI, a potentially similar ensuretzdata mechanism is somewhat less
necessary, and would be complicated enough that it is considered out of
scope for this PEP.

Because the zoneinfo module is designed to use the system time zone data
wherever possible, the tzdata package is unnecessary (and may be
undesirable) on systems that deploy time zone data, and so it does not
seem critical to ship tzdata with CPython.

It is also not yet clear how these hybrid standard library / PyPI
modules should be updated, (other than pip, 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.

Support for leap seconds

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 datetime.datetime currently has no support for leap
seconds, and the question of leap second data can be deferred until leap
second support is added.

The first-party tzdata package should ship the leap second data, even if
it is not used by the zoneinfo module.

Using a pytz-like interface

A pytz-like ([12]) interface was proposed in PEP 431, but was ultimately
withdrawn / rejected for lack of ambiguous datetime support. PEP 495
added the fold attribute to address this problem, but fold obviates the
need for pytz's non-standard tzinfo classes, and so a pytz-like
interface is no longer necessary.[13]

The zoneinfo approach is more closely based on dateutil.tz, which
implemented support for fold (including a backport to older versions)
just before the release of Python 3.6.

Windows support via Microsoft's ICU API

Windows does not ship the time zone database as TZif files, but as of
Windows 10's 2017 Creators Update, Microsoft has provided an API for
interacting with the International Components for Unicode (ICU)
project[14] [15] , which includes an API for accessing time zone data —
sourced from the IANA time zone database.[16]

Providing bindings for this would allow us to support Windows "out of
the box" without the need to install the tzdata 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.

Since it seems like ICU cannot be used as simply an additional data
source for ZoneInfo objects, this PEP considers the ICU support to be
out of scope, and probably better supported by a third-party library.

Alternative environment variable configurations

This PEP proposes to use a single environment variable: PYTHONTZPATH.
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.

There are several other schemes that were considered and rejected:

1.  Separate PYTHON_TZPATH into two environment variables:
    DEFAULT_PYTHONTZPATH and PYTHONTZPATH, where PYTHONTZPATH would
    contain values to append (or prepend) to the default time zone path,
    and DEFAULT_PYTHONTZPATH would replace 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.

2.  Adding either PYTHONTZPATH_PREPEND, PYTHONTZPATH_APPEND 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.

3.  Use only the PYTHONTZPATH variable, but provide a custom special
    value that represents the default time zone path, e.g.
    <<DEFAULT_TZPATH>>, so users could append to the time zone path
    with, e.g. PYTHONTZPATH=<<DEFAULT_TZPATH>>:/my/path could be used to
    append /my/path to the end of the time zone path.

    One advantage to this scheme would be that it would add a natural
    extension point for specifying non-file-based elements on the search
    path, such as changing the priority of tzdata if it exists, or if
    native support for TZDIST <7808> were to be added to the library in
    the future.

    This was rejected mainly because these sort of special values are
    not usually found in PATH-like variables and the only currently
    proposed use case is a stand-in for the default TZPATH, which can be
    acquired by executing a Python program to query for the default
    value. An additional factor in rejecting this is that because
    PYTHONTZPATH 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.

Using the datetime module

One possible idea would be to add ZoneInfo to the datetime module,
rather than giving it its own separate module. This PEP favors the use
of a separate zoneinfo module,though a nested datetime.zoneinfo module
was also under consideration.

Arguments against putting ZoneInfo directly into datetime

The datetime module is already somewhat crowded, as it has many classes
with somewhat complex behavior — datetime.datetime, datetime.date,
datetime.time, datetime.timedelta, datetime.timezone and
datetime.tzinfo. The module's 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.

The ZoneInfo class is also in some ways different from all the other
classes provided by datetime; the other classes are all intended to be
lean, simple data types, whereas the ZoneInfo 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.

Finally, while it is true that someone who needs the zoneinfo module
also needs the datetime module, the reverse is not necessarily true:
many people will want to use datetime without zoneinfo. Considering that
zoneinfo 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 Python's future.[17]

In the final analysis, it makes sense to keep zoneinfo a separate module
with a separate documentation page rather than to put its classes and
functions directly into datetime.

Using datetime.zoneinfo instead of zoneinfo

A more palatable configuration may be to nest zoneinfo as a module under
datetime, as datetime.zoneinfo.

Arguments in favor of this:

1.  It neatly namespaces zoneinfo together with datetime
2.  The timezone class is already in datetime, and it may seem strange
    that some time zones are in datetime and others are in a top-level
    module.
3.  As mentioned earlier, importing zoneinfo necessarily requires
    importing datetime, so it is no imposition to require importing the
    parent module.

Arguments against this:

1.  In order to avoid forcing all datetime users to import zoneinfo, the
    zoneinfo module would need to be lazily imported, which means that
    end-users would need to explicitly import datetime.zoneinfo (as
    opposed to importing datetime and accessing the zoneinfo attribute
    on the module). This is the way dateutil works (all submodules are
    lazily imported), and it is a perennial source of confusion for end
    users.

    This confusing requirement from end-users can be avoided using a
    module-level __getattr__ and __dir__ per PEP 562, but this would add
    some complexity to the implementation of the datetime 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 datetime.

2.  Nesting the implementation under datetime would likely require
    datetime to be reorganized from a single-file module (datetime.py)
    to a directory with an __init__.py. This is a minor concern, but the
    structure of the datetime module has been stable for many years, and
    it would be preferable to avoid churn if possible.

    This concern could be alleviated by implementing zoneinfo as
    _zoneinfo.py and importing it as zoneinfo from within datetime, 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 datetime.zoneinfo.

This PEP takes the position that on balance it would be best to use a
separate top-level zoneinfo module because the benefits of nesting are
not so great that it overwhelms the practical implementation concerns.

Footnotes

References

Other time zone implementations:

Copyright

This document is placed in the public domain or under the
CC0-1.0-Universal license, whichever is more permissive.

a

    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, dateutil provides many time
    zone types, but user support mostly focuses on these three types.

b

    The statement that identically constructed ZoneInfo objects should
    be identical objects may be violated if the user deliberately clears
    the time zone cache.

c

    The hash value for a given datetime is cached on first calculation,
    so we do not need to worry about the possibly more serious issue
    that a given datetime object's hash would change during its
    lifetime.

d

    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.

[1] datetime.tzinfo documentation
https://docs.python.org/3/library/datetime.html#datetime.tzinfo

[2] Wikipedia page for Tz database:
https://en.wikipedia.org/wiki/Tz_database

[3] Paul Ganssle: "A curious case of non-transitive datetime comparison"
(Published 15 February 2018)
https://blog.ganssle.io/articles/2018/02/a-curious-case-datetimes.html

[4] dateutil.tz https://dateutil.readthedocs.io/en/stable/tz.html

[5] Python documentation: "Glossary" (Version 3.8.2)
https://docs.python.org/3/glossary.html#term-hashable

[6] Hynek Schlawack: "Python Hashes and Equality" (Published 20 November
2017) https://hynek.me/articles/hashes-and-equality/

[7] CLDR: Unicode Common Locale Data Repository
http://cldr.unicode.org/#TOC-How-to-Use-

[8] Code of Matt: "On the Timing of Time Zone Changes" (Matt
Johnson-Pint, 23 April 2016)
https://codeofmatt.com/on-the-timing-of-time-zone-changes/

[9] importlib.resources documentation
https://docs.python.org/3/library/importlib.html#module-importlib.resources

[10] pkgutil.get_data documentation
https://docs.python.org/3/library/pkgutil.html#pkgutil.get_data

[11] tz mailing list: [PROPOSED] Support zi parsers that mishandle
negative DST offsets (Paul Eggert, 23 April 2018)
https://mm.icann.org/pipermail/tz/2018-April/026421.html

[12] pytz http://pytz.sourceforge.net/

[13] Paul Ganssle: "pytz: The Fastest Footgun in the West" (Published 19
March 2018)
https://blog.ganssle.io/articles/2018/03/pytz-fastest-footgun.html

[14] ICU TimeZone classes
http://userguide.icu-project.org/datetime/timezone

[15] Microsoft documentation for International Components for Unicode
(ICU)
https://docs.microsoft.com/en-us/windows/win32/intl/international-components-for-unicode--icu-

[16] icu::TimeZone class documentation
https://unicode-org.github.io/icu-docs/apidoc/released/icu4c/classicu_1_1TimeZone.html

[17] "Russell Keith-Magee: Python On Other Platforms" (15 May 2019,
Jesse Jiryu Davis)
https://pyfound.blogspot.com/2019/05/russell-keith-magee-python-on-other.html


text:
PEP: 528 Title: Change Windows console encoding to UTF-8 Version:
$Revision$ Last-Modified: $Date$ Author: Steve Dower
<EMAIL_ADDRESS>Status: Final Type: Standards Track
Content-Type: text/x-rst Created: 27-Aug-2016 Python-Version: 3.6
Post-History: 01-Sep-2016, 04-Sep-2016 Resolution:
https://mail.python.org/pipermail/python-dev/2016-September/146278.html

Abstract

Historically, Python uses the ANSI APIs for interacting with the Windows
operating system, often via C Runtime functions. However, these have
been long discouraged in favor of the UTF-16 APIs. Within the operating
system, all text is represented as UTF-16, and the ANSI APIs perform
encoding and decoding using the active code page.

This PEP proposes changing the default standard stream implementation on
Windows to use the Unicode APIs. This will allow users to print and
input the full range of Unicode characters at the default Windows
console. This also requires a subtle change to how the tokenizer parses
text from readline hooks.

Specific Changes

Add _io.WindowsConsoleIO

Currently an instance of _io.FileIO is used to wrap the file descriptors
representing standard input, output and error. We add a new class
(implemented in C) _io.WindowsConsoleIO that acts as a raw IO object
using the Windows console functions, specifically, ReadConsoleW and
WriteConsoleW.

This class will be used when the legacy-mode flag is not in effect, when
opening a standard stream by file descriptor and the stream is a console
buffer rather than a redirected file. Otherwise, _io.FileIO will be used
as it is today.

This is a raw (bytes) IO class that requires text to be passed encoded
with utf-8, which will be decoded to utf-16-le and passed to the Windows
APIs. Similarly, bytes read from the class will be provided by the
operating system as utf-16-le and converted into utf-8 when returned to
Python.

The use of an ASCII compatible encoding is required to maintain
compatibility with code that bypasses the TextIOWrapper and directly
writes ASCII bytes to the standard streams (for example, Twisted's
process_stdinreader.py). Code that assumes a particular encoding for the
standard streams other than ASCII will likely break.

Add _PyOS_WindowsConsoleReadline

To allow Unicode entry at the interactive prompt, a new readline hook is
required. The existing PyOS_StdioReadline function will delegate to the
new _PyOS_WindowsConsoleReadline function when reading from a file
descriptor that is a console buffer and the legacy-mode flag is not in
effect (the logic should be identical to above).

Since the readline interface is required to return an 8-bit encoded
string with no embedded nulls, the _PyOS_WindowsConsoleReadline function
transcodes from utf-16-le as read from the operating system into utf-8.

The function PyRun_InteractiveOneObject which currently obtains the
encoding from sys.stdin will select utf-8 unless the legacy-mode flag is
in effect. This may require readline hooks to change their encodings to
utf-8, or to require legacy-mode for correct behaviour.

Add legacy mode

Launching Python with the environment variable PYTHONLEGACYWINDOWSSTDIO
set will enable the legacy-mode flag, which completely restores the
previous behaviour.

Alternative Approaches

The win_unicode_console package is a pure-Python alternative to changing
the default behaviour of the console. It implements essentially the same
modifications as described here using pure Python code.

Code that may break

The following code patterns may break or see different behaviour as a
result of this change. All of these code samples require explicitly
choosing to use a raw file object in place of a more convenient wrapper
that would prevent any visible change.

Assuming stdin/stdout encoding

Code that assumes that the encoding required by sys.stdin.buffer or
sys.stdout.buffer is 'mbcs' or a more specific encoding may currently be
working by chance, but could encounter issues under this change. For
example:

    >>> sys.stdout.buffer.write(text.encode('mbcs'))
    >>> r = sys.stdin.buffer.read(16).decode('cp437')

To correct this code, the encoding specified on the TextIOWrapper should
be used, either implicitly or explicitly:

    >>> # Fix 1: Use wrapper correctly
    >>> sys.stdout.write(text)
    >>> r = sys.stdin.read(16)

    >>> # Fix 2: Use encoding explicitly
    >>> sys.stdout.buffer.write(text.encode(sys.stdout.encoding))
    >>> r = sys.stdin.buffer.read(16).decode(sys.stdin.encoding)

Incorrectly using the raw object

Code that uses the raw IO object and does not correctly handle partial
reads and writes may be affected. This is particularly important for
reads, where the number of characters read will never exceed one-fourth
of the number of bytes allowed, as there is no feasible way to prevent
input from encoding as much longer utf-8 strings:

    >>> raw_stdin = sys.stdin.buffer.raw
    >>> data = raw_stdin.read(15)
    abcdefghijklm
    b'abc'
    # data contains at most 3 characters, and never more than 12 bytes
    # error, as "defghijklm\r\n" is passed to the interactive prompt

To correct this code, the buffered reader/writer should be used, or the
caller should continue reading until its buffer is full:

    >>> # Fix 1: Use the buffered reader/writer
    >>> stdin = sys.stdin.buffer
    >>> data = stdin.read(15)
    abcedfghijklm
    b'abcdefghijklm\r\n'

    >>> # Fix 2: Loop until enough bytes have been read
    >>> raw_stdin = sys.stdin.buffer.raw
    >>> b = b''
    >>> while len(b) < 15:
    ...     b += raw_stdin.read(15)
    abcedfghijklm
    b'abcdefghijklm\r\n'

Using the raw object with small buffers

Code that uses the raw IO object and attempts to read less than four
characters will now receive an error. Because it's possible that any
single character may require up to four bytes when represented in utf-8,
requests must fail:

    >>> raw_stdin = sys.stdin.buffer.raw
    >>> data = raw_stdin.read(3)
    Traceback (most recent call last):
      File "<stdin>", line 1, in <module>
    ValueError: must read at least 4 bytes

The only workaround is to pass a larger buffer:

    >>> # Fix: Request at least four bytes
    >>> raw_stdin = sys.stdin.buffer.raw
    >>> data = raw_stdin.read(4)
    a
    b'a'
    >>> >>>

(The extra >>> is due to the newline remaining in the input buffer and
is expected in this situation.)

Copyright

This document has been placed in the public domain.

References

license: Public Domain


text:
PEP: 3143 Title: Standard daemon process library Version: $Revision$
Last-Modified: $Date$ Author: Ben Finney<EMAIL_ADDRESS>Status: Deferred Type: Standards Track Content-Type: text/x-rst Created:
26-Jan-2009 Python-Version: 3.x Post-History:

Abstract

Writing a program to become a well-behaved Unix daemon is somewhat
complex and tricky to get right, yet the steps are largely similar for
any daemon regardless of what else the program may need to do.

This PEP introduces a package to the Python standard library that
provides a simple interface to the task of becoming a daemon process.

PEP Deferral

Further exploration of the concepts covered in this PEP has been
deferred for lack of a current champion interested in promoting the
goals of the PEP and collecting and incorporating feedback, and with
sufficient available time to do so effectively.

Specification

Example usage

Simple example of direct DaemonContext usage:

    import daemon

    from spam import do_main_program

    with daemon.DaemonContext():
        do_main_program()

More complex example usage:

    import os
    import grp
    import signal
    import daemon
    import lockfile

    from spam import (
        initial_program_setup,
        do_main_program,
        program_cleanup,
        reload_program_config,
        )

    context = daemon.DaemonContext(
        working_directory='/var/lib/foo',
        umask=0o002,
        pidfile=lockfile.FileLock('/var/run/spam.pid'),
        )

    context.signal_map = {
        signal.SIGTERM: program_cleanup,
        signal.SIGHUP: 'terminate',
        signal.SIGUSR1: reload_program_config,
        }

    mail_gid = grp.getgrnam('mail').gr_gid
    context.gid = mail_gid

    important_file = open('spam.data', 'w')
    interesting_file = open('eggs.data', 'w')
    context.files_preserve = [important_file, interesting_file]

    initial_program_setup()

    with context:
        do_main_program()

Interface

A new package, daemon, is added to the standard library.

A class, DaemonContext, is defined to represent the settings and process
context for the program running as a daemon process.

DaemonContext objects

A DaemonContext instance represents the behaviour settings and process
context for the program when it becomes a daemon. The behaviour and
environment is customised by setting options on the instance, before
calling the open method.

Each option can be passed as a keyword argument to the DaemonContext
constructor, or subsequently altered by assigning to an attribute on the
instance at any time prior to calling open. That is, for options named
wibble and wubble, the following invocation:

    foo = daemon.DaemonContext(wibble=bar, wubble=baz)
    foo.open()

is equivalent to:

    foo = daemon.DaemonContext()
    foo.wibble = bar
    foo.wubble = baz
    foo.open()

The following options are defined.

files_preserve

    Default

        None

    List of files that should not be closed when starting the daemon. If
    None, all open file descriptors will be closed.

    Elements of the list are file descriptors (as returned by a file
    object's fileno() method) or Python file objects. Each specifies a
    file that is not to be closed during daemon start.

chroot_directory

    Default

        None

    Full path to a directory to set as the effective root directory of
    the process. If None, specifies that the root directory is not to be
    changed.

working_directory

    Default

        '/'

    Full path of the working directory to which the process should
    change on daemon start.

    Since a filesystem cannot be unmounted if a process has its current
    working directory on that filesystem, this should either be left at
    default or set to a directory that is a sensible “home directory”
    for the daemon while it is running.

umask

    Default

        0

    File access creation mask (“umask”) to set for the process on daemon
    start.

    Since a process inherits its umask from its parent process, starting
    the daemon will reset the umask to this value so that files are
    created by the daemon with access modes as it expects.

pidfile

    Default

        None

    Context manager for a PID lock file. When the daemon context opens
    and closes, it enters and exits the pidfile context manager.

detach_process

    Default

        None

    If True, detach the process context when opening the daemon context;
    if False, do not detach.

    If unspecified (None) during initialisation of the instance, this
    will be set to True by default, and False only if detaching the
    process is determined to be redundant; for example, in the case when
    the process was started by init, by initd, or by inetd.

signal_map

    Default

        system-dependent

    Mapping from operating system signals to callback actions.

    The mapping is used when the daemon context opens, and determines
    the action for each signal's signal handler:

    -   A value of None will ignore the signal (by setting the signal
        action to signal.SIG_IGN).
    -   A string value will be used as the name of an attribute on the
        DaemonContext instance. The attribute's value will be used as
        the action for the signal handler.
    -   Any other value will be used as the action for the signal
        handler.

    The default value depends on which signals are defined on the
    running system. Each item from the list below whose signal is
    actually defined in the signal module will appear in the default
    map:

    -   signal.SIGTTIN: None
    -   signal.SIGTTOU: None
    -   signal.SIGTSTP: None
    -   signal.SIGTERM: 'terminate'

    Depending on how the program will interact with its child processes,
    it may need to specify a signal map that includes the signal.SIGCHLD
    signal (received when a child process exits). See the specific
    operating system's documentation for more detail on how to determine
    what circumstances dictate the need for signal handlers.

uid

    Default

        os.getuid()

gid

    Default

        os.getgid()

    The user ID (“UID”) value and group ID (“GID”) value to switch the
    process to on daemon start.

    The default values, the real UID and GID of the process, will
    relinquish any effective privilege elevation inherited by the
    process.

prevent_core

    Default

        True

    If true, prevents the generation of core files, in order to avoid
    leaking sensitive information from daemons run as root.

stdin

    Default

        None

stdout

    Default

        None

stderr

    Default

        None

    Each of stdin, stdout, and stderr is a file-like object which will
    be used as the new file for the standard I/O stream sys.stdin,
    sys.stdout, and sys.stderr respectively. The file should therefore
    be open, with a minimum of mode 'r' in the case of stdin, and mode
    'w+' in the case of stdout and stderr.

    If the object has a fileno() method that returns a file descriptor,
    the corresponding file will be excluded from being closed during
    daemon start (that is, it will be treated as though it were listed
    in files_preserve).

    If None, the corresponding system stream is re-bound to the file
    named by os.devnull.

The following methods are defined.

open()

    Return

        None

    Open the daemon context, turning the current program into a daemon
    process. This performs the following steps:

    -   If this instance's is_open property is true, return immediately.
        This makes it safe to call open multiple times on an instance.

    -   If the prevent_core attribute is true, set the resource limits
        for the process to prevent any core dump from the process.

    -   If the chroot_directory attribute is not None, set the effective
        root directory of the process to that directory (via os.chroot).

        This allows running the daemon process inside a “chroot gaol” as
        a means of limiting the system's exposure to rogue behaviour by
        the process. Note that the specified directory needs to already
        be set up for this purpose.

    -   Set the process UID and GID to the uid and gid attribute values.

    -   Close all open file descriptors. This excludes those listed in
        the files_preserve attribute, and those that correspond to the
        stdin, stdout, or stderr attributes.

    -   Change current working directory to the path specified by the
        working_directory attribute.

    -   Reset the file access creation mask to the value specified by
        the umask attribute.

    -   If the detach_process option is true, detach the current process
        into its own process group, and disassociate from any
        controlling terminal.

    -   Set signal handlers as specified by the signal_map attribute.

    -   If any of the attributes stdin, stdout, stderr are not None,
        bind the system streams sys.stdin, sys.stdout, and/or sys.stderr
        to the files represented by the corresponding attributes. Where
        the attribute has a file descriptor, the descriptor is
        duplicated (instead of re-binding the name).

    -   If the pidfile attribute is not None, enter its context manager.

    -   Mark this instance as open (for the purpose of future open and
        close calls).

    -   Register the close method to be called during Python's exit
        processing.

    When the function returns, the running program is a daemon process.

close()

    Return

        None

    Close the daemon context. This performs the following steps:

    -   If this instance's is_open property is false, return
        immediately. This makes it safe to call close multiple times on
        an instance.
    -   If the pidfile attribute is not None, exit its context manager.
    -   Mark this instance as closed (for the purpose of future open and
        close calls).

is_open

    Return

        True if the instance is open, False otherwise.

    This property exposes the state indicating whether the instance is
    currently open. It is True if the instance's open method has been
    called and the close method has not subsequently been called.

terminate(signal_number, stack_frame)

    Return

        None

    Signal handler for the signal.SIGTERM signal. Performs the following
    step:

    -   Raise a SystemExit exception explaining the signal.

The class also implements the context manager protocol via __enter__ and
__exit__ methods.

__enter__()

    Return

        The DaemonContext instance

    Call the instance's open() method, then return the instance.

__exit__(exc_type, exc_value, exc_traceback)

    Return

        True or False as defined by the context manager protocol

    Call the instance's close() method, then return True if the
    exception was handled or False if it was not.

Motivation

The majority of programs written to be Unix daemons either implement
behaviour very similar to that in the specification, or are
poorly-behaved daemons by the correct daemon behaviour.

Since these steps should be much the same in most implementations but
are very particular and easy to omit or implement incorrectly, they are
a prime target for a standard well-tested implementation in the standard
library.

Rationale

Correct daemon behaviour

According to Stevens in [stevens] §2.6, a program should perform the
following steps to become a Unix daemon process.

-   Close all open file descriptors.
-   Change current working directory.
-   Reset the file access creation mask.
-   Run in the background.
-   Disassociate from process group.
-   Ignore terminal I/O signals.
-   Disassociate from control terminal.
-   Don't reacquire a control terminal.
-   Correctly handle the following circumstances:
    -   Started by System V init process.
    -   Daemon termination by SIGTERM signal.
    -   Children generate SIGCLD signal.

The daemon tool [slack-daemon] lists (in its summary of features)
behaviour that should be performed when turning a program into a
well-behaved Unix daemon process. It differs from this PEP's intent in
that it invokes a separate program as a daemon process. The following
features are appropriate for a daemon that starts itself once the
program is already running:

-   Sets up the correct process context for a daemon.
-   Behaves sensibly when started by initd(8) or inetd(8).
-   Revokes any suid or sgid privileges to reduce security risks in case
    daemon is incorrectly installed with special privileges.
-   Prevents the generation of core files to prevent leaking sensitive
    information from daemons run as root (optional).
-   Names the daemon by creating and locking a PID file to guarantee
    that only one daemon with the given name can execute at any given
    time (optional).
-   Sets the user and group under which to run the daemon (optional,
    root only).
-   Creates a chroot gaol (optional, root only).
-   Captures the daemon's stdout and stderr and directs them to syslog
    (optional).

A daemon is not a service

This PEP addresses only Unix-style daemons, for which the above correct
behaviour is relevant, as opposed to comparable behaviours on other
operating systems.

There is a related concept in many systems, called a “service”. A
service differs from the model in this PEP, in that rather than having
the current program continue to run as a daemon process, a service
starts an additional process to run in the background, and the current
process communicates with that additional process via some defined
channels.

The Unix-style daemon model in this PEP can be used, among other things,
to implement the background-process part of a service; but this PEP does
not address the other aspects of setting up and managing a service.

Reference Implementation

The python-daemon package [python-daemon].

Other daemon implementations

Prior to this PEP, several existing third-party Python libraries or
tools implemented some of this PEP's correct daemon behaviour.

The reference implementation is a fairly direct successor from the
following implementations:

-   Many good ideas were contributed by the community to Python cookbook
    recipes #66012 [cookbook-66012] and #278731 [cookbook-278731].
-   The bda.daemon library [bda.daemon] is an implementation of
    [cookbook-66012]. It is the predecessor of [python-daemon].

Other Python daemon implementations that differ from this PEP:

-   The zdaemon tool [zdaemon] was written for the Zope project. Like
    [slack-daemon], it differs from this specification because it is
    used to run another program as a daemon process.
-   The Python library daemon [clapper-daemon] is (according to its
    homepage) no longer maintained. As of version 1.0.1, it implements
    the basic steps from [stevens].
-   The daemonize library [seutter-daemonize] also implements the basic
    steps from [stevens].
-   Ray Burr's daemon.py module [burr-daemon] provides the [stevens]
    procedure as well as PID file handling and redirection of output to
    syslog.
-   Twisted [twisted] includes, perhaps unsurprisingly, an
    implementation of a process daemonisation API that is integrated
    with the rest of the Twisted framework; it differs significantly
    from the API in this PEP.
-   The Python initd library [dagitses-initd], which uses
    [clapper-daemon], implements an equivalent of Unix initd(8) for
    controlling a daemon process.

References

Copyright

This work is hereby placed in the public domain. To the extent that
placing a work in the public domain is not legally possible, the
copyright holder hereby grants to all recipients of this work all rights
and freedoms that would otherwise be restricted by copyright.

bda.daemon

    The bda.daemon library http://pypi.python.org/pypi/bda.daemon/ by
    Robert Niederreiter et al.

burr-daemon

    The daemon.py module http://www.nightmare.com/~ryb/code/daemon.py by
    Ray Burr.

clapper-daemon

    The daemon library http://pypi.python.org/pypi/daemon/ by Brian
    Clapper.

cookbook-278731

    Python Cookbook recipe 278731, “Creating a daemon the Python way”
    http://code.activestate.com/recipes/278731/.

cookbook-66012

    Python Cookbook recipe 66012, “Fork a daemon process on Unix”
    http://code.activestate.com/recipes/66012/.

dagitses-initd

    The Python initd library http://pypi.python.org/pypi/initd/ by
    Michael Andreas Dagitses.

python-daemon

    The python-daemon library http://pypi.python.org/pypi/python-daemon/
    by Ben Finney et al.

seutter-daemonize

    The daemonize library http://daemonize.sourceforge.net/ by Jerry
    Seutter.

slack-daemon

    The (non-Python) “libslack” implementation of a daemon tool
    http://www.libslack.org/daemon/ by “raf”<EMAIL_ADDRESS>
stevens

    Unix Network Programming, W. Richard Stevens, 1994 Prentice Hall.

twisted

    The Twisted application framework
    http://pypi.python.org/pypi/Twisted/ by Glyph Lefkowitz et al.

zdaemon

    The zdaemon tool http://pypi.python.org/pypi/zdaemon/ by Guido van
    Rossum et al.


text:
PEP: 239 Title: Adding a Rational Type to Python Author: Christopher A.
Craig<EMAIL_ADDRESS>Moshe Zadka<EMAIL_ADDRESS>Status: Rejected Type: Standards Track Created: 11-Mar-2001
Python-Version: 2.2 Post-History: 16-Mar-2001

The needs outlined in the rationale section have been addressed to some
extent by the acceptance of PEP 327 for decimal arithmetic. Guido also
noted, "Rational arithmetic was the default 'exact' arithmetic in ABC
and it did not work out as expected". See the python-dev discussion on
17 June 2005[1].

Postscript: With the acceptance of PEP 3141, "A Type Hierarchy for
Numbers", a 'Rational' numeric abstract base class was added with a
concrete implementation in the 'fractions' module.

Abstract

Python has no numeric type with the semantics of an unboundedly precise
rational number. This proposal explains the semantics of such a type,
and suggests builtin functions and literals to support such a type. This
PEP suggests no literals for rational numbers; that is left for
another PEP <240>.

Rationale

While sometimes slower and more memory intensive (in general,
unboundedly so) rational arithmetic captures more closely the
mathematical ideal of numbers, and tends to have behavior which is less
surprising to newbies. Though many Python implementations of rational
numbers have been written, none of these exist in the core, or are
documented in any way. This has made them much less accessible to people
who are less Python-savvy.

RationalType

There will be a new numeric type added called RationalType. Its unary
operators will do the obvious thing. Binary operators will coerce
integers and long integers to rationals, and rationals to floats and
complexes.

The following attributes will be supported: .numerator and .denominator.
The language definition will promise that:

    r.denominator * r == r.numerator

that the GCD of the numerator and the denominator is 1 and that the
denominator is positive.

The method r.trim(max_denominator) will return the closest rational s to
r such that abs(s.denominator) <= max_denominator.

The rational() Builtin

This function will have the signature rational(n, d=1). n and d must
both be integers, long integers or rationals. A guarantee is made that:

    rational(n, d) * d == n

Open Issues

-   Maybe the type should be called rat instead of rational. Somebody
    proposed that we have "abstract" pure mathematical types named
    complex, real, rational, integer, and "concrete" representation
    types with names like float, rat, long, int.

-   Should a rational number with an integer value be allowed as a
    sequence index? For example, should s[5/3 - 2/3] be equivalent to
    s[1]?

-   Should shift and mask operators be allowed for rational numbers? For
    rational numbers with integer values?

-   Marcin 'Qrczak' Kowalczyk summarized the arguments for and against
    unifying ints with rationals nicely on c.l.py

    Arguments for unifying ints with rationals:

    -   Since 2 == 2/1 and maybe str(2/1) == '2', it reduces surprises
        where objects seem equal but behave differently.
    -   / can be freely used for integer division when I know that there
        is no remainder (if I am wrong and there is a remainder, there
        will probably be some exception later).

    Arguments against:

    -   When I use the result of / as a sequence index, it's usually an
        error which should not be hidden by making the program working
        for some data, since it will break for other data.
    -   (this assumes that after unification int and rational would be
        different types:) Types should rarely depend on values. It's
        easier to reason when the type of a variable is known: I know
        how I can use it. I can determine that something is an int and
        expect that other objects used in this place will be ints too.
    -   (this assumes the same type for them:) Int is a good type in
        itself, not to be mixed with rationals. The fact that something
        is an integer should be expressible as a statement about its
        type. Many operations require ints and don't accept rationals.
        It's natural to think about them as about different types.

References

Copyright

This document has been placed in the public domain.

[1] Raymond Hettinger, Propose rejection of PEPs 239 and 240 -- a
builtin rational type and rational literals
https://mail.python.org/pipermail/python-dev/2005-June/054281.html

authors:
Christopher A. Craig
Moshe Zadka