From 074d268a3de1cb936739bf22e619cad0f1e27206 Mon Sep 17 00:00:00 2001 From: Alexander Belopolsky Date: Wed, 26 Aug 2015 22:24:22 -0400 Subject: [PATCH] PEP 495: The big rename. Changed 'first' to 'fold'. --- pep-0495.txt | 278 +++++++++++++++++++++++++++------------------------ 1 file changed, 150 insertions(+), 128 deletions(-) diff --git a/pep-0495.txt b/pep-0495.txt index de8ed0e0b..bc09c75fc 100644 --- a/pep-0495.txt +++ b/pep-0495.txt @@ -14,9 +14,12 @@ Created: 02-Aug-2015 Abstract ======== -This PEP adds a boolean member to the instances of ``datetime.time`` -and ``datetime.datetime`` classes that can be used to differentiate -between two moments in time for which local times are the same. +This PEP adds a new attribute ``fold`` to the instances of +``datetime.time`` and ``datetime.datetime`` classes that can be used +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 @@ -29,13 +32,13 @@ 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 -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 a boolean -flag to the ``datetime`` instances that will distinguish between the -two ambiguous times. +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. .. [#] People who live in locations observing the Daylight Saving Time (DST) move their clocks back (usually one hour) every Fall. @@ -54,28 +57,35 @@ two ambiguous times. 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 that falls in the fold is called *ambiguous*. A local time that falls 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 ======== -The "first" flag ----------------- +The "fold" attribute +-------------------- -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 +We propose adding an attribute called ``fold`` to the instances +of ``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. [#]_ +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 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 the spring-forward gap. Such instances don't represent any valid time, but neither the constructors nor the ``replace()`` methods @@ -89,68 +99,68 @@ Affected APIs Attributes .......... -Instances of ``datetime.time`` and ``datetime.datetime`` will get a -new boolean attribute called "first." +Instances of ``datetime.time`` and ``datetime.datetime`` classes will +get a new attribute ``fold`` with two possible values: 0 and 1. Constructors ............ The ``__new__`` methods of the ``datetime.time`` and ``datetime.datetime`` classes will get a new keyword-only argument -called ``first`` with the default value ``True``. The value of the -``first`` argument will be used to initialize the value of the -``first`` attribute in the returned instance. +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. Methods ....... The ``replace()`` methods of the ``datetime.time`` and ``datetime.datetime`` classes will get a new keyword-only argument -called ``first``. It will -behave similarly to the other ``replace()`` arguments: if the ``first`` -argument is specified and given a boolean value, the new instance -returned by ``replace()`` will have its ``first`` attribute set -to that value. In CPython, a non-boolean value of ``first`` will +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 when ``first`` is not given. If the -``first`` argument is not specified, the original value of the ``first`` +``None`` to behave the same as when ``fold`` is not given. If the +``fold`` argument is not specified, the original value of the ``fold`` attribute is copied to the result. 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. .. 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:: - 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 -boolean argument to specify the value of ``first`` in the created +New constructors will be defined that will take an additional +argument to specify the value of ``fold`` in the created instance: .. 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, -day, hour, minute, second, microsecond and first. +day, hour, minute, second, microsecond and fold. .. 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, -second, microsecond and first. +second, microsecond and fold. Affected Behaviors @@ -160,19 +170,19 @@ 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``. +``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()`` +implementation. If an instance of the ``datetime.timezone`` class +(*e.g.* ``datetime.timezone.utc``) is passed as ``tzinfo``, the +returned datetime instance will always have ``fold=0``. Conversion from naive to aware .............................. 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 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.astimezone().strftime('%D %T %Z%z') '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' @@ -189,14 +199,14 @@ Conversion from POSIX seconds from EPOCH ........................................ 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:: >>> datetime.fromtimestamp(1414906200) datetime.datetime(2014, 11, 2, 1, 30) >>> 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 @@ -204,7 +214,7 @@ 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 +of their ``fold`` attribute if and only if these instances represent an ambiguous or a missing time. 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 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:: - >>> datetime(2014, 11, 2, 1, 30, first=True).timestamp() + >>> datetime(2014, 11, 2, 1, 30, fold=0).timestamp() 1414906200.0 - >>> datetime(2014, 11, 2, 1, 30, first=False).timestamp() + >>> datetime(2014, 11, 2, 1, 30, fold=1).timestamp() 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. The value returned by ``dt.timestamp()`` given a missing -``dt`` will be the larger of the two "nice to know" values -if ``dt.first == True`` and the smaller otherwise. +``dt`` will be the greater of the two "nice to know" values +if ``dt.fold == 0`` and the smaller otherwise. 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 - >>> datetime(2015, 3, 8, 2, 30, first=False).timestamp() + >>> datetime(2015, 3, 8, 2, 30, fold=1).timestamp() 1425796200.0 @@ -252,36 +262,35 @@ Combining and splitting date and time ..................................... 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 -``first`` attribute to the resulting ``datetime.time`` instance. +``fold`` 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 +objects will not change. The ``fold`` value 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.) - +always 0. (This change only affects pickle format. In the C +implementation, the ``fold`` attribute will get a full byte to store its +value.) .. _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 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``. +regardless of the value of ``fold``. The basic implementation of ``fromutc()`` in the abstract ``datetime.tzinfo`` class will not change. It is currently not @@ -302,7 +311,7 @@ Ignorance is Bliss ------------------ 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. @@ -312,15 +321,15 @@ 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 should have ``first=True``. +``fromutc(u1)`` will return an instance with ``fold=0`` and +``fromutc(u2)`` will return an instance with ``fold=1``. In all +other cases the returned instance should have ``fold=0``. On an ambiguous time introduced at the end of DST, the values returned by ``utcoffset()`` and ``dst()`` methods should be as follows +-----------------+----------------+------------------+ -| | first=True | first=False | +| | fold=0 | fold=1 | +=================+================+==================+ | 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 +-----------------+----------------+------------------+ -| | first=True | first=False | +| | fold=0 | fold=1 | +=================+================+==================+ | 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 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: +-----------------+----------------+-----------------------------+ -| | first=True | first=False | +| | fold=0 | fold=1 | +=================+================+=============================+ | ambiguous | oldoff | newoff = oldoff - delta | +-----------------+----------------+-----------------------------+ @@ -369,23 +378,23 @@ where ``delta`` is the size of the fold or the gap. 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, ``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 -``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. 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``. +datetime will always have ``fold`` set to 0 even if the +original datetime instance had ``fold=1``. .. [#] 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`` + the ``fold`` attribute in either of the instances, but only if the + instance has ``tzinfo`` that accounts for the value of ``fold`` 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 -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 and from POSIX timestamps will now round-trip correctly (up to floating point rounding). Programs that implemented a work-around to the old incorrect behavior may need to be modified. 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 -versions. Pickles of instances with ``first=True`` (which is the +versions. Pickles of instances with ``fold=0`` (which is the 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 .................. @@ -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 ``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. +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. 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. +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. + +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 -alternative ("second") conflicts with the existing attribute. It has -since become 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. +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. -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** A close contender to "fold". One author dislikes it because 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 -the ``first`` flag: backward compatibility, analogy with ``tm_isdst`` +the ``fold`` attribute: backward compatibility, analogy with ``tm_isdst`` and strict checking for invalid times. @@ -518,23 +540,23 @@ Backward Compatibility ...................... 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 -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. This PEP provides only three ways for a program to discover that two 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 ``astimezone()`` method; and (3) conversion to ``float`` using the ``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 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 -the conversion unless the instances use a ``first``-aware ``tzinfo`` +zone. In all other cases, the value of ``fold`` will be ignored in +the conversion unless the instances use a ``fold``-aware ``tzinfo`` 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 @@ -548,7 +570,7 @@ 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 -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 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`` @@ -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 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 time for most time zones with DST transitions. Moreover, ``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 ``datetime.timestamp()`` and ``mktime`` with ``tm_isdst=-1`` may 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 set an error code in those cases. @@ -602,22 +624,22 @@ guaranteed that >>> datetime.datetime.fromtimestamp(t) 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:: - >>> 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(2015, 6, 1, 12, 0) .. 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(2015, 6, 1, 12, 0) -Thus one of the suggested uses for ``first=-1`` -- to match the legacy -behavior -- is not needed. Either choice of ``first`` will match the +Thus one of the suggested uses for ``fold=-1`` -- to match the legacy +behavior -- is not needed. Either choice of ``fold`` will match the old behavior except in the few cases where the old behavior was undefined. @@ -625,7 +647,7 @@ undefined. 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 and gaps and ``dt.utcoffset()`` should raise an error whenever ``dt`` 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): u = dt.utcoffset() - v = dt.replace(first=not dt.first).utcoffset() + v = dt.replace(fold=not dt.fold).utcoffset() if u == v: return u - if (u < v) == dt.first: + if (u < v) == dt.fold: if raise_on_fold: raise AmbiguousTimeError else: