From 32b6a542bdb85a07b1f2c3f7be4a0f23c0467629 Mon Sep 17 00:00:00 2001 From: Guido van Rossum Date: Mon, 21 Sep 2015 13:21:26 -0700 Subject: [PATCH] A variety of small edits and clarifications to PEP 495. --- pep-0495.txt | 74 ++++++++++++++++++++++++++++++++++------------------ 1 file changed, 49 insertions(+), 25 deletions(-) diff --git a/pep-0495.txt b/pep-0495.txt index 658fe1303..b4039a30d 100644 --- a/pep-0495.txt +++ b/pep-0495.txt @@ -41,7 +41,7 @@ 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. - + 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 @@ -122,7 +122,11 @@ 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 ``fold`` is not given. [#]_ If the ``fold`` argument is not +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 specified, the original value of the ``fold`` attribute is copied to the result. @@ -176,18 +180,23 @@ Affected Behaviors What time is it? ................ -The ``datetime.now()`` method called with no arguments, will set +The ``datetime.now()`` method called without arguments will set ``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 +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 returned datetime instance will always have ``fold=0``. +The ``datetime.utcnow()`` method is unaffected. Conversion from naive to aware .............................. +A new feature is proposed to facilitate conversion from naive datetime +instances to aware. + The ``astimezone()`` method will now work for naive ``self``. The system local timezone will be assumed in this case and the ``fold`` flag will be used to determine which local timezone is in effect @@ -201,6 +210,11 @@ For example, on a system set to US/Eastern timezone:: >>> dt.replace(fold=1).astimezone().strftime('%D %T %Z%z') '11/02/14 01:30:00 EST-0500' +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``). + Conversion from POSIX seconds from EPOCH ........................................ @@ -229,6 +243,8 @@ time, there are two values ``s0`` and ``s1`` such that:: datetime.fromtimestamp(s0) == datetime.fromtimestamp(s1) == dt +(This is because ``==`` disregards the value of fold -- see below.) + In this case, ``dt.timestamp()`` will return the smaller of ``s0`` and ``s1`` values if ``dt.fold == 0`` and the larger otherwise. @@ -240,7 +256,6 @@ For example, on a system set to US/Eastern timezone:: >>> datetime(2014, 11, 2, 1, 30, fold=1).timestamp() 1414909800.0 - When a ``datetime.datetime`` instance ``dt`` represents a missing time, there is no value ``s`` for which:: @@ -256,6 +271,8 @@ is always the same as the offset right after the gap. The value returned by ``dt.timestamp()`` given a missing ``dt`` will be the greater of the two "nice to know" values if ``dt.fold == 0`` and the smaller otherwise. +(This is not a typo -- it's intentionally backwards from the rule for +ambiguous times.) For example, on a system set to US/Eastern timezone:: @@ -272,13 +289,14 @@ 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 -the ``fold`` value. +the ``fold`` value. (This is because these pre-PEP implementations +are not using the ``fold`` attribute.) On the other hand, if an object's ``tzinfo`` is set to a fold-aware -implementation, then the value of ``fold`` will affect the result of -several methods, but only if the corresponding time is in a fold or in -a gap: ``utcoffset()``, ``dst()``, ``tzname()``, ``astimezone()``, -``strftime()`` (if "%Z" or "%z" directive is used in the format +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 specification), ``isoformat()``, and ``timetuple()``. @@ -300,8 +318,9 @@ with protocol version 4 (introduced in Python 3.4) or greater. Pickle sizes for the ``datetime.datetime`` and ``datetime.time`` objects will not change. The ``fold`` value will be encoded in the -first bit of the 3rd (1st) byte of ``datetime.datetime`` -(``datetime.time``) pickle payload. In the `current implementation`_ +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`_ these bytes are used to store the month (1-12) and hour (0-23) values 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 @@ -324,8 +343,11 @@ The basic implementation of ``fromutc()`` in the abstract ``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 -offset timezones) override ``fromutc()``. - +offset timezones) override ``fromutc()``. Keeping the default +implementation unchanged has the benefit that pre-PEP 3rd party +implementations that inherit the default ``fromutc()`` are not +accidentally affected. + Guidelines for New tzinfo Implementations ========================================= @@ -381,7 +403,7 @@ Mind the Gap The ``fromutc()`` method should never produce a time in the gap. -If ``utcoffset()``, ``tzname()`` or ``dst()`` method is called on a +If the ``utcoffset()``, ``tzname()`` or ``dst()`` method is called on a 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. @@ -477,7 +499,7 @@ with naive datetime instances. As a consequence, naive by the value of ``fold`` will compare as equal. Applications that need to differentiate between such instances should check the value of ``fold`` explicitly or convert those instances to a timezone that does -not have ambiguous times. +not have ambiguous times (such as UTC). The value of ``fold`` will also be ignored whenever a timedelta is added to or subtracted from a datetime instance which may be either @@ -495,7 +517,9 @@ previous paragraph, timedelta addition ignores both ``fold`` and subtraction. Naive and intra-zone comparisons will ignore the value of ``fold`` and -return the same results as they do now. +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.) The inter-zone subtraction will be defined as it is now: ``t - s`` is computed as ``(t - t.utcoffset()) - (s - @@ -506,14 +530,14 @@ depend on the values of ``t.fold`` and ``s.fold`` when either .. [#] Note that the new rules may result in a paradoxical situation when ``s == t`` but ``s - u != t - u``. Such paradoxes are not really new and are inherent in the overloading of the minus - operator as two different intra- and inter-zone operations. For + operator differently for intra- and inter-zone operations. For 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 - operations: inter-zone datetime subtraction, timedelta subtraction - and intra-zone datetime subtraction which have the mathematical + operations: inter-zone datetime subtraction, timedelta subtraction, + and intra-zone datetime subtraction, which each have the mathematical properties of subtraction separately, but not when combined in a single expression. @@ -522,9 +546,9 @@ Aware datetime Equality Comparison ---------------------------------- The aware datetime comparison operators will work the same as they do -now with results indirectly affected by the value of ``fold`` whenever -``utcoffset()`` value of one of the operands depends on it, with one -exception. Whenever one of the operands in inter-zone comparison is +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 such that its ``utcoffset()`` depends on the value of its ``fold`` fold attribute, the result is ``False``. [#]_ @@ -541,7 +565,7 @@ value of ``fold``: def toutc(t, fold): u = t - t.replace(fold=fold).utcoffset() - return u.replace(tzinfo=None) + return u.replace(tzinfo=None) Then ``t == s`` is equivalent to