PEP 495: The big rename. Changed 'first' to 'fold'.

This commit is contained in:
Alexander Belopolsky 2015-08-26 22:24:22 -04:00
parent 6562c0033c
commit 074d268a3d
1 changed files with 150 additions and 128 deletions

View File

@ -14,9 +14,12 @@ Created: 02-Aug-2015
Abstract Abstract
======== ========
This PEP adds a boolean member to the instances of ``datetime.time`` This PEP adds a new attribute ``fold`` to the instances of
and ``datetime.datetime`` classes that can be used to differentiate ``datetime.time`` and ``datetime.datetime`` classes that can be used
between two moments in time for which local times are the same. to differentiate between two moments in time for which local times are
the same. The allowed values for the `fold` attribute will be 0 and 1
with 0 corresponding to the earlier and 1 to the later of the two
possible readings of an ambiguous local time.
.. sidebar:: US public service advertisement .. sidebar:: US public service advertisement
@ -29,13 +32,13 @@ Rationale
========= =========
In the most world locations there have been and will be times when 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
in which local clocks show the same time twice in the same day. In introduced in which local clocks show the same time twice in the same
these situations, the information displayed on a local clock (or day. In these situations, the information displayed on a local clock
stored in a Python datetime instance) is insufficient to identify a (or stored in a Python datetime instance) is insufficient to identify
particular moment in time. The proposed solution is to add a boolean a particular moment in time. The proposed solution is to add an
flag to the ``datetime`` instances that will distinguish between the attribute to the ``datetime`` instances taking values of 0 and 1 that
two ambiguous times. will enumerate the two ambiguous times.
.. [#] People who live in locations observing the Daylight Saving .. [#] People who live in locations observing the Daylight Saving
Time (DST) move their clocks back (usually one hour) every Fall. Time (DST) move their clocks back (usually one hour) every Fall.
@ -54,28 +57,35 @@ two ambiguous times.
Terminology Terminology
=========== ===========
When clocks are moved back, we say that a *fold* is created in time. 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 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 that falls in the fold is called *ambiguous*. A local time that falls
in the gap is called *missing*. in the gap is called *missing*.
.. [#] 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
Proposal Proposal
======== ========
The "first" flag The "fold" attribute
---------------- --------------------
We propose adding a boolean member called ``first`` to the instances We propose adding an attribute called ``fold`` to the instances
of ``datetime.time`` and ``datetime.datetime`` classes. This member of ``datetime.time`` and ``datetime.datetime`` classes. This attribute
should have the value ``True`` for all instances except those that should have the value 0 for all instances except those that
represent the second (chronologically) moment in time in an ambiguous represent the second (chronologically) moment in time in an ambiguous
case. [#]_ case. For those instances, the value will be 1. [#]_
.. [#] An instance that has ``first=False`` in a non-ambiguous case is .. [#] An instance that has ``fold=1`` in a non-ambiguous case is
said to represent an invalid time (or is invalid for short), but said to represent an invalid time (or is invalid for short), but
users are not prevented from creating invalid instances by passing users are not prevented from creating invalid instances by passing
``first=False`` to a constructor or to a ``replace()`` method. This ``fold=1`` to a constructor or to a ``replace()`` method. This
is similar to the current situation with the instances that fall in is similar to the current situation with the instances that fall in
the spring-forward gap. Such instances don't represent any valid the spring-forward gap. Such instances don't represent any valid
time, but neither the constructors nor the ``replace()`` methods time, but neither the constructors nor the ``replace()`` methods
@ -89,68 +99,68 @@ Affected APIs
Attributes Attributes
.......... ..........
Instances of ``datetime.time`` and ``datetime.datetime`` will get a Instances of ``datetime.time`` and ``datetime.datetime`` classes will
new boolean attribute called "first." get a new attribute ``fold`` with two possible values: 0 and 1.
Constructors Constructors
............ ............
The ``__new__`` methods of the ``datetime.time`` and The ``__new__`` methods of the ``datetime.time`` and
``datetime.datetime`` classes will get a new keyword-only argument ``datetime.datetime`` classes will get a new keyword-only argument
called ``first`` with the default value ``True``. The value of the called ``fold`` with the default value 0. The value of the
``first`` argument will be used to initialize the value of the ``fold`` argument will be used to initialize the value of the
``first`` attribute in the returned instance. ``fold`` attribute in the returned instance.
Methods Methods
....... .......
The ``replace()`` methods of the ``datetime.time`` and The ``replace()`` methods of the ``datetime.time`` and
``datetime.datetime`` classes will get a new keyword-only argument ``datetime.datetime`` classes will get a new keyword-only argument
called ``first``. It will called ``fold``. It will
behave similarly to the other ``replace()`` arguments: if the ``first`` behave similarly to the other ``replace()`` arguments: if the ``fold``
argument is specified and given a boolean value, the new instance argument is specified and given a value 0 or 1, the new instance
returned by ``replace()`` will have its ``first`` attribute set returned by ``replace()`` will have its ``fold`` attribute set
to that value. In CPython, a non-boolean value of ``first`` will to that value. In CPython, any non-integer value of ``fold`` will
raise a ``TypeError``, but other implementations may allow the value raise a ``TypeError``, but other implementations may allow the value
``None`` to behave the same as when ``first`` is not given. If the ``None`` to behave the same as when ``fold`` is not given. If the
``first`` argument is not specified, the original value of the ``first`` ``fold`` argument is not specified, the original value of the ``fold``
attribute is copied to the result. attribute is copied to the result.
C-API C-API
..... .....
Access macros will be defined to extract the value of ``first`` from Access macros will be defined to extract the value of ``fold`` from
``PyDateTime_DateTime`` and ``PyDateTime_Time`` objects. ``PyDateTime_DateTime`` and ``PyDateTime_Time`` objects.
.. code:: .. code::
bool PyDateTime_GET_FIRST(PyDateTime_DateTime *o) int PyDateTime_GET_FOLD(PyDateTime_DateTime *o)
Return the value of ``first`` as a C99 ``bool``. Return the value of ``fold`` as a C ``int``.
.. code:: .. code::
bool PyDateTime_TIME_GET_FIRST(PyDateTime_Time *o) int PyDateTime_TIME_GET_FOLD(PyDateTime_Time *o)
Return the value of ``first`` as a C99 ``bool``. Return the value of ``fold`` as a C ``int``.
Additional constructors will be defined that will take an additional New constructors will be defined that will take an additional
boolean argument to specify the value of ``first`` in the created argument to specify the value of ``fold`` in the created
instance: instance:
.. code:: .. code::
PyObject* PyDateTime_FromDateAndTimeAndFirst(int year, int month, int day, int hour, int minute, int second, int usecond, bool first) PyObject* PyDateTime_FromDateAndTimeAndFold(int year, int month, int day, int hour, int minute, int second, int usecond, int fold)
Return a ``datetime.datetime`` object with the specified year, month, Return a ``datetime.datetime`` object with the specified year, month,
day, hour, minute, second, microsecond and first. day, hour, minute, second, microsecond and fold.
.. code:: .. code::
PyObject* PyTime_FromTimeAndFirst(int hour, int minute, int second, int usecond, bool first) PyObject* PyTime_FromTimeAndFold(int hour, int minute, int second, int usecond, int fold)
Return a ``datetime.time`` object with the specified hour, minute, Return a ``datetime.time`` object with the specified hour, minute,
second, microsecond and first. second, microsecond and fold.
Affected Behaviors Affected Behaviors
@ -160,19 +170,19 @@ What time is it?
................ ................
The ``datetime.now()`` method called with no arguments, will set The ``datetime.now()`` method called with no arguments, will set
``first=False`` when returning the second of the two ambiguous times ``fold=1`` when returning the second of the two ambiguous times in a
in a fold. When called with a ``tzinfo`` argument, the value of the system local time fold. When called with a ``tzinfo`` argument, the
``first`` will be determined by the ``tzinfo.fromutc()`` value of the ``fold`` will be determined by the ``tzinfo.fromutc()``
implementation. If an instance of the built-in ``datetime.timezone`` implementation. If an instance of the ``datetime.timezone`` class
is passed as ``tzinfo``, the returned datetime instance will always (*e.g.* ``datetime.timezone.utc``) is passed as ``tzinfo``, the
have ``first=True``. returned datetime instance will always have ``fold=0``.
Conversion from naive to aware Conversion from naive to aware
.............................. ..............................
The ``astimezone()`` method will now work for naive ``self``. The The ``astimezone()`` method will now work for naive ``self``. The
system local timezone will be assumed in this case and the ``first`` system local timezone will be assumed in this case and the ``fold``
flag will be used to determine which local timezone is in effect flag will be used to determine which local timezone is in effect
in the ambiguous case. in the ambiguous case.
@ -181,7 +191,7 @@ For example, on a system set to US/Eastern timezone::
>>> dt = datetime(2014, 11, 2, 1, 30) >>> dt = datetime(2014, 11, 2, 1, 30)
>>> dt.astimezone().strftime('%D %T %Z%z') >>> dt.astimezone().strftime('%D %T %Z%z')
'11/02/14 01:30:00 EDT-0400' '11/02/14 01:30:00 EDT-0400'
>>> dt.replace(first=False).astimezone().strftime('%D %T %Z%z') >>> dt.replace(fold=1).astimezone().strftime('%D %T %Z%z')
'11/02/14 01:30:00 EST-0500' '11/02/14 01:30:00 EST-0500'
@ -189,14 +199,14 @@ Conversion from POSIX seconds from EPOCH
........................................ ........................................
The ``fromtimestamp()`` static method of ``datetime.datetime`` will The ``fromtimestamp()`` static method of ``datetime.datetime`` will
set the ``first`` attribute appropriately in the returned object. set the ``fold`` attribute appropriately in the returned object.
For example, on a system set to US/Eastern timezone:: For example, on a system set to US/Eastern timezone::
>>> datetime.fromtimestamp(1414906200) >>> datetime.fromtimestamp(1414906200)
datetime.datetime(2014, 11, 2, 1, 30) datetime.datetime(2014, 11, 2, 1, 30)
>>> datetime.fromtimestamp(1414906200 + 3600) >>> datetime.fromtimestamp(1414906200 + 3600)
datetime.datetime(2014, 11, 2, 1, 30, first=False) datetime.datetime(2014, 11, 2, 1, 30, fold=1)
Conversion to POSIX seconds from EPOCH Conversion to POSIX seconds from EPOCH
@ -204,7 +214,7 @@ Conversion to POSIX seconds from EPOCH
The ``timestamp()`` method of ``datetime.datetime`` will return different The ``timestamp()`` method of ``datetime.datetime`` will return different
values for ``datetime.datetime`` instances that differ only by the value values for ``datetime.datetime`` instances that differ only by the value
of their ``first`` attribute if and only if these instances represent an of their ``fold`` attribute if and only if these instances represent an
ambiguous or a missing time. ambiguous or a missing time.
When a ``datetime.datetime`` instance ``dt`` represents an ambiguous When a ``datetime.datetime`` instance ``dt`` represents an ambiguous
@ -213,14 +223,14 @@ time, there are two values ``s0`` and ``s1`` such that::
datetime.fromtimestamp(s0) == datetime.fromtimestamp(s1) == dt datetime.fromtimestamp(s0) == datetime.fromtimestamp(s1) == dt
In this case, ``dt.timestamp()`` will return the smaller of ``s0`` In this case, ``dt.timestamp()`` will return the smaller of ``s0``
and ``s1`` values if ``dt.first == True`` and the larger otherwise. and ``s1`` values if ``dt.fold == True`` and the larger otherwise.
For example, on a system set to US/Eastern timezone:: For example, on a system set to US/Eastern timezone::
>>> datetime(2014, 11, 2, 1, 30, first=True).timestamp() >>> datetime(2014, 11, 2, 1, 30, fold=0).timestamp()
1414906200.0 1414906200.0
>>> datetime(2014, 11, 2, 1, 30, first=False).timestamp() >>> datetime(2014, 11, 2, 1, 30, fold=1).timestamp()
1414909800.0 1414909800.0
@ -237,14 +247,14 @@ other is the similar value but in a timezone the UTC offset
is always the same as the offset right after the gap. is always the same as the offset right after the gap.
The value returned by ``dt.timestamp()`` given a missing The value returned by ``dt.timestamp()`` given a missing
``dt`` will be the larger of the two "nice to know" values ``dt`` will be the greater of the two "nice to know" values
if ``dt.first == True`` and the smaller otherwise. if ``dt.fold == 0`` and the smaller otherwise.
For example, on a system set to US/Eastern timezone:: For example, on a system set to US/Eastern timezone::
>>> datetime(2015, 3, 8, 2, 30, first=True).timestamp() >>> datetime(2015, 3, 8, 2, 30, fold=0).timestamp()
1425799800.0 1425799800.0
>>> datetime(2015, 3, 8, 2, 30, first=False).timestamp() >>> datetime(2015, 3, 8, 2, 30, fold=1).timestamp()
1425796200.0 1425796200.0
@ -252,36 +262,35 @@ Combining and splitting date and time
..................................... .....................................
The ``datetime.datetime.combine()`` method will copy the value of the The ``datetime.datetime.combine()`` method will copy the value of the
``first`` attribute to the resulting ``datetime.datetime`` instance. ``fold`` attribute to the resulting ``datetime.datetime`` instance.
The ``datetime.datetime.time()`` method will copy the value of the The ``datetime.datetime.time()`` method will copy the value of the
``first`` attribute to the resulting ``datetime.time`` instance. ``fold`` attribute to the resulting ``datetime.time`` instance.
Pickles Pickles
....... .......
Pickle sizes for the ``datetime.datetime`` and ``datetime.time`` Pickle sizes for the ``datetime.datetime`` and ``datetime.time``
objects will not change. The ``first`` flag will be encoded in the objects will not change. The ``fold`` value will be encoded in the
first bit of the 5th byte of the ``datetime.datetime`` pickle payload first bit of the 5th byte of the ``datetime.datetime`` pickle payload
or the 2nd byte of the datetime.time. In the `current implementation`_ 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 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 always 0. (This change only affects pickle format. In the C
bit and ``first=False`` as 1. (This change only affects pickle implementation, the ``fold`` attribute will get a full byte to store its
format. In the C implementation, the "first" member will get a full byte value.)
to store the actual boolean value.)
.. _current implementation: https://hg.python.org/cpython/file/d3b20bff9c5d/Include/datetime.h#l17 .. _current implementation: https://hg.python.org/cpython/file/d3b20bff9c5d/Include/datetime.h#l17
Implementations of tzinfo in stdlib
=================================== Implementations of tzinfo in the Standard Library
=================================================
No new implementations of ``datetime.tzinfo`` abstract class are No new implementations of ``datetime.tzinfo`` abstract class are
proposed 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()`` not introduce ambiguous local times and their ``utcoffset()``
implementation will return the same constant value as they do now implementation will return the same constant value as they do now
regardless of the value of ``first``. regardless of the value of ``fold``.
The basic implementation of ``fromutc()`` in the abstract The basic implementation of ``fromutc()`` in the abstract
``datetime.tzinfo`` class will not change. It is currently not ``datetime.tzinfo`` class will not change. It is currently not
@ -302,7 +311,7 @@ Ignorance is Bliss
------------------ ------------------
New implementations of ``utcoffset()``, ``tzname()`` and ``dst()`` New implementations of ``utcoffset()``, ``tzname()`` and ``dst()``
methods should ignore the value of ``first`` unless they are called on methods should ignore the value of ``fold`` unless they are called on
the ambiguous or missing times. the ambiguous or missing times.
@ -312,15 +321,15 @@ In the DST Fold
New subclasses should override the base-class ``fromutc()`` method and New subclasses should override the base-class ``fromutc()`` method and
implement it so that in all cases where two UTC times ``u1`` and implement it so that in all cases where two UTC times ``u1`` and
``u2`` (``u1`` <``u2``) correspond to the same local time ``u2`` (``u1`` <``u2``) correspond to the same local time
``fromutc(u1)`` will return an instance with ``first=True`` and ``fromutc(u1)`` will return an instance with ``fold=0`` and
``fromutc(u2)`` will return an instance with ``first=False``. In all ``fromutc(u2)`` will return an instance with ``fold=1``. In all
other cases the returned instance should have ``first=True``. other cases the returned instance should have ``fold=0``.
On an ambiguous time introduced at the end of DST, the values returned On an ambiguous time introduced at the end of DST, the values returned
by ``utcoffset()`` and ``dst()`` methods should be as follows by ``utcoffset()`` and ``dst()`` methods should be as follows
+-----------------+----------------+------------------+ +-----------------+----------------+------------------+
| | first=True | first=False | | | fold=0 | fold=1 |
+=================+================+==================+ +=================+================+==================+
| utcoffset() | stdoff + dstoff| stdoff | | utcoffset() | stdoff + dstoff| stdoff |
+-----------------+----------------+------------------+ +-----------------+----------------+------------------+
@ -339,7 +348,7 @@ On a missing time introduced at the start of DST, the values returned
by ``utcoffset()`` and ``dst()`` methods should be as follows by ``utcoffset()`` and ``dst()`` methods should be as follows
+-----------------+----------------+------------------+ +-----------------+----------------+------------------+
| | first=True | first=False | | | fold=0 | fold=1 |
+=================+================+==================+ +=================+================+==================+
| utcoffset() | stdoff | stdoff + dstoff | | utcoffset() | stdoff | stdoff + dstoff |
+-----------------+----------------+------------------+ +-----------------+----------------+------------------+
@ -352,11 +361,11 @@ Non-DST Folds and Gaps
On ambiguous/missing times introduced by the change in the standard time On ambiguous/missing times introduced by the change in the standard time
offset, the ``dst()`` method should return the same value regardless of offset, the ``dst()`` method should return the same value regardless of
the value of ``first`` and the ``utcoffset()`` should return values the value of ``fold`` and the ``utcoffset()`` should return values
according to the following table: according to the following table:
+-----------------+----------------+-----------------------------+ +-----------------+----------------+-----------------------------+
| | first=True | first=False | | | fold=0 | fold=1 |
+=================+================+=============================+ +=================+================+=============================+
| ambiguous | oldoff | newoff = oldoff - delta | | ambiguous | oldoff | newoff = oldoff - delta |
+-----------------+----------------+-----------------------------+ +-----------------+----------------+-----------------------------+
@ -369,23 +378,23 @@ where ``delta`` is the size of the fold or the gap.
Temporal Arithmetic Temporal Arithmetic
=================== ===================
The value of "first" will be ignored in all operations except those The value of "fold" will be ignored in all operations except those
that involve conversion between timezones. [#]_ As a consequence, that involve conversion between timezones. [#]_ As a consequence,
``datetime.datetime`` or ``datetime.time`` instances that differ only ``datetime.datetime`` or ``datetime.time`` instances that differ only
by the value of ``first`` will compare as equal. Applications that by the value of ``fold`` will compare as equal. Applications that
need to differentiate between such instances should check the value of need to differentiate between such instances should check the value of
``first`` or convert them to a timezone that does not have ambiguous ``fold`` or convert them to a timezone that does not have ambiguous
times. times.
The result of addition (subtraction) of a timedelta to (from) a The result of addition (subtraction) of a timedelta to (from) a
datetime will always have ``first`` set to ``True`` even if the datetime will always have ``fold`` set to 0 even if the
original datetime instance had ``first=False``. original datetime instance had ``fold=1``.
.. [#] Computing a difference between two aware datetime instances .. [#] Computing a difference between two aware datetime instances
with different values of ``tzinfo`` involves an implicit timezone with different values of ``tzinfo`` involves an implicit timezone
conversion. In this case, the result may depend on the value of conversion. In this case, the result may depend on the value of
the ``first`` flag in either of the instances, but only if the the ``fold`` attribute in either of the instances, but only if the
instance has ``tzinfo`` that accounts for the value of ``first`` instance has ``tzinfo`` that accounts for the value of ``fold``
in its ``utcoffset()`` method. in its ``utcoffset()`` method.
@ -393,16 +402,16 @@ Backward and Forward Compatibility
================================== ==================================
This proposal will have little effect on the programs that do not read This proposal will have little effect on the programs that do not read
the ``first`` flag explicitly or use tzinfo implementations that do. the ``fold`` flag explicitly or use tzinfo implementations that do.
The only visible change for such programs will be that conversions to The only visible change for such programs will be that conversions to
and from POSIX timestamps will now round-trip correctly (up to and from POSIX timestamps will now round-trip correctly (up to
floating point rounding). Programs that implemented a work-around to floating point rounding). Programs that implemented a work-around to
the old incorrect behavior may need to be modified. the old incorrect behavior may need to be modified.
Pickles produced by older programs will remain fully forward Pickles produced by older programs will remain fully forward
compatible. Only datetime/time instances with ``first=False`` pickled compatible. Only datetime/time instances with ``fold=1`` pickled
in the new versions will become unreadable by the older Python in the new versions will become unreadable by the older Python
versions. Pickles of instances with ``first=True`` (which is the versions. Pickles of instances with ``fold=0`` (which is the
default) will remain unchanged. default) will remain unchanged.
@ -428,6 +437,17 @@ A non-technical answer
------- -------
(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
the first 01:30 AM and fold=1 is the second.
A technical reason A technical reason
.................. ..................
@ -448,35 +468,37 @@ there is rarely a way to know anything about DST *before* a call to
``time.mktime`` is made, the only sane choice is usually ``time.mktime`` is made, the only sane choice is usually
``tm_isdst=-1``. ``tm_isdst=-1``.
Unlike ``tm_isdst``, the proposed ``first`` flag has no effect on the Unlike ``tm_isdst``, the proposed ``fold`` attribute has no effect on
interpretation of the datetime instance unless without that flag two the interpretation of the datetime instance unless without that
(or no) interpretations are possible. attribute two (or no) interpretations are possible.
Since it would be very confusing to have something called ``isdst`` Since it would be very confusing to have something called ``isdst``
that does not have the same semantics as ``tm_isdst``, we need a that does not have the same semantics as ``tm_isdst``, we need a
different name. Moreover, the ``datetime.datetime`` class already has different name. Moreover, the ``datetime.datetime`` class already has
a method called ``dst()`` and if we called ``first`` "isdst", we would a method called ``dst()`` and if we called ``fold`` "isdst", we would
necessarily have situations when "isdst" and ``bool(dst())`` values necessarily have situations when "isdst" is zero but ``dst()`` is not
are different. or the other way around.
Why "fold"?
-----------
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.
Why "first"? What is "first"?
------------ ----------------
This is a working name chosen initially because the obvious This was a working name of the attribute chosen initially because the
alternative ("second") conflicts with the existing attribute. It has obvious alternative ("second") conflicts with the existing attribute.
since become clear that it is desirable to have a flag with the It was rejected mostly on the grounds that it would make True a
default value ``False`` and such that chronological ordering of default value.
disambiguated (datetime, flag) pairs would match their lexicographical
order.
The following alternative names have been proposed: The following alternative names have also been considered:
**fold**
Suggested by Guido van Rossum and favored by one (but disfavored by another) author. Has
correct connotations and easy mnemonic rules, but at the same
time does not invite unbased assumptions.
**later** **later**
A close contender to "fold". One author dislikes it because A close contender to "fold". One author dislikes it because
it is confusable with equally fitting "latter," but in the age it is confusable with equally fitting "latter," but in the age
@ -510,7 +532,7 @@ Are two values enough?
---------------------- ----------------------
Several reasons have been raised to allow a ``None`` or -1 value for Several reasons have been raised to allow a ``None`` or -1 value for
the ``first`` flag: backward compatibility, analogy with ``tm_isdst`` the ``fold`` attribute: backward compatibility, analogy with ``tm_isdst``
and strict checking for invalid times. and strict checking for invalid times.
@ -518,23 +540,23 @@ Backward Compatibility
...................... ......................
It has been suggested that backward compatibility can be improved if It has been suggested that backward compatibility can be improved if
the default value of the ``first`` flag was ``None`` which would the default value of the ``fold`` flag was ``None`` which would
signal that pre-PEP behavior is requested. Based on the analysis signal that pre-PEP behavior is requested. Based on the analysis
below, we believe that the proposed changes with the ``first=True`` below, we believe that the proposed changes with the ``fold=0``
default are sufficiently backward compatible. default are sufficiently backward compatible.
This PEP provides only three ways for a program to discover that two This PEP provides only three ways for a program to discover that two
otherwise identical datetime instances have different values of otherwise identical datetime instances have different values of
``first``: (1) an explicit check of the ``first`` attribute; (2) if ``fold``: (1) an explicit check of the ``fold`` attribute; (2) if
the instances are naive - conversion to another timezone using the the instances are naive - conversion to another timezone using the
``astimezone()`` method; and (3) conversion to ``float`` using the ``astimezone()`` method; and (3) conversion to ``float`` using the
``timestamp()`` method. ``timestamp()`` method.
Since ``first`` is a new attribute, the first option is not available Since ``fold`` is a new attribute, the first option is not available
to the existing programs. Note that option (2) only works for naive 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 datetimes that happen to be in a fold or a gap in the system time
zone. In all other cases, the value of ``first`` will be ignored in zone. In all other cases, the value of ``fold`` will be ignored in
the conversion unless the instances use a ``first``-aware ``tzinfo`` the conversion unless the instances use a ``fold``-aware ``tzinfo``
which would not be available in a pre-PEP program. Similarly, the which would not be available in a pre-PEP program. Similarly, the
``astimezone()`` called on a naive instance will not be available in ``astimezone()`` called on a naive instance will not be available in
such program because ``astimezone()`` does not currently work with such program because ``astimezone()`` does not currently work with
@ -548,7 +570,7 @@ implementation, the result is undefined. Depending on the system
``mktime`` implementation, the programs can see different results or ``mktime`` implementation, the programs can see different results or
errors in those cases. With this PEP in place, the value of timestamp 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 will be well-defined in those cases but will depend on the value of
the ``first`` flag. We consider the change in the ``fold`` flag. We consider the change in
``datetime.timestamp()`` method behavior a bug fix enabled by this ``datetime.timestamp()`` method behavior a bug fix enabled by this
PEP. The old behavior can still be emulated by the users who depend 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`` on it by writing ``time.mktime(dt.timetuple()) + 1e-6*dt.microsecond``
@ -563,16 +585,16 @@ 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 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. the fields) is the only choice that is useful in practice.
With the ``first`` flag, however, ``datetime.timestamp()`` will return With the ``fold`` flag, however, ``datetime.timestamp()`` will return
the same value as ``mktime`` with ``tm_isdst=-1`` in 99.98% of the the same value as ``mktime`` with ``tm_isdst=-1`` in 99.98% of the
time for most time zones with DST transitions. Moreover, time for most time zones with DST transitions. Moreover,
``tm_isdst=-1``-like behavior is specified *regardless* of the value ``tm_isdst=-1``-like behavior is specified *regardless* of the value
of ``first``. of ``fold``.
It is only in the 0.02% cases (2 hours per year) that the It is only in the 0.02% cases (2 hours per year) that the
``datetime.timestamp()`` and ``mktime`` with ``tm_isdst=-1`` may ``datetime.timestamp()`` and ``mktime`` with ``tm_isdst=-1`` may
disagree. However, even in this case, most of the ``mktime`` disagree. However, even in this case, most of the ``mktime``
implementations will return the ``first=True`` or the ``first=False`` implementations will return the ``fold=0`` or the ``fold=1``
value even though relevant standards allow ``mktime`` to return -1 and value even though relevant standards allow ``mktime`` to return -1 and
set an error code in those cases. set an error code in those cases.
@ -602,22 +624,22 @@ guaranteed that
>>> datetime.datetime.fromtimestamp(t) >>> datetime.datetime.fromtimestamp(t)
datetime.datetime(2015, 6, 1, 12, 0) datetime.datetime(2015, 6, 1, 12, 0)
This PEP extends the same guarantee to both values of ``first``: This PEP extends the same guarantee to both values of ``fold``:
.. code:: .. code::
>>> t = datetime.datetime(2015, 6, 1, 12, first=True).timestamp() >>> t = datetime.datetime(2015, 6, 1, 12, fold=0).timestamp()
>>> datetime.datetime.fromtimestamp(t) >>> datetime.datetime.fromtimestamp(t)
datetime.datetime(2015, 6, 1, 12, 0) datetime.datetime(2015, 6, 1, 12, 0)
.. code:: .. code::
>>> t = datetime.datetime(2015, 6, 1, 12, first=False).timestamp() >>> t = datetime.datetime(2015, 6, 1, 12, fold=1).timestamp()
>>> datetime.datetime.fromtimestamp(t) >>> datetime.datetime.fromtimestamp(t)
datetime.datetime(2015, 6, 1, 12, 0) datetime.datetime(2015, 6, 1, 12, 0)
Thus one of the suggested uses for ``first=-1`` -- to match the legacy Thus one of the suggested uses for ``fold=-1`` -- to match the legacy
behavior -- is not needed. Either choice of ``first`` will match the behavior -- is not needed. Either choice of ``fold`` will match the
old behavior except in the few cases where the old behavior was old behavior except in the few cases where the old behavior was
undefined. undefined.
@ -625,7 +647,7 @@ undefined.
Strict Invalid Time Checking Strict Invalid Time Checking
............................ ............................
Another suggestion was to use ``first=-1`` or ``first=None`` to Another suggestion was to use ``fold=-1`` or ``fold=None`` to
indicate that the program truly has no means to deal with the folds indicate that the program truly has no means to deal with the folds
and gaps and ``dt.utcoffset()`` should raise an error whenever ``dt`` and gaps and ``dt.utcoffset()`` should raise an error whenever ``dt``
represents an ambiguous or missing local time. represents an ambiguous or missing local time.
@ -642,10 +664,10 @@ implemented in user code:
def utcoffset(dt, raise_on_gap=True, raise_on_fold=False): def utcoffset(dt, raise_on_gap=True, raise_on_fold=False):
u = dt.utcoffset() u = dt.utcoffset()
v = dt.replace(first=not dt.first).utcoffset() v = dt.replace(fold=not dt.fold).utcoffset()
if u == v: if u == v:
return u return u
if (u < v) == dt.first: if (u < v) == dt.fold:
if raise_on_fold: if raise_on_fold:
raise AmbiguousTimeError raise AmbiguousTimeError
else: else: