classification
Title: Describe PEP 495 features in "What's New in Python 3.6" document
Type: enhancement Stage: resolved
Components: Documentation Versions: Python 3.6
process
Status: closed Resolution: fixed
Dependencies: Superseder:
Assigned To: belopolsky Nosy List: Elvis.Pranskevichus, belopolsky, p-ganssle, tim.peters, yselivanov
Priority: normal Keywords:

Created on 2016-11-08 17:10 by belopolsky, last changed 2016-11-10 18:23 by yselivanov. This issue is now closed.

Messages (6)
msg280320 - (view) Author: Alexander Belopolsky (belopolsky) * (Python committer) Date: 2016-11-08 17:10
See also #27595.
msg280321 - (view) Author: Alexander Belopolsky (belopolsky) * (Python committer) Date: 2016-11-08 17:14
Paul (p-ganssle), based on your recent experience implementing PEP 495, what are the main points that we should cover in What's New?  Also, any comments/criticism on the recent changes to the datetime module documentation are welcome.
msg280396 - (view) Author: Paul Ganssle (p-ganssle) * (Python committer) Date: 2016-11-09 14:02
I've never written a "What's New" before, but here are the main things I took away from implementing a PEP 495-compliant tzinfo class:

- The `fold` attribute is the SECOND occurrence of the time, not the first occurrence of the time. In my first pass solution of this, I had this inverted, so I would say it's worth making it very clear which is which, maybe with an example (I'm using full zone names here like those returned by `dateutil.tz.tzwin`):

    >>> datetime(2011, 11, 6, 1, 30, fold=0, tzinfo=US_EASTERN).tzname()
    'Eastern Daylight Time'
    >>> datetime(2011, 11, 6, 1, 30, fold=1, tzinfo=US_EASTERN).tzname()
    'Eastern Standard Time'

- Because the default for "fold" is 0, the default for "unspecified" fold has changed from being on the STD side to being on the DST side:

    >>> datetime(2011, 11, 6, 1, 30, tzinfo=US_EASTERN_NO495).tzname()
    'EST'
    >>> datetime(2011, 11, 6, 1, 30, tzinfo=US_EASTERN_PEP495).tzname()
    'EDT'

