2015-08-02 16:58:27 -04:00
|
|
|
PEP: 495
|
|
|
|
Title: Local Time Disambiguation
|
|
|
|
Version: $Revision$
|
|
|
|
Last-Modified: $Date$
|
2015-08-21 22:39:45 -04:00
|
|
|
Author: Alexander Belopolsky <alexander.belopolsky@gmail.com>, Tim Peters <tim.peters@gmail.com>
|
2022-02-27 17:46:36 -05:00
|
|
|
Discussions-To: datetime-sig@python.org
|
2017-07-12 15:00:30 -04:00
|
|
|
Status: Final
|
2015-08-02 16:58:27 -04:00
|
|
|
Type: Standards Track
|
|
|
|
Content-Type: text/x-rst
|
|
|
|
Created: 02-Aug-2015
|
2015-09-21 19:42:51 -04:00
|
|
|
Python-Version: 3.6
|
|
|
|
Resolution: https://mail.python.org/pipermail/datetime-sig/2015-September/000900.html
|
2015-08-02 16:58:27 -04:00
|
|
|
|
|
|
|
|
|
|
|
Abstract
|
|
|
|
========
|
|
|
|
|
2015-09-21 13:00:13 -04:00
|
|
|
This PEP adds a new attribute ``fold`` to instances of the
|
2015-08-26 22:24:22 -04:00
|
|
|
``datetime.time`` and ``datetime.datetime`` classes that can be used
|
|
|
|
to differentiate between two moments in time for which local times are
|
2015-09-21 17:12:12 -04:00
|
|
|
the same. The allowed values for the ``fold`` attribute will be 0 and 1
|
2015-08-26 22:24:22 -04:00
|
|
|
with 0 corresponding to the earlier and 1 to the later of the two
|
|
|
|
possible readings of an ambiguous local time.
|
2015-08-02 16:58:27 -04:00
|
|
|
|
2015-08-04 10:10:01 -04:00
|
|
|
|
2015-08-02 16:58:27 -04:00
|
|
|
Rationale
|
|
|
|
=========
|
|
|
|
|
2015-09-21 13:00:13 -04:00
|
|
|
In most world locations, there have been and will be times when
|
2015-08-26 22:24:22 -04:00
|
|
|
local clocks are moved back. [#]_ In those times, intervals are
|
|
|
|
introduced in which local clocks show the same time twice in the same
|
|
|
|
day. In these situations, the information displayed on a local clock
|
|
|
|
(or stored in a Python datetime instance) is insufficient to identify
|
|
|
|
a particular moment in time. The proposed solution is to add an
|
|
|
|
attribute to the ``datetime`` instances taking values of 0 and 1 that
|
|
|
|
will enumerate the two ambiguous times.
|
2015-08-02 16:58:27 -04:00
|
|
|
|
2015-09-20 20:50:45 -04:00
|
|
|
.. image:: pep-0495-daylightsavings.png
|
|
|
|
:align: center
|
|
|
|
:width: 30%
|
|
|
|
|
|
|
|
|
2015-08-21 22:28:19 -04:00
|
|
|
.. [#] People who live in locations observing the Daylight Saving
|
|
|
|
Time (DST) move their clocks back (usually one hour) every Fall.
|
2015-09-21 16:21:26 -04:00
|
|
|
|
2015-08-21 22:28:19 -04:00
|
|
|
It is less common, but occasionally clocks can be moved back for
|
|
|
|
other reasons. For example, Ukraine skipped the spring-forward
|
|
|
|
transition in March 1990 and instead, moved their clocks back on
|
|
|
|
July 1, 1990, switching from Moscow Time to Eastern European Time.
|
|
|
|
In that case, standard (winter) time was in effect before and after
|
|
|
|
the transition.
|
|
|
|
|
|
|
|
Both DST and standard time changes may result in time shifts other
|
|
|
|
than an hour.
|
|
|
|
|
2015-08-09 20:33:18 -04:00
|
|
|
|
|
|
|
Terminology
|
|
|
|
===========
|
|
|
|
|
2015-08-26 22:24:22 -04:00
|
|
|
When clocks are moved back, we say that a *fold* [#]_ is created in time.
|
2015-08-21 22:28:19 -04:00
|
|
|
When the clocks are moved forward, a *gap* is created. A local time
|
|
|
|
that falls in the fold is called *ambiguous*. A local time that falls
|
|
|
|
in the gap is called *missing*.
|
2015-08-09 20:33:18 -04:00
|
|
|
|
2015-08-26 22:24:22 -04:00
|
|
|
.. [#] The term "fall-backward fold" was invented in 1990s by Paul Eggert
|
|
|
|
of UCLA who used it in various Internet discussions related to the C language
|
|
|
|
standard that culminated in a `Defect Report #139`_.
|
|
|
|
|
|
|
|
.. _Defect Report #139: http://www.open-std.org/jtc1/sc22/wg14/docs/rr/dr_136.html
|
|
|
|
|
|
|
|
|
2015-08-09 20:33:18 -04:00
|
|
|
|
2015-08-02 16:58:27 -04:00
|
|
|
Proposal
|
|
|
|
========
|
|
|
|
|
2015-08-26 22:24:22 -04:00
|
|
|
The "fold" attribute
|
|
|
|
--------------------
|
2015-08-02 16:58:27 -04:00
|
|
|
|
2015-09-21 13:00:13 -04:00
|
|
|
We propose adding an attribute called ``fold`` to instances of the
|
|
|
|
``datetime.time`` and ``datetime.datetime`` classes. This attribute
|
|
|
|
should have the value 0 for all instances except those that represent
|
|
|
|
the second (chronologically) moment in time in an ambiguous case. For
|
|
|
|
those instances, the value will be 1. [#]_
|
2015-08-09 19:51:45 -04:00
|
|
|
|
2015-08-26 22:24:22 -04:00
|
|
|
.. [#] An instance that has ``fold=1`` in a non-ambiguous case is
|
2015-08-09 19:51:45 -04:00
|
|
|
said to represent an invalid time (or is invalid for short), but
|
|
|
|
users are not prevented from creating invalid instances by passing
|
2015-08-26 22:24:22 -04:00
|
|
|
``fold=1`` to a constructor or to a ``replace()`` method. This
|
2015-08-09 19:51:45 -04:00
|
|
|
is similar to the current situation with the instances that fall in
|
|
|
|
the spring-forward gap. Such instances don't represent any valid
|
|
|
|
time, but neither the constructors nor the ``replace()`` methods
|
|
|
|
check whether the instances that they produce are valid. Moreover,
|
|
|
|
this PEP specifies how various functions should behave when given an
|
|
|
|
invalid instance.
|
2015-08-02 16:58:27 -04:00
|
|
|
|
2015-08-30 21:40:19 -04:00
|
|
|
|
2015-08-02 16:58:27 -04:00
|
|
|
Affected APIs
|
|
|
|
-------------
|
|
|
|
|
|
|
|
Attributes
|
|
|
|
..........
|
|
|
|
|
2015-08-26 22:24:22 -04:00
|
|
|
Instances of ``datetime.time`` and ``datetime.datetime`` classes will
|
|
|
|
get a new attribute ``fold`` with two possible values: 0 and 1.
|
2015-08-02 16:58:27 -04:00
|
|
|
|
|
|
|
Constructors
|
|
|
|
............
|
|
|
|
|
|
|
|
The ``__new__`` methods of the ``datetime.time`` and
|
|
|
|
``datetime.datetime`` classes will get a new keyword-only argument
|
2015-08-26 22:24:22 -04:00
|
|
|
called ``fold`` with the default value 0. The value of the
|
|
|
|
``fold`` argument will be used to initialize the value of the
|
|
|
|
``fold`` attribute in the returned instance.
|
2015-08-02 16:58:27 -04:00
|
|
|
|
|
|
|
Methods
|
|
|
|
.......
|
|
|
|
|
|
|
|
The ``replace()`` methods of the ``datetime.time`` and
|
|
|
|
``datetime.datetime`` classes will get a new keyword-only argument
|
2015-09-21 13:00:13 -04:00
|
|
|
called ``fold``. It will behave similarly to the other ``replace()``
|
|
|
|
arguments: if the ``fold`` argument is specified and given a value 0
|
|
|
|
or 1, the new instance returned by ``replace()`` will have its
|
|
|
|
``fold`` attribute set to that value. In CPython, any non-integer
|
|
|
|
value of ``fold`` will raise a ``TypeError``, but other
|
|
|
|
implementations may allow the value ``None`` to behave the same as
|
2015-09-21 16:21:26 -04:00
|
|
|
when ``fold`` is not given. [#]_ (This is
|
|
|
|
a nod to the existing difference in treatment of ``None`` arguments
|
|
|
|
in other positions of this method across Python implementations;
|
|
|
|
it is not intended to leave the door open for future alternative
|
|
|
|
interpretation of ``fold=None``.) If the ``fold`` argument is not
|
2015-09-21 13:00:13 -04:00
|
|
|
specified, the original value of the ``fold`` attribute is copied to
|
|
|
|
the result.
|
|
|
|
|
2015-09-21 13:02:27 -04:00
|
|
|
.. [#] PyPy and pure Python implementation distributed with CPython
|
|
|
|
already allow ``None`` to mean "no change to existing
|
2015-09-21 13:00:13 -04:00
|
|
|
attribute" for all other attributes in ``replace()``.
|
2015-08-02 16:58:27 -04:00
|
|
|
|
2015-08-15 20:24:23 -04:00
|
|
|
C-API
|
|
|
|
.....
|
|
|
|
|
2015-08-26 22:24:22 -04:00
|
|
|
Access macros will be defined to extract the value of ``fold`` from
|
2015-08-15 20:24:23 -04:00
|
|
|
``PyDateTime_DateTime`` and ``PyDateTime_Time`` objects.
|
|
|
|
|
2015-09-21 13:00:13 -04:00
|
|
|
.. code::
|
2015-08-15 20:24:23 -04:00
|
|
|
|
2022-07-12 20:08:21 -04:00
|
|
|
int PyDateTime_DATE_GET_FOLD(PyDateTime_DateTime *o)
|
2015-08-15 20:24:23 -04:00
|
|
|
|
2015-08-26 22:24:22 -04:00
|
|
|
Return the value of ``fold`` as a C ``int``.
|
2015-08-15 20:24:23 -04:00
|
|
|
|
2015-09-21 13:00:13 -04:00
|
|
|
.. code::
|
|
|
|
|
2015-08-26 22:24:22 -04:00
|
|
|
int PyDateTime_TIME_GET_FOLD(PyDateTime_Time *o)
|
2015-08-15 20:24:23 -04:00
|
|
|
|
2015-08-26 22:24:22 -04:00
|
|
|
Return the value of ``fold`` as a C ``int``.
|
2015-08-15 20:24:23 -04:00
|
|
|
|
2015-08-26 22:24:22 -04:00
|
|
|
New constructors will be defined that will take an additional
|
|
|
|
argument to specify the value of ``fold`` in the created
|
2015-08-15 20:24:23 -04:00
|
|
|
instance:
|
|
|
|
|
|
|
|
.. code::
|
|
|
|
|
2015-09-21 13:00:13 -04:00
|
|
|
PyObject* PyDateTime_FromDateAndTimeAndFold(
|
|
|
|
int year, int month, int day, int hour, int minute,
|
|
|
|
int second, int usecond, int fold)
|
2015-08-15 20:24:23 -04:00
|
|
|
|
|
|
|
Return a ``datetime.datetime`` object with the specified year, month,
|
2015-08-26 22:24:22 -04:00
|
|
|
day, hour, minute, second, microsecond and fold.
|
2015-08-15 20:24:23 -04:00
|
|
|
|
|
|
|
.. code::
|
|
|
|
|
2015-09-21 13:00:13 -04:00
|
|
|
PyObject* PyTime_FromTimeAndFold(
|
|
|
|
int hour, int minute, int second, int usecond, int fold)
|
2015-08-15 20:24:23 -04:00
|
|
|
|
|
|
|
Return a ``datetime.time`` object with the specified hour, minute,
|
2015-08-26 22:24:22 -04:00
|
|
|
second, microsecond and fold.
|
2015-08-15 20:24:23 -04:00
|
|
|
|
|
|
|
|
2015-08-02 16:58:27 -04:00
|
|
|
Affected Behaviors
|
|
|
|
------------------
|
|
|
|
|
2015-08-21 22:28:19 -04:00
|
|
|
What time is it?
|
|
|
|
................
|
|
|
|
|
2015-09-21 16:21:26 -04:00
|
|
|
The ``datetime.now()`` method called without arguments will set
|
2015-08-26 22:24:22 -04:00
|
|
|
``fold=1`` when returning the second of the two ambiguous times in a
|
|
|
|
system local time fold. When called with a ``tzinfo`` argument, the
|
|
|
|
value of the ``fold`` will be determined by the ``tzinfo.fromutc()``
|
2015-09-21 16:21:26 -04:00
|
|
|
implementation. When an instance of the ``datetime.timezone`` class
|
|
|
|
(the stdlib's fixed-offset ``tzinfo`` subclass,
|
|
|
|
*e.g.* ``datetime.timezone.utc``) is passed as ``tzinfo``, the
|
2015-08-26 22:24:22 -04:00
|
|
|
returned datetime instance will always have ``fold=0``.
|
2015-09-21 16:21:26 -04:00
|
|
|
The ``datetime.utcnow()`` method is unaffected.
|
2015-08-21 22:28:19 -04:00
|
|
|
|
|
|
|
|
2015-08-07 22:58:51 -04:00
|
|
|
Conversion from naive to aware
|
|
|
|
..............................
|
|
|
|
|
2015-09-21 16:21:26 -04:00
|
|
|
A new feature is proposed to facilitate conversion from naive datetime
|
|
|
|
instances to aware.
|
|
|
|
|
2015-08-07 22:58:51 -04:00
|
|
|
The ``astimezone()`` method will now work for naive ``self``. The
|
2015-08-26 22:24:22 -04:00
|
|
|
system local timezone will be assumed in this case and the ``fold``
|
2015-08-07 22:58:51 -04:00
|
|
|
flag will be used to determine which local timezone is in effect
|
|
|
|
in the ambiguous case.
|
|
|
|
|
|
|
|
For example, on a system set to US/Eastern timezone::
|
|
|
|
|
|
|
|
>>> dt = datetime(2014, 11, 2, 1, 30)
|
|
|
|
>>> dt.astimezone().strftime('%D %T %Z%z')
|
|
|
|
'11/02/14 01:30:00 EDT-0400'
|
2015-08-26 22:24:22 -04:00
|
|
|
>>> dt.replace(fold=1).astimezone().strftime('%D %T %Z%z')
|
2015-08-07 22:58:51 -04:00
|
|
|
'11/02/14 01:30:00 EST-0500'
|
|
|
|
|
2015-09-21 16:21:26 -04:00
|
|
|
An implication is that ``datetime.now(tz)`` is fully equivalent to
|
|
|
|
``datetime.now().astimezone(tz)`` (assuming ``tz`` is an instance of a
|
|
|
|
post-PEP ``tzinfo`` implementation, i.e. one that correctly handles
|
|
|
|
and sets ``fold``).
|
|
|
|
|
2015-08-21 22:28:19 -04:00
|
|
|
|
|
|
|
Conversion from POSIX seconds from EPOCH
|
|
|
|
........................................
|
|
|
|
|
|
|
|
The ``fromtimestamp()`` static method of ``datetime.datetime`` will
|
2015-08-26 22:24:22 -04:00
|
|
|
set the ``fold`` attribute appropriately in the returned object.
|
2015-08-21 22:28:19 -04:00
|
|
|
|
|
|
|
For example, on a system set to US/Eastern timezone::
|
|
|
|
|
|
|
|
>>> datetime.fromtimestamp(1414906200)
|
|
|
|
datetime.datetime(2014, 11, 2, 1, 30)
|
|
|
|
>>> datetime.fromtimestamp(1414906200 + 3600)
|
2015-08-26 22:24:22 -04:00
|
|
|
datetime.datetime(2014, 11, 2, 1, 30, fold=1)
|
2015-08-21 22:28:19 -04:00
|
|
|
|
|
|
|
|
2015-08-07 22:58:51 -04:00
|
|
|
Conversion to POSIX seconds from EPOCH
|
|
|
|
......................................
|
|
|
|
|
|
|
|
The ``timestamp()`` method of ``datetime.datetime`` will return different
|
|
|
|
values for ``datetime.datetime`` instances that differ only by the value
|
2015-08-26 22:24:22 -04:00
|
|
|
of their ``fold`` attribute if and only if these instances represent an
|
2015-08-21 22:28:19 -04:00
|
|
|
ambiguous or a missing time.
|
2015-08-07 22:58:51 -04:00
|
|
|
|
|
|
|
When a ``datetime.datetime`` instance ``dt`` represents an ambiguous
|
2015-08-21 22:28:19 -04:00
|
|
|
time, there are two values ``s0`` and ``s1`` such that::
|
2015-08-07 22:58:51 -04:00
|
|
|
|
|
|
|
datetime.fromtimestamp(s0) == datetime.fromtimestamp(s1) == dt
|
|
|
|
|
2015-09-21 16:21:26 -04:00
|
|
|
(This is because ``==`` disregards the value of fold -- see below.)
|
|
|
|
|
2015-08-07 22:58:51 -04:00
|
|
|
In this case, ``dt.timestamp()`` will return the smaller of ``s0``
|
2015-09-21 13:33:12 -04:00
|
|
|
and ``s1`` values if ``dt.fold == 0`` and the larger otherwise.
|
2015-08-07 22:58:51 -04:00
|
|
|
|
|
|
|
|
|
|
|
For example, on a system set to US/Eastern timezone::
|
|
|
|
|
2015-08-26 22:24:22 -04:00
|
|
|
>>> datetime(2014, 11, 2, 1, 30, fold=0).timestamp()
|
2015-08-07 22:58:51 -04:00
|
|
|
1414906200.0
|
2015-08-26 22:24:22 -04:00
|
|
|
>>> datetime(2014, 11, 2, 1, 30, fold=1).timestamp()
|
2015-08-07 22:58:51 -04:00
|
|
|
1414909800.0
|
|
|
|
|
2015-08-09 20:33:18 -04:00
|
|
|
When a ``datetime.datetime`` instance ``dt`` represents a missing
|
2015-08-07 22:58:51 -04:00
|
|
|
time, there is no value ``s`` for which::
|
|
|
|
|
|
|
|
datetime.fromtimestamp(s) == dt
|
|
|
|
|
|
|
|
but we can form two "nice to know" values of ``s`` that differ
|
|
|
|
by the size of the gap in seconds. One is the value of ``s``
|
|
|
|
that would correspond to ``dt`` in a timezone where the UTC offset
|
|
|
|
is always the same as the offset right before the gap and the
|
|
|
|
other is the similar value but in a timezone the UTC offset
|
|
|
|
is always the same as the offset right after the gap.
|
|
|
|
|
2015-08-09 20:33:18 -04:00
|
|
|
The value returned by ``dt.timestamp()`` given a missing
|
2015-08-26 22:24:22 -04:00
|
|
|
``dt`` will be the greater of the two "nice to know" values
|
|
|
|
if ``dt.fold == 0`` and the smaller otherwise.
|
2015-09-21 16:21:26 -04:00
|
|
|
(This is not a typo -- it's intentionally backwards from the rule for
|
|
|
|
ambiguous times.)
|
2015-08-07 22:58:51 -04:00
|
|
|
|
|
|
|
For example, on a system set to US/Eastern timezone::
|
|
|
|
|
2015-08-26 22:24:22 -04:00
|
|
|
>>> datetime(2015, 3, 8, 2, 30, fold=0).timestamp()
|
2015-08-07 22:58:51 -04:00
|
|
|
1425799800.0
|
2015-08-26 22:24:22 -04:00
|
|
|
>>> datetime(2015, 3, 8, 2, 30, fold=1).timestamp()
|
2015-08-07 22:58:51 -04:00
|
|
|
1425796200.0
|
|
|
|
|
|
|
|
|
2015-08-30 22:01:40 -04:00
|
|
|
Aware datetime instances
|
|
|
|
........................
|
|
|
|
|
|
|
|
Users of pre-PEP implementations of ``tzinfo`` will not see any
|
|
|
|
changes in the behavior of their aware datetime instances. Two such
|
|
|
|
instances that differ only by the value of the ``fold`` attribute will
|
|
|
|
not be distinguishable by any means other than an explicit access to
|
2015-09-21 16:21:26 -04:00
|
|
|
the ``fold`` value. (This is because these pre-PEP implementations
|
|
|
|
are not using the ``fold`` attribute.)
|
2015-08-30 22:01:40 -04:00
|
|
|
|
2015-09-21 13:00:13 -04:00
|
|
|
On the other hand, if an object's ``tzinfo`` is set to a fold-aware
|
2015-09-21 16:21:26 -04:00
|
|
|
implementation, then in a fold or gap the value of ``fold`` will
|
|
|
|
affect the result of several methods:
|
|
|
|
``utcoffset()``, ``dst()``, ``tzname()``, ``astimezone()``,
|
|
|
|
``strftime()`` (if the "%Z" or "%z" directive is used in the format
|
2015-08-30 22:15:10 -04:00
|
|
|
specification), ``isoformat()``, and ``timetuple()``.
|
2015-08-30 22:01:40 -04:00
|
|
|
|
|
|
|
|
2015-08-09 19:51:45 -04:00
|
|
|
Combining and splitting date and time
|
|
|
|
.....................................
|
|
|
|
|
2015-08-09 20:33:18 -04:00
|
|
|
The ``datetime.datetime.combine()`` method will copy the value of the
|
2015-08-26 22:24:22 -04:00
|
|
|
``fold`` attribute to the resulting ``datetime.datetime`` instance.
|
2015-08-09 19:51:45 -04:00
|
|
|
|
2015-08-09 20:33:18 -04:00
|
|
|
The ``datetime.datetime.time()`` method will copy the value of the
|
2015-08-26 22:24:22 -04:00
|
|
|
``fold`` attribute to the resulting ``datetime.time`` instance.
|
2015-08-09 19:51:45 -04:00
|
|
|
|
|
|
|
|
2015-08-21 22:28:19 -04:00
|
|
|
Pickles
|
|
|
|
.......
|
|
|
|
|
2015-09-20 23:17:33 -04:00
|
|
|
The value of the fold attribute will only be saved in pickles created
|
|
|
|
with protocol version 4 (introduced in Python 3.4) or greater.
|
|
|
|
|
2015-08-21 22:28:19 -04:00
|
|
|
Pickle sizes for the ``datetime.datetime`` and ``datetime.time``
|
2015-08-26 22:24:22 -04:00
|
|
|
objects will not change. The ``fold`` value will be encoded in the
|
2015-09-21 16:21:26 -04:00
|
|
|
first bit of the 3rd byte of the ``datetime.datetime``
|
|
|
|
pickle payload; and in the first bit of the 1st byte of the
|
|
|
|
``datetime.time`` payload. In the `current implementation`_
|
2015-09-20 23:52:23 -04:00
|
|
|
these bytes are used to store the month (1-12) and hour (0-23) values
|
2015-09-20 23:17:33 -04:00
|
|
|
and the first bit is always 0. We picked these bytes because they are
|
|
|
|
the only bytes that are checked by the current unpickle code. Thus
|
|
|
|
loading post-PEP ``fold=1`` pickles in a pre-PEP Python will result in
|
|
|
|
an exception rather than an instance with out of range components.
|
2015-08-21 22:28:19 -04:00
|
|
|
|
2015-09-20 23:23:49 -04:00
|
|
|
.. _current implementation: https://hg.python.org/cpython/file/v3.5.0/Include/datetime.h#l10
|
2015-08-21 22:28:19 -04:00
|
|
|
|
2015-08-26 22:24:22 -04:00
|
|
|
|
|
|
|
Implementations of tzinfo in the Standard Library
|
|
|
|
=================================================
|
2015-08-07 22:58:51 -04:00
|
|
|
|
|
|
|
No new implementations of ``datetime.tzinfo`` abstract class are
|
2015-08-21 22:28:19 -04:00
|
|
|
proposed in this PEP. The existing (fixed offset) timezones do
|
2015-08-07 22:58:51 -04:00
|
|
|
not introduce ambiguous local times and their ``utcoffset()``
|
|
|
|
implementation will return the same constant value as they do now
|
2015-09-21 13:00:13 -04:00
|
|
|
regardless of the value of ``fold``.
|
2015-08-07 22:58:51 -04:00
|
|
|
|
2015-08-09 19:51:45 -04:00
|
|
|
The basic implementation of ``fromutc()`` in the abstract
|
2015-09-21 13:00:13 -04:00
|
|
|
``datetime.tzinfo`` class will not change. It is currently not used
|
|
|
|
anywhere in the stdlib because the only included ``tzinfo``
|
|
|
|
implementation (the ``datetime.timezone`` class implementing fixed
|
2015-09-21 17:52:10 -04:00
|
|
|
offset timezones) overrides ``fromutc()``. Keeping the default
|
2015-09-21 16:21:26 -04:00
|
|
|
implementation unchanged has the benefit that pre-PEP 3rd party
|
|
|
|
implementations that inherit the default ``fromutc()`` are not
|
|
|
|
accidentally affected.
|
|
|
|
|
2015-08-07 22:58:51 -04:00
|
|
|
|
2015-08-21 22:28:19 -04:00
|
|
|
Guidelines for New tzinfo Implementations
|
|
|
|
=========================================
|
2015-08-07 22:58:51 -04:00
|
|
|
|
|
|
|
Implementors of concrete ``datetime.tzinfo`` subclasses who want to
|
2015-08-21 22:28:19 -04:00
|
|
|
support variable UTC offsets (due to DST and other causes) should follow
|
2015-08-07 22:58:51 -04:00
|
|
|
these guidelines.
|
|
|
|
|
2015-08-21 22:28:19 -04:00
|
|
|
|
|
|
|
Ignorance is Bliss
|
|
|
|
------------------
|
|
|
|
|
|
|
|
New implementations of ``utcoffset()``, ``tzname()`` and ``dst()``
|
2015-08-26 22:24:22 -04:00
|
|
|
methods should ignore the value of ``fold`` unless they are called on
|
2015-08-21 22:28:19 -04:00
|
|
|
the ambiguous or missing times.
|
|
|
|
|
|
|
|
|
2015-09-20 22:42:43 -04:00
|
|
|
In the Fold
|
|
|
|
-----------
|
2015-08-21 22:28:19 -04:00
|
|
|
|
|
|
|
New subclasses should override the base-class ``fromutc()`` method and
|
2015-09-21 17:52:10 -04:00
|
|
|
implement it so that in all cases where two different UTC times ``u0`` and
|
2015-09-20 22:42:43 -04:00
|
|
|
``u1`` (``u0`` <``u1``) correspond to the same local time ``t``,
|
|
|
|
``fromutc(u0)`` will return an instance with ``fold=0`` and
|
|
|
|
``fromutc(u1)`` will return an instance with ``fold=1``. In all
|
2015-08-26 22:24:22 -04:00
|
|
|
other cases the returned instance should have ``fold=0``.
|
2015-08-07 22:58:51 -04:00
|
|
|
|
2015-09-20 22:42:43 -04:00
|
|
|
The ``utcoffset()``, ``tzname()`` and ``dst()`` methods should use the
|
|
|
|
value of the fold attribute to determine whether an otherwise
|
|
|
|
ambiguous time ``t`` corresponds to the time before or after the
|
|
|
|
transition. By definition, ``utcoffset()`` is greater before and
|
|
|
|
smaller after any transition that creates a fold. The values returned
|
|
|
|
by ``tzname()`` and ``dst()`` may or may not depend on the value of
|
2015-09-21 17:52:10 -04:00
|
|
|
the ``fold`` attribute depending on the kind of the transition.
|
2015-09-20 22:42:43 -04:00
|
|
|
|
2022-04-23 09:03:16 -04:00
|
|
|
.. image:: pep-0495-fold.svg
|
2015-09-20 20:25:12 -04:00
|
|
|
:align: center
|
|
|
|
:width: 60%
|
2022-03-11 15:41:57 -05:00
|
|
|
:class: invert-in-dark-mode
|
2015-09-20 20:25:12 -04:00
|
|
|
|
2015-09-20 22:42:43 -04:00
|
|
|
The sketch above illustrates the relationship between the UTC and
|
|
|
|
local time around a fall-back transition. The zig-zag line is a graph
|
|
|
|
of the function implemented by ``fromutc()``. Two intervals on the
|
|
|
|
UTC axis adjacent to the transition point and having the size of the
|
|
|
|
time shift at the transition are mapped to the same interval on the
|
|
|
|
local axis. New implementations of ``fromutc()`` method should set
|
|
|
|
the fold attribute to 1 when ``self`` is in the region marked in
|
|
|
|
yellow on the UTC axis. (All intervals should be treated as closed on
|
|
|
|
the left and open on the right.)
|
2015-08-07 22:58:51 -04:00
|
|
|
|
|
|
|
|
2015-09-20 22:42:43 -04:00
|
|
|
Mind the Gap
|
|
|
|
------------
|
2015-08-21 22:28:19 -04:00
|
|
|
|
2015-09-20 22:42:43 -04:00
|
|
|
The ``fromutc()`` method should never produce a time in the gap.
|
2015-08-21 22:28:19 -04:00
|
|
|
|
2015-09-21 16:21:26 -04:00
|
|
|
If the ``utcoffset()``, ``tzname()`` or ``dst()`` method is called on a
|
2015-09-20 22:42:43 -04:00
|
|
|
local time that falls in a gap, the rules in effect before the
|
|
|
|
transition should be used if ``fold=0``. Otherwise, the rules in
|
|
|
|
effect after the transition should be used.
|
2015-08-07 22:58:51 -04:00
|
|
|
|
2022-04-23 09:03:16 -04:00
|
|
|
.. image:: pep-0495-gap.svg
|
2015-09-20 20:25:12 -04:00
|
|
|
:align: center
|
|
|
|
:width: 60%
|
2022-03-11 15:41:57 -05:00
|
|
|
:class: invert-in-dark-mode
|
2015-09-20 20:25:12 -04:00
|
|
|
|
2015-09-20 22:42:43 -04:00
|
|
|
The sketch above illustrates the relationship between the UTC and
|
|
|
|
local time around a spring-forward transition. At the transition, the
|
|
|
|
local clock is advanced skipping the times in the gap. For the
|
|
|
|
purposes of determining the values of ``utcoffset()``, ``tzname()``
|
|
|
|
and ``dst()``, the line before the transition is extended forward to
|
|
|
|
find the UTC time corresponding to the time in the gap with ``fold=0``
|
|
|
|
and for instances with ``fold=1``, the line after the transition is
|
|
|
|
extended back.
|
|
|
|
|
|
|
|
Summary of Rules at a Transition
|
|
|
|
--------------------------------
|
|
|
|
|
|
|
|
On ambiguous/missing times ``utcoffset()`` should return values
|
|
|
|
according to the following table:
|
|
|
|
|
|
|
|
+-----------------+----------------+-----------------------------+
|
|
|
|
| | fold=0 | fold=1 |
|
|
|
|
+=================+================+=============================+
|
|
|
|
| Fold | oldoff | newoff = oldoff - delta |
|
|
|
|
+-----------------+----------------+-----------------------------+
|
|
|
|
| Gap | oldoff | newoff = oldoff + delta |
|
|
|
|
+-----------------+----------------+-----------------------------+
|
|
|
|
|
|
|
|
where ``oldoff`` (``newoff``) is the UTC offset before (after) the
|
|
|
|
transition and ``delta`` is the absolute size of the fold or the gap.
|
|
|
|
|
|
|
|
Note that the interpretation of the fold attribute is consistent in
|
|
|
|
the fold and gap cases. In both cases, ``fold=0`` (``fold=1``) means
|
|
|
|
use ``fromutc()`` line before (after) the transition to find the UTC
|
|
|
|
time. Only in the "Fold" case, the UTC times ``u0`` and ``u1`` are
|
|
|
|
"real" solutions for the equation ``fromutc(u) == t``, while in the
|
|
|
|
"Gap" case they are "imaginary" solutions.
|
|
|
|
|
|
|
|
|
|
|
|
The DST Transitions
|
|
|
|
-------------------
|
|
|
|
|
2015-08-09 20:33:18 -04:00
|
|
|
On a missing time introduced at the start of DST, the values returned
|
2017-03-23 18:57:19 -04:00
|
|
|
by ``utcoffset()`` and ``dst()`` methods should be as follows
|
2015-08-07 22:58:51 -04:00
|
|
|
|
|
|
|
+-----------------+----------------+------------------+
|
2015-08-26 22:24:22 -04:00
|
|
|
| | fold=0 | fold=1 |
|
2015-08-09 20:02:58 -04:00
|
|
|
+=================+================+==================+
|
2015-08-21 22:28:19 -04:00
|
|
|
| utcoffset() | stdoff | stdoff + dstoff |
|
2015-08-07 22:58:51 -04:00
|
|
|
+-----------------+----------------+------------------+
|
2015-08-21 22:28:19 -04:00
|
|
|
| dst() | zero | dstoff |
|
2015-08-07 22:58:51 -04:00
|
|
|
+-----------------+----------------+------------------+
|
|
|
|
|
|
|
|
|
2015-09-20 22:42:43 -04:00
|
|
|
On an ambiguous time introduced at the end of DST, the values returned
|
|
|
|
by ``utcoffset()`` and ``dst()`` methods should be as follows
|
2015-08-07 22:58:51 -04:00
|
|
|
|
2015-09-20 22:42:43 -04:00
|
|
|
+-----------------+----------------+------------------+
|
|
|
|
| | fold=0 | fold=1 |
|
|
|
|
+=================+================+==================+
|
|
|
|
| utcoffset() | stdoff + dstoff| stdoff |
|
|
|
|
+-----------------+----------------+------------------+
|
|
|
|
| dst() | dstoff | zero |
|
|
|
|
+-----------------+----------------+------------------+
|
2015-08-02 16:58:27 -04:00
|
|
|
|
2015-09-20 22:42:43 -04:00
|
|
|
where ``stdoff`` is the standard (non-DST) offset, ``dstoff`` is the
|
|
|
|
DST correction (typically ``dstoff = timedelta(hours=1)``) and ``zero
|
|
|
|
= timedelta(0)``.
|
2015-08-02 16:58:27 -04:00
|
|
|
|
|
|
|
|
2015-09-12 23:23:39 -04:00
|
|
|
Temporal Arithmetic and Comparison Operators
|
|
|
|
============================================
|
2015-08-02 16:58:27 -04:00
|
|
|
|
2015-09-15 16:11:37 -04:00
|
|
|
.. epigraph::
|
|
|
|
|
|
|
|
| In *mathematicks* he was greater
|
|
|
|
| Than Tycho Brahe, or Erra Pater:
|
|
|
|
| For he, by geometric scale,
|
|
|
|
| Could take the size of pots of ale;
|
|
|
|
| Resolve, by sines and tangents straight,
|
|
|
|
| If bread or butter wanted weight,
|
|
|
|
| And wisely tell what hour o' th' day
|
|
|
|
| The clock does strike by algebra.
|
|
|
|
|
|
|
|
-- "Hudibras" by Samuel Butler
|
|
|
|
|
2015-09-12 23:23:39 -04:00
|
|
|
The value of the ``fold`` attribute will be ignored in all operations
|
|
|
|
with naive datetime instances. As a consequence, naive
|
2015-08-15 23:18:58 -04:00
|
|
|
``datetime.datetime`` or ``datetime.time`` instances that differ only
|
2015-08-26 22:24:22 -04:00
|
|
|
by the value of ``fold`` will compare as equal. Applications that
|
2015-08-09 19:51:45 -04:00
|
|
|
need to differentiate between such instances should check the value of
|
2015-09-12 23:23:39 -04:00
|
|
|
``fold`` explicitly or convert those instances to a timezone that does
|
2015-09-21 16:21:26 -04:00
|
|
|
not have ambiguous times (such as UTC).
|
2015-08-02 16:58:27 -04:00
|
|
|
|
2015-09-12 23:23:39 -04:00
|
|
|
The value of ``fold`` will also be ignored whenever a timedelta is
|
|
|
|
added to or subtracted from a datetime instance which may be either
|
|
|
|
aware or naive. The result of addition (subtraction) of a timedelta
|
|
|
|
to (from) a datetime will always have ``fold`` set to 0 even if the
|
2015-08-26 22:24:22 -04:00
|
|
|
original datetime instance had ``fold=1``.
|
2015-08-02 16:58:27 -04:00
|
|
|
|
2015-09-12 23:23:39 -04:00
|
|
|
No changes are proposed to the way the difference ``t - s`` is
|
|
|
|
computed for datetime instances ``t`` and ``s``. If both instances
|
|
|
|
are naive or ``t.tzinfo`` is the same instance as ``s.tzinfo``
|
|
|
|
(``t.tzinfo is s.tzinfo`` evaluates to ``True``) then ``t - s`` is a
|
|
|
|
timedelta ``d`` such that ``s + d == t``. As explained in the
|
|
|
|
previous paragraph, timedelta addition ignores both ``fold`` and
|
|
|
|
``tzinfo`` attributes and so does intra-zone or naive datetime
|
|
|
|
subtraction.
|
|
|
|
|
|
|
|
Naive and intra-zone comparisons will ignore the value of ``fold`` and
|
2015-09-21 16:21:26 -04:00
|
|
|
return the same results as they do now. (This is the only way to
|
|
|
|
preserve backward compatibility. If you need an aware intra-zone
|
|
|
|
comparison that uses the fold, convert both sides to UTC first.)
|
2015-09-12 23:23:39 -04:00
|
|
|
|
|
|
|
The inter-zone subtraction will be defined as it is now: ``t - s`` is
|
|
|
|
computed as ``(t - t.utcoffset()) - (s -
|
2015-09-12 23:54:36 -04:00
|
|
|
s.utcoffset()).replace(tzinfo=t.tzinfo)``, but the result will
|
2015-09-12 23:23:39 -04:00
|
|
|
depend on the values of ``t.fold`` and ``s.fold`` when either
|
|
|
|
``t.tzinfo`` or ``s.tzinfo`` is post-PEP. [#]_
|
|
|
|
|
|
|
|
.. [#] Note that the new rules may result in a paradoxical situation
|
2015-09-12 23:47:36 -04:00
|
|
|
when ``s == t`` but ``s - u != t - u``. Such paradoxes are
|
2015-09-12 23:23:39 -04:00
|
|
|
not really new and are inherent in the overloading of the minus
|
2015-09-21 16:21:26 -04:00
|
|
|
operator differently for intra- and inter-zone operations. For
|
2015-09-12 23:23:39 -04:00
|
|
|
example, one can easily construct datetime instances ``t`` and ``s``
|
|
|
|
with some variable offset ``tzinfo`` and a datetime ``u`` with
|
|
|
|
``tzinfo=timezone.utc`` such that ``(t - u) - (s - u) != t - s``.
|
|
|
|
The explanation for this paradox is that the minuses inside the
|
|
|
|
parentheses and the two other minuses are really three different
|
2015-09-21 16:21:26 -04:00
|
|
|
operations: inter-zone datetime subtraction, timedelta subtraction,
|
|
|
|
and intra-zone datetime subtraction, which each have the mathematical
|
2015-09-12 23:23:39 -04:00
|
|
|
properties of subtraction separately, but not when combined in a
|
|
|
|
single expression.
|
|
|
|
|
|
|
|
|
|
|
|
Aware datetime Equality Comparison
|
|
|
|
----------------------------------
|
|
|
|
|
|
|
|
The aware datetime comparison operators will work the same as they do
|
2015-09-21 16:21:26 -04:00
|
|
|
now, with results indirectly affected by the value of ``fold`` whenever
|
|
|
|
the ``utcoffset()`` value of one of the operands depends on it, with one
|
|
|
|
exception. Whenever one or both of the operands in inter-zone comparison is
|
2015-09-12 23:23:39 -04:00
|
|
|
such that its ``utcoffset()`` depends on the value of its ``fold``
|
|
|
|
fold attribute, the result is ``False``. [#]_
|
|
|
|
|
|
|
|
.. [#] This exception is designed to preserve the hash and equivalence
|
|
|
|
invariants in the face of paradoxes of inter-zone arithmetic.
|
|
|
|
|
|
|
|
Formally, ``t == s`` when ``t.tzinfo is s.tzinfo`` evaluates to
|
|
|
|
``False`` can be defined as follows. Let ``toutc(t, fold)`` be a
|
|
|
|
function that takes an aware datetime instance ``t`` and returns a
|
|
|
|
naive instance representing the same time in UTC assuming a given
|
|
|
|
value of ``fold``:
|
|
|
|
|
|
|
|
.. code::
|
|
|
|
|
|
|
|
def toutc(t, fold):
|
|
|
|
u = t - t.replace(fold=fold).utcoffset()
|
2015-09-21 16:21:26 -04:00
|
|
|
return u.replace(tzinfo=None)
|
2015-09-12 23:23:39 -04:00
|
|
|
|
|
|
|
Then ``t == s`` is equivalent to
|
|
|
|
|
|
|
|
.. code::
|
|
|
|
|
|
|
|
toutc(t, fold=0) == toutc(t, fold=1) == toutc(s, fold=0) == toutc(s, fold=1)
|
2015-08-02 16:58:27 -04:00
|
|
|
|
|
|
|
|
|
|
|
Backward and Forward Compatibility
|
2015-08-21 22:28:19 -04:00
|
|
|
==================================
|
2015-08-02 16:58:27 -04:00
|
|
|
|
2015-08-09 20:58:00 -04:00
|
|
|
This proposal will have little effect on the programs that do not read
|
2015-08-26 22:24:22 -04:00
|
|
|
the ``fold`` flag explicitly or use tzinfo implementations that do.
|
2015-08-09 20:58:00 -04:00
|
|
|
The only visible change for such programs will be that conversions to
|
|
|
|
and from POSIX timestamps will now round-trip correctly (up to
|
2015-08-22 17:24:15 -04:00
|
|
|
floating point rounding). Programs that implemented a work-around to
|
|
|
|
the old incorrect behavior may need to be modified.
|
2015-08-09 20:58:00 -04:00
|
|
|
|
2015-08-02 16:58:27 -04:00
|
|
|
Pickles produced by older programs will remain fully forward
|
2015-08-26 22:24:22 -04:00
|
|
|
compatible. Only datetime/time instances with ``fold=1`` pickled
|
2015-08-02 16:58:27 -04:00
|
|
|
in the new versions will become unreadable by the older Python
|
2015-08-26 22:24:22 -04:00
|
|
|
versions. Pickles of instances with ``fold=0`` (which is the
|
2015-08-02 16:58:27 -04:00
|
|
|
default) will remain unchanged.
|
|
|
|
|
2015-08-21 22:28:19 -04:00
|
|
|
|
2015-08-02 16:58:27 -04:00
|
|
|
Questions and Answers
|
|
|
|
=====================
|
|
|
|
|
2015-08-21 22:28:19 -04:00
|
|
|
Why not call the new flag "isdst"?
|
|
|
|
----------------------------------
|
2015-08-02 16:58:27 -04:00
|
|
|
|
2015-08-21 22:28:19 -04:00
|
|
|
A non-technical answer
|
|
|
|
......................
|
2015-08-02 16:58:27 -04:00
|
|
|
|
2015-08-21 22:28:19 -04:00
|
|
|
* Alice: Bob - let's have a stargazing party at 01:30 AM tomorrow!
|
|
|
|
* Bob: Should I presume initially that Daylight Saving Time is or is
|
|
|
|
not in effect for the specified time?
|
2015-08-02 16:58:27 -04:00
|
|
|
* Alice: Huh?
|
|
|
|
|
|
|
|
-------
|
|
|
|
|
|
|
|
* Bob: Alice - let's have a stargazing party at 01:30 AM tomorrow!
|
|
|
|
* Alice: You know, Bob, 01:30 AM will happen twice tomorrow. Which time do you have in mind?
|
|
|
|
* Bob: I did not think about it, but let's pick the first.
|
|
|
|
|
2015-08-21 22:28:19 -04:00
|
|
|
-------
|
2015-08-02 16:58:27 -04:00
|
|
|
|
2015-08-26 22:24:22 -04:00
|
|
|
(same characters, an hour later)
|
|
|
|
|
|
|
|
-------
|
|
|
|
|
|
|
|
* Bob: Alice - this Py-O-Clock gadget of mine asks me to choose
|
|
|
|
between fold=0 and fold=1 when I set it for tomorrow 01:30 AM.
|
|
|
|
What should I do?
|
|
|
|
* Alice: I've never hear of a Py-O-Clock, but I guess fold=0 is
|
2015-09-21 13:00:13 -04:00
|
|
|
the first 01:30 AM and fold=1 is the second.
|
2015-08-26 22:24:22 -04:00
|
|
|
|
|
|
|
|
2015-08-21 22:28:19 -04:00
|
|
|
A technical reason
|
|
|
|
..................
|
|
|
|
|
|
|
|
While the ``tm_isdst`` field of the ``time.struct_time`` object can be
|
|
|
|
used to disambiguate local times in the fold, the semantics of such
|
|
|
|
disambiguation are completely different from the proposal in this PEP.
|
|
|
|
|
|
|
|
The main problem with the ``tm_isdst`` field is that it is impossible
|
|
|
|
to know what value is appropriate for ``tm_isdst`` without knowing the
|
|
|
|
details about the time zone that are only available to the ``tzinfo``
|
|
|
|
implementation. Thus while ``tm_isdst`` is useful in the *output* of
|
2015-08-22 17:24:15 -04:00
|
|
|
methods such as ``time.localtime``, it is cumbersome as an *input* of
|
2015-08-21 22:28:19 -04:00
|
|
|
methods such as ``time.mktime``.
|
|
|
|
|
2015-08-22 17:24:15 -04:00
|
|
|
If the programmer misspecified a non-negative value of ``tm_isdst`` to
|
2015-08-21 22:28:19 -04:00
|
|
|
``time.mktime``, the result will be time that is 1 hour off and since
|
|
|
|
there is rarely a way to know anything about DST *before* a call to
|
|
|
|
``time.mktime`` is made, the only sane choice is usually
|
|
|
|
``tm_isdst=-1``.
|
|
|
|
|
2015-08-26 22:24:22 -04:00
|
|
|
Unlike ``tm_isdst``, the proposed ``fold`` attribute has no effect on
|
|
|
|
the interpretation of the datetime instance unless without that
|
|
|
|
attribute two (or no) interpretations are possible.
|
2015-08-21 22:28:19 -04:00
|
|
|
|
|
|
|
Since it would be very confusing to have something called ``isdst``
|
|
|
|
that does not have the same semantics as ``tm_isdst``, we need a
|
|
|
|
different name. Moreover, the ``datetime.datetime`` class already has
|
2015-08-26 22:24:22 -04:00
|
|
|
a method called ``dst()`` and if we called ``fold`` "isdst", we would
|
|
|
|
necessarily have situations when "isdst" is zero but ``dst()`` is not
|
|
|
|
or the other way around.
|
2015-08-21 22:28:19 -04:00
|
|
|
|
2015-08-26 22:24:22 -04:00
|
|
|
Why "fold"?
|
|
|
|
-----------
|
2015-08-21 22:28:19 -04:00
|
|
|
|
2015-08-26 22:24:22 -04:00
|
|
|
Suggested by Guido van Rossum and favored by one (but initially
|
|
|
|
disfavored by another) author. A consensus was reached after the
|
|
|
|
allowed values for the attribute were changed from False/True to 0/1.
|
|
|
|
The noun "fold" has correct connotations and easy mnemonic rules, but
|
|
|
|
at the same time does not invite unbased assumptions.
|
|
|
|
|
|
|
|
|
|
|
|
What is "first"?
|
|
|
|
----------------
|
2015-08-21 22:28:19 -04:00
|
|
|
|
2015-08-26 22:24:22 -04:00
|
|
|
This was a working name of the attribute chosen initially because the
|
|
|
|
obvious alternative ("second") conflicts with the existing attribute.
|
|
|
|
It was rejected mostly on the grounds that it would make True a
|
|
|
|
default value.
|
2015-08-21 22:28:19 -04:00
|
|
|
|
2015-08-26 22:24:22 -04:00
|
|
|
The following alternative names have also been considered:
|
2015-08-21 22:28:19 -04:00
|
|
|
|
2016-05-03 04:35:10 -04:00
|
|
|
**later**
|
|
|
|
A close contender to "fold". One author dislikes it because
|
|
|
|
it is confusable with equally fitting "latter," but in the age
|
|
|
|
of auto-completion everywhere this is a small consideration. A
|
|
|
|
stronger objection may be that in the case of missing time, we
|
|
|
|
will have ``later=True`` instance converted to an earlier time by
|
|
|
|
``.astimezone(timezone.utc)`` that that with ``later=False``.
|
|
|
|
Yet again, this can be interpreted as a desirable indication that
|
|
|
|
the original time is invalid.
|
|
|
|
|
|
|
|
**which**
|
2023-09-01 15:32:22 -04:00
|
|
|
The `original`_ placeholder name for the ``localtime`` function
|
2016-05-03 04:35:10 -04:00
|
|
|
branch index was `independently proposed`_ for the name of the
|
|
|
|
disambiguation attribute and received `some support`_.
|
|
|
|
|
|
|
|
**repeated**
|
|
|
|
Did not receive any support on the mailing list.
|
|
|
|
|
|
|
|
**ltdf**
|
|
|
|
(Local Time Disambiguation Flag) - short and no-one will attempt
|
|
|
|
to guess what it means without reading the docs. (This abbreviation
|
|
|
|
was used in PEP discussions with the meaning ``ltdf=False`` is the
|
|
|
|
earlier by those who didn't want to endorse any of the alternatives.)
|
2015-08-21 22:28:19 -04:00
|
|
|
|
2015-08-26 10:37:02 -04:00
|
|
|
.. _original: https://mail.python.org/pipermail/python-dev/2015-April/139099.html
|
|
|
|
.. _independently proposed: https://mail.python.org/pipermail/datetime-sig/2015-August/000479.html
|
|
|
|
.. _some support: https://mail.python.org/pipermail/datetime-sig/2015-August/000483.html
|
2015-08-02 16:58:27 -04:00
|
|
|
|
2015-08-22 17:24:15 -04:00
|
|
|
Are two values enough?
|
2015-08-22 21:22:28 -04:00
|
|
|
----------------------
|
|
|
|
|
|
|
|
Several reasons have been raised to allow a ``None`` or -1 value for
|
2015-08-26 22:24:22 -04:00
|
|
|
the ``fold`` attribute: backward compatibility, analogy with ``tm_isdst``
|
2015-08-22 21:22:28 -04:00
|
|
|
and strict checking for invalid times.
|
|
|
|
|
|
|
|
|
|
|
|
Backward Compatibility
|
|
|
|
......................
|
|
|
|
|
2015-08-24 17:29:10 -04:00
|
|
|
It has been suggested that backward compatibility can be improved if
|
2015-08-26 22:24:22 -04:00
|
|
|
the default value of the ``fold`` flag was ``None`` which would
|
2015-08-24 17:29:10 -04:00
|
|
|
signal that pre-PEP behavior is requested. Based on the analysis
|
2015-08-26 22:24:22 -04:00
|
|
|
below, we believe that the proposed changes with the ``fold=0``
|
2015-08-24 17:29:10 -04:00
|
|
|
default are sufficiently backward compatible.
|
|
|
|
|
2015-08-22 21:22:28 -04:00
|
|
|
This PEP provides only three ways for a program to discover that two
|
|
|
|
otherwise identical datetime instances have different values of
|
2015-08-26 22:24:22 -04:00
|
|
|
``fold``: (1) an explicit check of the ``fold`` attribute; (2) if
|
2015-08-22 21:22:28 -04:00
|
|
|
the instances are naive - conversion to another timezone using the
|
|
|
|
``astimezone()`` method; and (3) conversion to ``float`` using the
|
|
|
|
``timestamp()`` method.
|
|
|
|
|
2015-08-26 22:24:22 -04:00
|
|
|
Since ``fold`` is a new attribute, the first option is not available
|
2015-08-22 21:22:28 -04:00
|
|
|
to the existing programs. Note that option (2) only works for naive
|
|
|
|
datetimes that happen to be in a fold or a gap in the system time
|
2015-08-26 22:24:22 -04:00
|
|
|
zone. In all other cases, the value of ``fold`` will be ignored in
|
|
|
|
the conversion unless the instances use a ``fold``-aware ``tzinfo``
|
2015-08-22 21:22:28 -04:00
|
|
|
which would not be available in a pre-PEP program. Similarly, the
|
|
|
|
``astimezone()`` called on a naive instance will not be available in
|
|
|
|
such program because ``astimezone()`` does not currently work with
|
|
|
|
naive datetimes.
|
|
|
|
|
|
|
|
This leaves us with only one situation where an existing program can
|
2015-09-24 19:23:49 -04:00
|
|
|
start producing different results after the implementation of this PEP:
|
2015-08-22 21:22:28 -04:00
|
|
|
when a ``datetime.timestamp()`` method is called on a naive datetime
|
|
|
|
instance that happen to be in the fold or the gap. In the current
|
|
|
|
implementation, the result is undefined. Depending on the system
|
|
|
|
``mktime`` implementation, the programs can see different results or
|
|
|
|
errors in those cases. With this PEP in place, the value of timestamp
|
|
|
|
will be well-defined in those cases but will depend on the value of
|
2015-08-26 22:24:22 -04:00
|
|
|
the ``fold`` flag. We consider the change in
|
2015-08-22 21:22:28 -04:00
|
|
|
``datetime.timestamp()`` method behavior a bug fix enabled by this
|
|
|
|
PEP. The old behavior can still be emulated by the users who depend
|
|
|
|
on it by writing ``time.mktime(dt.timetuple()) + 1e-6*dt.microsecond``
|
|
|
|
instead of ``dt.timestamp()``.
|
|
|
|
|
|
|
|
|
|
|
|
Analogy with tm_isdst
|
|
|
|
.....................
|
2015-08-22 17:24:15 -04:00
|
|
|
|
|
|
|
The ``time.mktime`` interface allows three values for the ``tm_isdst``
|
|
|
|
flag: -1, 0, and 1. As we explained above, -1 (asking ``mktime`` to
|
|
|
|
determine whether DST is in effect for the given time from the rest of
|
|
|
|
the fields) is the only choice that is useful in practice.
|
|
|
|
|
2015-08-26 22:24:22 -04:00
|
|
|
With the ``fold`` flag, however, ``datetime.timestamp()`` will return
|
2015-08-22 17:24:15 -04:00
|
|
|
the same value as ``mktime`` with ``tm_isdst=-1`` in 99.98% of the
|
|
|
|
time for most time zones with DST transitions. Moreover,
|
|
|
|
``tm_isdst=-1``-like behavior is specified *regardless* of the value
|
2015-08-26 22:24:22 -04:00
|
|
|
of ``fold``.
|
2015-08-22 17:24:15 -04:00
|
|
|
|
|
|
|
It is only in the 0.02% cases (2 hours per year) that the
|
|
|
|
``datetime.timestamp()`` and ``mktime`` with ``tm_isdst=-1`` may
|
|
|
|
disagree. However, even in this case, most of the ``mktime``
|
2015-08-26 22:24:22 -04:00
|
|
|
implementations will return the ``fold=0`` or the ``fold=1``
|
2015-08-22 17:24:15 -04:00
|
|
|
value even though relevant standards allow ``mktime`` to return -1 and
|
|
|
|
set an error code in those cases.
|
|
|
|
|
|
|
|
In other words, ``tm_isdst=-1`` behavior is not missing from this PEP.
|
|
|
|
To the contrary, it is the only behavior provided in two different
|
|
|
|
well-defined flavors. The behavior that is missing is when a given
|
|
|
|
local hour is interpreted as a different local hour because of the
|
|
|
|
misspecified ``tm_isdst``.
|
|
|
|
|
|
|
|
For example, in the DST-observing time zones in the Northern
|
|
|
|
hemisphere (where DST is in effect in June) one can get
|
|
|
|
|
|
|
|
.. code::
|
|
|
|
|
|
|
|
>>> from time import mktime, localtime
|
|
|
|
>>> t = mktime((2015, 6, 1, 12, 0, 0, -1, -1, 0))
|
|
|
|
>>> localtime(t)[:]
|
|
|
|
(2015, 6, 1, 13, 0, 0, 0, 152, 1)
|
|
|
|
|
|
|
|
Note that 12:00 was interpreted as 13:00 by ``mktime``. With the
|
|
|
|
``datetime.timestamp``, ``datetime.fromtimestamp``, it is currently
|
2015-09-21 13:00:13 -04:00
|
|
|
guaranteed that
|
2015-08-22 17:24:15 -04:00
|
|
|
|
|
|
|
.. code::
|
|
|
|
|
|
|
|
>>> t = datetime.datetime(2015, 6, 1, 12).timestamp()
|
|
|
|
>>> datetime.datetime.fromtimestamp(t)
|
2015-09-21 13:00:13 -04:00
|
|
|
datetime.datetime(2015, 6, 1, 12, 0)
|
2015-08-22 17:24:15 -04:00
|
|
|
|
2015-08-26 22:24:22 -04:00
|
|
|
This PEP extends the same guarantee to both values of ``fold``:
|
2015-08-22 17:24:15 -04:00
|
|
|
|
|
|
|
.. code::
|
|
|
|
|
2015-08-26 22:24:22 -04:00
|
|
|
>>> t = datetime.datetime(2015, 6, 1, 12, fold=0).timestamp()
|
2015-08-22 17:24:15 -04:00
|
|
|
>>> datetime.datetime.fromtimestamp(t)
|
2015-09-21 13:00:13 -04:00
|
|
|
datetime.datetime(2015, 6, 1, 12, 0)
|
2015-08-22 17:24:15 -04:00
|
|
|
|
|
|
|
.. code::
|
|
|
|
|
2015-08-26 22:24:22 -04:00
|
|
|
>>> t = datetime.datetime(2015, 6, 1, 12, fold=1).timestamp()
|
2015-08-22 17:24:15 -04:00
|
|
|
>>> datetime.datetime.fromtimestamp(t)
|
2015-09-21 13:00:13 -04:00
|
|
|
datetime.datetime(2015, 6, 1, 12, 0)
|
2015-08-22 17:24:15 -04:00
|
|
|
|
2015-08-26 22:24:22 -04:00
|
|
|
Thus one of the suggested uses for ``fold=-1`` -- to match the legacy
|
|
|
|
behavior -- is not needed. Either choice of ``fold`` will match the
|
2015-08-22 17:24:15 -04:00
|
|
|
old behavior except in the few cases where the old behavior was
|
|
|
|
undefined.
|
|
|
|
|
2015-08-22 21:22:28 -04:00
|
|
|
|
|
|
|
Strict Invalid Time Checking
|
|
|
|
............................
|
|
|
|
|
2015-08-26 22:24:22 -04:00
|
|
|
Another suggestion was to use ``fold=-1`` or ``fold=None`` to
|
2015-08-22 17:24:15 -04:00
|
|
|
indicate that the program truly has no means to deal with the folds
|
|
|
|
and gaps and ``dt.utcoffset()`` should raise an error whenever ``dt``
|
|
|
|
represents an ambiguous or missing local time.
|
|
|
|
|
|
|
|
The main problem with this proposal, is that ``dt.utcoffset()`` is
|
|
|
|
used internally in situations where raising an error is not an option:
|
|
|
|
for example, in dictionary lookups or list/set membership checks. So
|
|
|
|
strict gap/fold checking behavior would need to be controlled by a
|
|
|
|
separate flag, say ``dt.utcoffset(raise_on_gap=True,
|
|
|
|
raise_on_fold=False)``. However, this functionality can be easily
|
|
|
|
implemented in user code:
|
|
|
|
|
|
|
|
.. code::
|
|
|
|
|
|
|
|
def utcoffset(dt, raise_on_gap=True, raise_on_fold=False):
|
|
|
|
u = dt.utcoffset()
|
2015-08-26 22:24:22 -04:00
|
|
|
v = dt.replace(fold=not dt.fold).utcoffset()
|
2015-08-22 17:24:15 -04:00
|
|
|
if u == v:
|
|
|
|
return u
|
2015-08-26 22:24:22 -04:00
|
|
|
if (u < v) == dt.fold:
|
2015-08-22 17:24:15 -04:00
|
|
|
if raise_on_fold:
|
2017-03-23 18:57:19 -04:00
|
|
|
raise AmbiguousTimeError
|
2015-08-22 17:24:15 -04:00
|
|
|
else:
|
2017-03-23 18:57:19 -04:00
|
|
|
if raise_on_gap:
|
|
|
|
raise MissingTimeError
|
2015-08-22 17:24:15 -04:00
|
|
|
return u
|
|
|
|
|
|
|
|
Moreover, raising an error in the problem cases is only one of many
|
|
|
|
possible solutions. An interactive program can ask the user for
|
|
|
|
additional input, while a server process may log a warning and take an
|
|
|
|
appropriate default action. We cannot possibly provide functions for
|
|
|
|
all possible user requirements, but this PEP provides the means to
|
|
|
|
implement any desired behavior in a few lines of code.
|
|
|
|
|
|
|
|
|
2015-08-02 16:58:27 -04:00
|
|
|
Implementation
|
|
|
|
==============
|
|
|
|
|
2015-09-20 23:18:38 -04:00
|
|
|
* Github fork: https://github.com/abalkin/cpython/tree/issue24773-s3
|
2015-08-02 23:44:22 -04:00
|
|
|
* Tracker issue: http://bugs.python.org/issue24773
|
2015-08-02 16:58:27 -04:00
|
|
|
|
|
|
|
|
|
|
|
Copyright
|
|
|
|
=========
|
|
|
|
|
|
|
|
This document has been placed in the public domain.
|
|
|
|
|
|
|
|
|
|
|
|
Picture Credit
|
|
|
|
==============
|
|
|
|
|
|
|
|
This image is a work of a U.S. military or Department of Defense
|
|
|
|
employee, taken or made as part of that person's official duties. As a
|
|
|
|
work of the U.S. federal government, the image is in the public
|
|
|
|
domain.
|