diff -r 535f88ad80d8 Doc/includes/tzinfo-examples.py --- a/Doc/includes/tzinfo-examples.py Mon Aug 08 14:07:05 2016 +0300 +++ b/Doc/includes/tzinfo-examples.py Mon Aug 08 14:59:23 2016 -0400 @@ -2,45 +2,12 @@ ZERO = timedelta(0) HOUR = timedelta(hours=1) - -# A UTC class. - -class UTC(tzinfo): - """UTC""" - - def utcoffset(self, dt): - return ZERO - - def tzname(self, dt): - return "UTC" - - def dst(self, dt): - return ZERO - -utc = UTC() - -# A class building tzinfo objects for fixed-offset time zones. -# Note that FixedOffset(0, "UTC") is a different way to build a -# UTC tzinfo object. - -class FixedOffset(tzinfo): - """Fixed offset in minutes east from UTC.""" - - def __init__(self, offset, name): - self.__offset = timedelta(minutes=offset) - self.__name = name - - def utcoffset(self, dt): - return self.__offset - - def tzname(self, dt): - return self.__name - - def dst(self, dt): - return ZERO +SECOND = timedelta(seconds=1) # A class capturing the platform's idea of local time. - +# (May result in wrong values on historical times in +# timezones where UTC offset and/or the DST rules had +# changed in the past.) import time as _time STDOFFSET = timedelta(seconds = -_time.timezone) @@ -53,6 +20,16 @@ class LocalTimezone(tzinfo): + def fromutc(self, dt): + assert dt.tzinfo is self + stamp = (dt - datetime(1970, 1, 1, tzinfo=self)) // SECOND + args = _time.localtime(stamp)[:6] + dst_diff = DSTDIFF // SECOND + # Detect fold + fold = (args == _time.localtime(stamp - dst_diff)) + return datetime(*args, microsecond=dt.microsecond, + tzinfo=self, fold=fold) + def utcoffset(self, dt): if self._isdst(dt): return DSTOFFSET @@ -99,20 +76,37 @@ # In the US, since 2007, DST starts at 2am (standard time) on the second # Sunday in March, which is the first Sunday on or after Mar 8. DSTSTART_2007 = datetime(1, 3, 8, 2) -# and ends at 2am (DST time; 1am standard time) on the first Sunday of Nov. -DSTEND_2007 = datetime(1, 11, 1, 1) +# and ends at 2am (DST time) on the first Sunday of Nov. +DSTEND_2007 = datetime(1, 11, 1, 2) # From 1987 to 2006, DST used to start at 2am (standard time) on the first -# Sunday in April and to end at 2am (DST time; 1am standard time) on the last +# Sunday in April and to end at 2am (DST time) on the last # Sunday of October, which is the first Sunday on or after Oct 25. DSTSTART_1987_2006 = datetime(1, 4, 1, 2) -DSTEND_1987_2006 = datetime(1, 10, 25, 1) +DSTEND_1987_2006 = datetime(1, 10, 25, 2) # From 1967 to 1986, DST used to start at 2am (standard time) on the last -# Sunday in April (the one on or after April 24) and to end at 2am (DST time; -# 1am standard time) on the last Sunday of October, which is the first Sunday +# Sunday in April (the one on or after April 24) and to end at 2am (DST time) +# on the last Sunday of October, which is the first Sunday # on or after Oct 25. DSTSTART_1967_1986 = datetime(1, 4, 24, 2) DSTEND_1967_1986 = DSTEND_1987_2006 +def us_dst_range(year): + # Find start and end times for US DST. For years before 1967, return + # ZERO for no DST. + if 2006 < year: + dststart, dstend = DSTSTART_2007, DSTEND_2007 + elif 1986 < year < 2007: + dststart, dstend = DSTSTART_1987_2006, DSTEND_1987_2006 + elif 1966 < year < 1987: + dststart, dstend = DSTSTART_1967_1986, DSTEND_1967_1986 + else: + return ZERO + + start = first_sunday_on_or_after(dststart.replace(year=year)) + end = first_sunday_on_or_after(dstend.replace(year=year)) + return start, end + + class USTimeZone(tzinfo): def __init__(self, hours, reprname, stdname, dstname): @@ -141,27 +135,38 @@ # implementation) passes a datetime with dt.tzinfo is self. return ZERO assert dt.tzinfo is self - - # Find start and end times for US DST. For years before 1967, return - # ZERO for no DST. - if 2006 < dt.year: - dststart, dstend = DSTSTART_2007, DSTEND_2007 - elif 1986 < dt.year < 2007: - dststart, dstend = DSTSTART_1987_2006, DSTEND_1987_2006 - elif 1966 < dt.year < 1987: - dststart, dstend = DSTSTART_1967_1986, DSTEND_1967_1986 + start, end = us_dst_range(dt.year) + # Can't compare naive to aware objects, so strip the timezone from + # dt first. + dt = dt.replace(tzinfo=None) + if start + HOUR <= dt < end: + # DST is in effect. + return HOUR + elif end <= dt < end + HOUR: + # Fold (an ambiguous hour): use dt.fold to disambiguate. + return ZERO if dt.fold else HOUR + elif start <= dt < start + HOUR: + # Gap (a non-existent hour): reverse the fold rule. + return HOUR if dt.fold else ZERO else: + # DST is off. return ZERO - start = first_sunday_on_or_after(dststart.replace(year=dt.year)) - end = first_sunday_on_or_after(dstend.replace(year=dt.year)) - - # Can't compare naive to aware objects, so strip the timezone from - # dt first. - if start <= dt.replace(tzinfo=None) < end: - return HOUR - else: - return ZERO + def fromutc(self, dt): + assert dt.tzinfo is self + start, end = us_dst_range(dt.year) + start = start.replace(tzinfo=self) + end = end.replace(tzinfo=self) + std_time = dt + self.stdoffset + if std_time < start or std_time > end: + # Standard time + return std_time + if start <= std_time < end - HOUR: + # Daylight saving time + return std_time + HOUR + if end - HOUR <= std_time < end: + # Repeated hour + return (std_time + HOUR).replace(fold=1) Eastern = USTimeZone(-5, "Eastern", "EST", "EDT") Central = USTimeZone(-6, "Central", "CST", "CDT") diff -r 535f88ad80d8 Doc/library/datetime.rst --- a/Doc/library/datetime.rst Mon Aug 08 14:07:05 2016 +0300 +++ b/Doc/library/datetime.rst Mon Aug 08 14:59:23 2016 -0400 @@ -522,7 +522,7 @@ Instance methods: -.. method:: date.replace(year, month, day) +.. method:: date.replace(year=self.year, month=self.month, day=self.day) Return a date with the same value, except for those parameters given new values by whichever keyword arguments are specified. For example, if ``d == @@ -683,22 +683,26 @@ Constructor: -.. class:: datetime(year, month, day, hour=0, minute=0, second=0, microsecond=0, tzinfo=None) +.. class:: datetime(year, month, day, hour=0, minute=0, second=0, microsecond=0, tzinfo=None, *, fold=0) The year, month and day arguments are required. *tzinfo* may be ``None``, or an instance of a :class:`tzinfo` subclass. The remaining arguments may be integers, in the following ranges: - * ``MINYEAR <= year <= MAXYEAR`` - * ``1 <= month <= 12`` - * ``1 <= day <= number of days in the given month and year`` - * ``0 <= hour < 24`` - * ``0 <= minute < 60`` - * ``0 <= second < 60`` - * ``0 <= microsecond < 1000000`` + * ``MINYEAR <= year <= MAXYEAR``, + * ``1 <= month <= 12``, + * ``1 <= day <= number of days in the given month and year``, + * ``0 <= hour < 24``, + * ``0 <= minute < 60``, + * ``0 <= second < 60``, + * ``0 <= microsecond < 1000000``, + * ``fold in [0, 1]``. If an argument outside those ranges is given, :exc:`ValueError` is raised. + .. versionadded:: 3.6 + Added the ``fold`` argument. + Other constructors, all class methods: .. classmethod:: datetime.today() @@ -758,6 +762,8 @@ instead of :exc:`ValueError` on :c:func:`localtime` or :c:func:`gmtime` failure. + .. versionchanged:: 3.6 + :meth:`fromtimestamp` may return instances with :attr:`.fold` set to 1. .. classmethod:: datetime.utcfromtimestamp(timestamp) @@ -794,7 +800,7 @@ microsecond of the result are all 0, and :attr:`.tzinfo` is ``None``. -.. classmethod:: datetime.combine(date, time[, tzinfo]) +.. classmethod:: datetime.combine(date, time, tzinfo=self.tzinfo) Return a new :class:`.datetime` object whose date components are equal to the given :class:`date` object's, and whose time components @@ -886,6 +892,16 @@ or ``None`` if none was passed. +.. attribute:: datetime.fold + + In ``[0, 1]``. Used to disambiguate wall times during a repeated interval. (A + repeated interval occurs when clocks are rolled back at the end of daylight saving + time or when the UTC offset for the current zone is decreased for political reasons.) + The value 0 (1) represents the earlier (later) of the two moments with the same wall + time representation. + + .. versionadded:: 3.6 + Supported operations: +---------------------------------------+--------------------------------+ @@ -984,13 +1000,18 @@ tzinfo attributes. See also method :meth:`time`. -.. method:: datetime.replace([year[, month[, day[, hour[, minute[, second[, microsecond[, tzinfo]]]]]]]]) +.. method:: datetime.replace(year=self.year, month=self.month, day=self.day, \ + hour=self.hour, minute=self.minute, second=self.second, microsecond=self.microsecond, \ + tzinfo=self.tzinfo, * fold=0) Return a datetime with the same attributes, except for those attributes given new values by whichever keyword arguments are specified. Note that ``tzinfo=None`` can be specified to create a naive datetime from an aware datetime with no conversion of date and time data. + .. versionadded:: 3.6 + Added the ``fold`` argument. + .. method:: datetime.astimezone(tz=None) @@ -999,23 +1020,20 @@ *self*, but in *tz*'s local time. If provided, *tz* must be an instance of a :class:`tzinfo` subclass, and its - :meth:`utcoffset` and :meth:`dst` methods must not return ``None``. *self* must - be aware (``self.tzinfo`` must not be ``None``, and ``self.utcoffset()`` must - not return ``None``). + :meth:`utcoffset` and :meth:`dst` methods must not return ``None``. If *self* + is naive (``self.tzinfo is None``), it is presumed to represent time in the + system timezone. If called without arguments (or with ``tz=None``) the system local - timezone is assumed. The ``.tzinfo`` attribute of the converted + timezone is assumed for the target timezone. The ``.tzinfo`` attribute of the converted datetime instance will be set to an instance of :class:`timezone` with the zone name and offset obtained from the OS. If ``self.tzinfo`` is *tz*, ``self.astimezone(tz)`` is equal to *self*: no adjustment of date or time data is performed. Else the result is local - time in time zone *tz*, representing the same UTC time as *self*: after - ``astz = dt.astimezone(tz)``, ``astz - astz.utcoffset()`` will usually have - the same date and time data as ``dt - dt.utcoffset()``. The discussion - of class :class:`tzinfo` explains the cases at Daylight Saving Time transition - boundaries where this cannot be achieved (an issue only if *tz* models both - standard and daylight time). + time in the timezone *tz*, representing the same UTC time as *self*: after + ``astz = dt.astimezone(tz)``, ``astz - astz.utcoffset()`` will have + the same date and time data as ``dt - dt.utcoffset()``. If you merely want to attach a time zone object *tz* to a datetime *dt* without adjustment of date and time data, use ``dt.replace(tzinfo=tz)``. If you @@ -1037,6 +1055,10 @@ .. versionchanged:: 3.3 *tz* now can be omitted. + .. versionchanged:: 3.6 + The :meth:`astimezone` method can now be called on naive instances that + are presumed to represent system local time. + .. method:: datetime.utcoffset() @@ -1113,6 +1135,10 @@ .. versionadded:: 3.3 + .. versionchanged:: 3.6 + The :meth:`timestamp` method uses the :attr:`.fold` attribute to + disambiguate the times during a repeated interval. + .. note:: There is no method to obtain the POSIX timestamp directly from a @@ -1342,16 +1368,17 @@ A time object represents a (local) time of day, independent of any particular day, and subject to adjustment via a :class:`tzinfo` object. -.. class:: time(hour=0, minute=0, second=0, microsecond=0, tzinfo=None) +.. class:: time(hour=0, minute=0, second=0, microsecond=0, tzinfo=None, *, fold=0) All arguments are optional. *tzinfo* may be ``None``, or an instance of a :class:`tzinfo` subclass. The remaining arguments may be integers, in the following ranges: - * ``0 <= hour < 24`` - * ``0 <= minute < 60`` - * ``0 <= second < 60`` - * ``0 <= microsecond < 1000000``. + * ``0 <= hour < 24``, + * ``0 <= minute < 60``, + * ``0 <= second < 60``, + * ``0 <= microsecond < 1000000``, + * ``fold in [0, 1]``. If an argument outside those ranges is given, :exc:`ValueError` is raised. All default to ``0`` except *tzinfo*, which defaults to :const:`None`. @@ -1404,6 +1431,17 @@ ``None`` if none was passed. +.. attribute:: time.fold + + In ``[0, 1]``. Used to disambiguate wall times during a repeated interval. (A + repeated interval occurs when clocks are rolled back at the end of daylight saving + time or when the UTC offset for the current zone is decreased for political reasons.) + The value 0 (1) represents the earlier (later) of the two moments with the same wall + time representation. + + .. versionadded:: 3.6 + + Supported operations: * comparison of :class:`.time` to :class:`.time`, where *a* is considered less @@ -1439,13 +1477,17 @@ Instance methods: -.. method:: time.replace([hour[, minute[, second[, microsecond[, tzinfo]]]]]) +.. method:: time.replace(hour=self.hour, minute=self.minute, second=self.second, \ + microsecond=self.microsecond, tzinfo=self.tzinfo, * fold=0) Return a :class:`.time` with the same value, except for those attributes given new values by whichever keyword arguments are specified. Note that ``tzinfo=None`` can be specified to create a naive :class:`.time` from an aware :class:`.time`, without conversion of the time data. + .. versionadded:: 3.6 + Added the ``fold`` argument. + .. method:: time.isoformat(timespec='auto') @@ -1754,9 +1796,7 @@ When DST starts (the "start" line), the local wall clock leaps from 1:59 to 3:00. A wall time of the form 2:MM doesn't really make sense on that day, so ``astimezone(Eastern)`` won't deliver a result with ``hour == 2`` on the day DST -begins. In order for :meth:`astimezone` to make this guarantee, the -:meth:`tzinfo.dst` method must consider times in the "missing hour" (2:MM for -Eastern) to be in daylight time. +begins. When DST ends (the "end" line), there's a potentially worse problem: there's an hour that can't be spelled unambiguously in local wall time: the last hour of @@ -1765,28 +1805,28 @@ to 1:00 (standard time) again. Local times of the form 1:MM are ambiguous. :meth:`astimezone` mimics the local clock's behavior by mapping two adjacent UTC hours into the same local hour then. In the Eastern example, UTC times of the -form 5:MM and 6:MM both map to 1:MM when converted to Eastern. In order for -:meth:`astimezone` to make this guarantee, the :meth:`tzinfo.dst` method must -consider times in the "repeated hour" to be in standard time. This is easily -arranged, as in the example, by expressing DST switch times in the time zone's -standard local time. - -Applications that can't bear such ambiguities should avoid using hybrid +form 5:MM and 6:MM both map to 1:MM when converted to Eastern, but earlier times +have the :attr:`.fold` attribute set to 0 and the later times have it set to 1. Note +that the :class:`datetime` instances that differ only by the value of the +:attr:`.fold` attribute are considered equal in comparisons. + +Applications that can't bear wall-time ambiguities should explicitly check the +value of the :attr:`.fold` atribute or avoid using hybrid :class:`tzinfo` subclasses; there are no ambiguities when using :class:`timezone`, or any other fixed-offset :class:`tzinfo` subclass (such as a class representing only EST (fixed offset -5 hours), or only EDT (fixed offset -4 hours)). .. seealso:: - `pytz `_ + `datetuil.tz `_ The standard library has :class:`timezone` class for handling arbitrary fixed offsets from UTC and :attr:`timezone.utc` as UTC timezone instance. - *pytz* library brings the *IANA timezone database* (also known as the + *datetuil.tz* library brings the *IANA timezone database* (also known as the Olson database) to Python and its usage is recommended. `IANA timezone database `_ - The Time Zone Database (often called tz or zoneinfo) contains code and + The Time Zone Database (often called tz, tzdata or zoneinfo) contains code and data that represent the history of local time for many representative locations around the globe. It is updated periodically to reflect changes made by political bodies to time zone boundaries, UTC offsets, and @@ -1806,7 +1846,7 @@ made to civil time. -.. class:: timezone(offset[, name]) +.. class:: timezone(offset, name=None) The *offset* argument must be specified as a :class:`timedelta` object representing the difference between the local time and UTC. It must