- It is now best practices to implement your own `fromutc()` (I got the impression that in the past it was encouraged that you generally just wrote a `utcoffset()` and `dst()` function.

- Comparisons of datetimes have changed such that inter-zone comparisons representing the same wall time and offset will now fail during ambiguous times (depending on the resolution of #28601, the exact semantics of what is an inter-zone comparison should be clarified):

    >>> dt1 = datetime(2016, 11, 6, 1, 30, fold=1, tzinfo=US_EASTERN)
    >>> dt2 = datetime(2016, 11, 6, 1, 30, fold=0, tzinfo=US_CENTRAL)
    >>> dt1 == dt2
    False

I think the remainder of my insights would have to do with backwards-compatibility and I'm not sure how much relevance they have to "What's New".

With regards to the documentation, here are my notes:

- One thing that should be made more clear is that arithmetic operations wipe out the `fold` attribute - this behavior and the reasoning behind it should probably be clear in the section on datetime operations, because I think people will be confused by this:

   >>> from datetime import datetime, timedelta
   >>> from dateutil import tz
   >>> dt = datetime(2011, 11, 6, 1, 30, fold=1, tz=gettz('US/Eastern')
   >>> dt.tzname()
   'EST'
   >>> (dt + timedelta(minutes=1)).tzname()
   'EDT'

- Not related to the changes in 3.6, but it might also be worth clarifying that `datetime.astimezone()` with no arguments returns a datetime with a fixed offset zone, NOT the local zone (it's somewhat clear, but it could be more explicit):

   >>> dt.astimezone().tzname()
   2011-11-06 01:30:00-5:00
   >>> dt.astimezone() - timedelta(hours=5)
   2011-11-05 20:30:00-05:00
   >>> dt.astimezone(tz.tzlocal())
   2011-11-06 01:30:00-5:00
   >>> dt.astimezone(tz.tzlocal()) - timedelta(hours=5)
   2011-11-05 20:30:00-04:00

- In the section on "working with datetime objects", GMT1 and GMT2 objects don't support the `fold` attribute. That might be confusing (though there are fold-aware tzinfo objects later in the documentation).

- In the section on time.tzname(), the example defines its own GMT1 time zone. It might be clearer to just define GMT1 = datetime.timezone(timedelta(hours=1), "Europe/Prague') rather than create a new fixed offset tzinfo.

- The "most implementations of dst() will probably look like one of these two" section should be updated to reflect `fold`, maybe something like this:

    def dst(self, dt):
        # Code to set dston and dstoff to the time zone's DST
        # transition times based on the input dt.year, and expressed
        # in standard local time.

        dt_naive = dt.replace(tzinfo=None)
        if dston <= dt_naive < dstoff:
            if dt_naive < dston + timedelta(hours=1) and dt.fold:
                return timedelta(0)
            else:
                return timedelta(hours=1)
        else:
            return timedelta(0)

- The tzinfo.fromutc(dt) documentation suggests that this method is capable of handling non-fixed offset zones.  Per #28602, it seems that it should be made clear that all zones implementing `fold` support should implement their own `fromutc` method, as there is a deliberate bug in the algorithm when folds are supported.

- This may be slightly confusing: "Note that the datetime instances that differ only by the value of the fold attribute are considered equal in comparisons.", because when I first read this, I assumed it meant that datetime(2011, 11, 6, 1, 30, tzinfo=US_EASTERN) == datetime(2011, 11, 6, 1, 30, fold=1, tzinfo=US_EASTERN), but in fact it either means that *unambiguous* datetimes that differ only by their `fold` attribute are equal, ambiguous datetimes with a fold-aware tzinfo attached will return a different utcoffset() and thus not compare equal.

- I found this slightly confusing, under the "dateutil.tz" heading: "The standard library has timezone class for handling arbitrary fixed offsets from UTC and timezone.utc as UTC timezone instance." Since dateutil.tz does not provide a `timezone` class, I thought it was a mistake, but I think that that text should actually be *above* the "dateutil.tz" header, since it refers to the Python standard library.

- On the subject of the dateutil.tz callout (thanks for that, by the way), it may be worth noting that while pytz only provides IANA zones, dateutil.tz also provides other zones, such as zones parsed from GNU TZ strings and a wrapper around the Windows time zone interface. (This latter is useful, by the way, because of a bug in Microsoft CRT that makes it so that time.localtime will not reflect changes in system time zone without a new instance of Python - see #10634. dateutil.tz.tzwinlocal() uses the native Windows interface and is not subject to this bug, though dateutil.tz.tzlocal(), as a wrapper around time, is).
msg280397 - (view) Author: Yury Selivanov (yselivanov) * (Python committer) Date: 2016-11-09 14:36
Thanks, Paul. I'm adding Elvis to the nosy list, he's the main editor of What's New in 3.6.
msg280529 - (view) Author: Elvis Pranskevichus (Elvis.Pranskevichus) * (Python triager) Date: 2016-11-10 18:23
This should now be covered in #28635.
msg280530 - (view) Author: Yury Selivanov (yselivanov) * (Python committer) Date: 2016-11-10 18:23
Closing this one.
History
Date User Action Args
2016-11-10 18:23:58yselivanovsetstatus: open -> closed
type: enhancement
messages: + msg280530

resolution: fixed
stage: needs patch -> resolved
2016-11-10 18:23:05Elvis.Pranskevichussetmessages: + msg280529
2016-11-09 14:36:30yselivanovsetnosy: + yselivanov, Elvis.Pranskevichus
messages: + msg280397
2016-11-09 14:02:15p-gansslesetmessages: + msg280396
2016-11-08 17:14:53belopolskysetmessages: + msg280321
2016-11-08 17:10:46belopolskycreate