PEP 495: Addressed several (not all yet) mailing list commments.
This commit is contained in:
parent
104dc4b901
commit
173e09ac8b
273
pep-0495.txt
273
pep-0495.txt
|
@ -21,15 +21,15 @@ between two moments in time for which local times are the same.
|
|||
.. sidebar:: US public service advertisement
|
||||
|
||||
.. image:: pep-0495-daylightsavings.png
|
||||
:align: right
|
||||
:width: 15%
|
||||
:align: center
|
||||
:width: 95%
|
||||
|
||||
|
||||
Rationale
|
||||
=========
|
||||
|
||||
In the most world locations there have been and will be times when
|
||||
local clocks are moved back. In those times, intervals are introduced
|
||||
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
|
||||
|
@ -37,14 +37,27 @@ particular moment in time. The proposed solution is to add a boolean
|
|||
flag to the ``datetime`` instances that will distinguish between the
|
||||
two ambiguous times.
|
||||
|
||||
.. [#] People who live in locations observing the Daylight Saving
|
||||
Time (DST) move their clocks back (usually one hour) every Fall.
|
||||
|
||||
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.
|
||||
|
||||
|
||||
Terminology
|
||||
===========
|
||||
|
||||
When clocks are moved back, we say that a *fold* is created in the
|
||||
fabric of time. When the clock 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*.
|
||||
When clocks are moved back, we say that a *fold* is created in time.
|
||||
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*.
|
||||
|
||||
|
||||
Proposal
|
||||
|
@ -55,7 +68,7 @@ The "first" flag
|
|||
|
||||
We propose adding a boolean member called ``first`` to the instances
|
||||
of ``datetime.time`` and ``datetime.datetime`` classes. This member
|
||||
should have the value True for all instances except those that
|
||||
should have the value ``True`` for all instances except those that
|
||||
represent the second (chronologically) moment in time in an ambiguous
|
||||
case. [#]_
|
||||
|
||||
|
@ -143,6 +156,18 @@ second, microsecond and first.
|
|||
Affected Behaviors
|
||||
------------------
|
||||
|
||||
What time is it?
|
||||
................
|
||||
|
||||
The ``datetime.now()`` method called with no arguments, will set
|
||||
``first=False`` when returning the second of the two ambiguous times
|
||||
in a fold. When called with a ``tzinfo`` argument, the value of the
|
||||
``first`` will be determined by the ``tzinfo.fromutc()``
|
||||
implementation. If an instance of the built-in ``datetime.timezone``
|
||||
is passed as ``tzinfo``, the returned datetime instance will always
|
||||
have ``first=True``.
|
||||
|
||||
|
||||
Conversion from naive to aware
|
||||
..............................
|
||||
|
||||
|
@ -159,16 +184,31 @@ For example, on a system set to US/Eastern timezone::
|
|||
>>> dt.replace(first=False).astimezone().strftime('%D %T %Z%z')
|
||||
'11/02/14 01:30:00 EST-0500'
|
||||
|
||||
|
||||
Conversion from POSIX seconds from EPOCH
|
||||
........................................
|
||||
|
||||
The ``fromtimestamp()`` static method of ``datetime.datetime`` will
|
||||
set the ``first`` attribute appropriately in the returned object.
|
||||
|
||||
For example, on a system set to US/Eastern timezone::
|
||||
|
||||
>>> datetime.fromtimestamp(1414906200)
|
||||
datetime.datetime(2014, 11, 2, 1, 30)
|
||||
>>> datetime.fromtimestamp(1414906200 + 3600)
|
||||
datetime.datetime(2014, 11, 2, 1, 30, first=False)
|
||||
|
||||
|
||||
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
|
||||
of their ``first`` attribute if and only if these instances represent an
|
||||
ambiguous or a non-existent time.
|
||||
ambiguous or a missing time.
|
||||
|
||||
When a ``datetime.datetime`` instance ``dt`` represents an ambiguous
|
||||
(repeated) time, there are two values ``s0`` and ``s1`` such that::
|
||||
time, there are two values ``s0`` and ``s1`` such that::
|
||||
|
||||
datetime.fromtimestamp(s0) == datetime.fromtimestamp(s1) == dt
|
||||
|
||||
|
@ -208,21 +248,6 @@ For example, on a system set to US/Eastern timezone::
|
|||
1425796200.0
|
||||
|
||||
|
||||
Conversion from POSIX seconds from EPOCH
|
||||
........................................
|
||||
|
||||
|
||||
The ``fromtimestamp()`` static method of ``datetime.datetime`` will
|
||||
set the ``first`` attribute appropriately in the returned object.
|
||||
|
||||
For example, on a system set to US/Eastern timezone::
|
||||
|
||||
>>> datetime.fromtimestamp(1414906200)
|
||||
datetime.datetime(2014, 11, 2, 1, 30)
|
||||
>>> datetime.fromtimestamp(1414906200 + 3600)
|
||||
datetime.datetime(2014, 11, 2, 1, 30, first=False)
|
||||
|
||||
|
||||
Combining and splitting date and time
|
||||
.....................................
|
||||
|
||||
|
@ -233,11 +258,27 @@ The ``datetime.datetime.time()`` method will copy the value of the
|
|||
``first`` attribute to the resulting ``datetime.time`` instance.
|
||||
|
||||
|
||||
Pickles
|
||||
.......
|
||||
|
||||
Pickle sizes for the ``datetime.datetime`` and ``datetime.time``
|
||||
objects will not change. The ``first`` flag will be encoded in the
|
||||
first bit of the 5th byte of the ``datetime.datetime`` pickle payload
|
||||
or the 2nd byte of the datetime.time. In the `current implementation`_
|
||||
these bytes are used to store minute value (0-59) and the first bit is
|
||||
always 0. Note that ``first=True`` will be encoded as 0 in the first
|
||||
bit and ``first=False`` as 1. (This change only affects pickle
|
||||
format. In the C implementation, the "first" member will get a full byte
|
||||
to store the actual boolean value.)
|
||||
|
||||
|
||||
.. _current implementation: https://hg.python.org/cpython/file/d3b20bff9c5d/Include/datetime.h#l17
|
||||
|
||||
Implementations of tzinfo in stdlib
|
||||
...................................
|
||||
===================================
|
||||
|
||||
No new implementations of ``datetime.tzinfo`` abstract class are
|
||||
introduced in this PEP. The existing (fixed offset) timezones do
|
||||
proposed in this PEP. The existing (fixed offset) timezones do
|
||||
not introduce ambiguous local times and their ``utcoffset()``
|
||||
implementation will return the same constant value as they do now
|
||||
regardless of the value of ``first``.
|
||||
|
@ -248,27 +289,32 @@ used anywhere in the stdlib because the only included ``tzinfo``
|
|||
implementation (the ``datetime.timzeone`` class implementing fixed
|
||||
offset timezones) override ``fromutc()``.
|
||||
|
||||
New guidelines will be published for implementing concrete timezones
|
||||
with variable UTC offset.
|
||||
|
||||
|
||||
Guidelines for new tzinfo implementations
|
||||
-----------------------------------------
|
||||
Guidelines for New tzinfo Implementations
|
||||
=========================================
|
||||
|
||||
Implementors of concrete ``datetime.tzinfo`` subclasses who want to
|
||||
support variable UTC offsets (due to DST and other causes) must follow
|
||||
support variable UTC offsets (due to DST and other causes) should follow
|
||||
these guidelines.
|
||||
|
||||
New subclasses must override the base-class ``fromutc()`` method and
|
||||
|
||||
Ignorance is Bliss
|
||||
------------------
|
||||
|
||||
New implementations of ``utcoffset()``, ``tzname()`` and ``dst()``
|
||||
methods should ignore the value of ``first`` unless they are called on
|
||||
the ambiguous or missing times.
|
||||
|
||||
|
||||
In the DST Fold
|
||||
---------------
|
||||
|
||||
New subclasses should override the base-class ``fromutc()`` method and
|
||||
implement it so that in all cases where two UTC times ``u1`` and
|
||||
``u2`` (``u1`` <``u2``) correspond to the same local time
|
||||
``fromutc(u1)`` will return an instance with ``first=True`` and
|
||||
``fromutc(u2)`` will return an instance with ``first=False``. In all
|
||||
other cases the returned instance must have ``first=True``.
|
||||
|
||||
New implementations of ``utcoffset()`` and ``dst()`` methods should
|
||||
ignore the value of ``first`` unless they are called on the ambiguous
|
||||
or missing times.
|
||||
other cases the returned instance should have ``first=True``.
|
||||
|
||||
On an ambiguous time introduced at the end of DST, the values returned
|
||||
by ``utcoffset()`` and ``dst()`` methods should be as follows
|
||||
|
@ -276,30 +322,37 @@ by ``utcoffset()`` and ``dst()`` methods should be as follows
|
|||
+-----------------+----------------+------------------+
|
||||
| | first=True | first=False |
|
||||
+=================+================+==================+
|
||||
| utcoff() | stdoff + hour | stdoff |
|
||||
| utcoffset() | stdoff + dstoff| stdoff |
|
||||
+-----------------+----------------+------------------+
|
||||
| dst() | hour | zero |
|
||||
| dst() | dstoff | zero |
|
||||
+-----------------+----------------+------------------+
|
||||
|
||||
where ``stdoff`` is the standard (non-DST) offset,
|
||||
``hour = timedelta(hours=1)`` and ``zero = timedelta(0)``.
|
||||
where ``stdoff`` is the standard (non-DST) offset, ``dstoff`` is the
|
||||
DST correction (typically ``dstoff = timedelta(hours=1)``) and ``zero
|
||||
= timedelta(0)``.
|
||||
|
||||
|
||||
Mind the DST Gap
|
||||
----------------
|
||||
|
||||
On a missing time introduced at the start of DST, the values returned
|
||||
by ``utcoffset()`` and ``dst()`` methods should be as follows
|
||||
|
||||
|
||||
+-----------------+----------------+------------------+
|
||||
| | first=True | first=False |
|
||||
+=================+================+==================+
|
||||
| utcoff() | stdoff | stdoff + hour |
|
||||
| utcoffset() | stdoff | stdoff + dstoff |
|
||||
+-----------------+----------------+------------------+
|
||||
| dst() | zero | hour |
|
||||
| dst() | zero | dstoff |
|
||||
+-----------------+----------------+------------------+
|
||||
|
||||
|
||||
Non-DST Folds and Gaps
|
||||
----------------------
|
||||
|
||||
On ambiguous/missing times introduced by the change in the standard time
|
||||
offset, the ``dst()`` method should return the same value regardless of
|
||||
the value of ``first`` and the ``utcoff()`` should return values
|
||||
the value of ``first`` and the ``utcoffset()`` should return values
|
||||
according to the following table:
|
||||
|
||||
+-----------------+----------------+-----------------------------+
|
||||
|
@ -310,28 +363,11 @@ according to the following table:
|
|||
| missing | oldoff | newoff = oldoff + delta |
|
||||
+-----------------+----------------+-----------------------------+
|
||||
|
||||
where ``delta`` is the size of the fold or the gap.
|
||||
|
||||
|
||||
Pickle size
|
||||
-----------
|
||||
|
||||
Pickle sizes for the ``datetime.datetime`` and ``datetime.time``
|
||||
objects will not change. The ``first`` flag will be encoded in the
|
||||
first bit of the 5th byte of the ``datetime.datetime`` pickle payload
|
||||
or the 2nd byte of the datetime.time. In the `current implementation`_
|
||||
these bytes are used to store minute value (0-59) and the first bit is
|
||||
always 0. Note that ``first=True`` will be encoded as 0 in the first
|
||||
bit and ``first=False`` as 1. (This change only affects pickle
|
||||
format. In C implementation, the "first" member will get a full byte
|
||||
to store the actual boolean value.)
|
||||
|
||||
We chose the minute byte to store the the "first" bit because this
|
||||
choice preserves the natural ordering.
|
||||
|
||||
.. _current implementation: https://hg.python.org/cpython/file/d3b20bff9c5d/Include/datetime.h#l17
|
||||
|
||||
Temporal Arithmetics
|
||||
--------------------
|
||||
Temporal Arithmetic
|
||||
===================
|
||||
|
||||
The value of "first" will be ignored in all operations except those
|
||||
that involve conversion between timezones. [#]_ As a consequence,
|
||||
|
@ -345,15 +381,16 @@ The result of addition (subtraction) of a timedelta to (from) a
|
|||
datetime will always have ``first`` set to ``True`` even if the
|
||||
original datetime instance had ``first=False``.
|
||||
|
||||
.. [#] As of Python 3.5, ``tzinfo`` is ignored whenever timedelta is
|
||||
added or subtracted from a ``datetime.datetime`` instance or when
|
||||
one ``datetime.datetime`` instance is subtracted from another with
|
||||
the same (even not-None) ``tzinfo``. This may change in the future,
|
||||
but such changes are outside of the scope of this PEP.
|
||||
.. [#] Computing a difference between two aware datetime instances
|
||||
with different values of ``tzinfo`` involves an implicit timezone
|
||||
conversion. In this case, the result may depend on the value of
|
||||
the ``first`` flag in either of the instances, but only if the
|
||||
instance has ``tzinfo`` that accounts for the value of ``first``
|
||||
in its ``utcoffset()`` method.
|
||||
|
||||
|
||||
Backward and Forward Compatibility
|
||||
----------------------------------
|
||||
==================================
|
||||
|
||||
This proposal will have little effect on the programs that do not read
|
||||
the ``first`` flag explicitly or use tzinfo implementations that do.
|
||||
|
@ -368,16 +405,19 @@ in the new versions will become unreadable by the older Python
|
|||
versions. Pickles of instances with ``first=True`` (which is the
|
||||
default) will remain unchanged.
|
||||
|
||||
|
||||
Questions and Answers
|
||||
=====================
|
||||
|
||||
1. Why not call the new flag "isdst"?
|
||||
Why not call the new flag "isdst"?
|
||||
----------------------------------
|
||||
|
||||
-------
|
||||
A non-technical answer
|
||||
......................
|
||||
|
||||
* Alice: Bob - let's have a stargazing party at 01:30 AM tomorrow!
|
||||
* Bob: Should I presume initially that summer time (for example, Daylight
|
||||
Saving Time) is or is not (respectively) in effect for the specified time?
|
||||
* Bob: Should I presume initially that Daylight Saving Time is or is
|
||||
not in effect for the specified time?
|
||||
* Alice: Huh?
|
||||
|
||||
-------
|
||||
|
@ -386,32 +426,77 @@ Questions and Answers
|
|||
* 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.
|
||||
|
||||
-------
|
||||
|
||||
2. Why "first"?
|
||||
A technical reason
|
||||
..................
|
||||
|
||||
* Rejections:
|
||||
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.
|
||||
|
||||
**second**
|
||||
rejected because "second" is already there.
|
||||
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
|
||||
methods such as ``time.localtime``, it is cumbursome as an *input* of
|
||||
methods such as ``time.mktime``.
|
||||
|
||||
If the programmer misspecifies a non-negative value of ``tm_isdst`` to
|
||||
``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``.
|
||||
|
||||
Unlike ``tm_isdst``, the proposed ``first`` flag has no effect on the
|
||||
interpretation of the datetime instance unless without that flag two
|
||||
(or no) interpretations are possible.
|
||||
|
||||
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
|
||||
a method called ``dst()`` and if we called ``first`` "isdst", we would
|
||||
necessarily have situations when "isdst" and ``bool(dst())`` values
|
||||
are different.
|
||||
|
||||
|
||||
Why "first"?
|
||||
------------
|
||||
|
||||
This is a working name chosen initially because the obvious
|
||||
alternative ("second") conflicts with the existing attribute. It has
|
||||
since became clear that it is desirable to have a flag with the
|
||||
default value ``False`` and such that chronological ordering of
|
||||
disambiguated (datetime, flag) pairs would match their lexicographical
|
||||
order.
|
||||
|
||||
The following alternative names have been proposed:
|
||||
|
||||
**fold**
|
||||
Suggested by Guido van Rossum and favored by the author. Has
|
||||
correct connotations and easy mnemonic rules, but at the same
|
||||
time does not invite unbased assumptions.
|
||||
|
||||
**later**
|
||||
rejected because "later" is confusable with "latter".
|
||||
|
||||
**earlier**
|
||||
rejected because "earlier" has the same issue as "first" (requires
|
||||
default to be True) but is two characters longer.
|
||||
|
||||
* Remaining possibilities:
|
||||
A close contender to "fold". The author dislikes it because
|
||||
it is confusable with equally fitting "latter," but in the age
|
||||
of autocompletion 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.
|
||||
|
||||
**repeated**
|
||||
this is a strong candidate
|
||||
|
||||
**is_first**
|
||||
arguably more grammatically correct than "first"
|
||||
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.
|
||||
(Local Time Disambiguation Flag) - short and no-one will attempt
|
||||
to guess what it means without reading the docs. (Feel free to
|
||||
use it in discussions with the meaning ltdf=False is the
|
||||
earlier if you don't want to endorse any of the alternatives
|
||||
above.)
|
||||
|
||||
|
||||
Implementation
|
||||
==============
|
||||
|
|
Loading…
Reference in New Issue