[Datetime-SIG] PEP -0500: A protocol for delegating datetime methods

Chris Barker chris.barker at noaa.gov
Tue Aug 11 03:48:47 CEST 2015


Thanks for writing this up.

It does look like this is a way forward to more complete datetime use cases.

However, IIUC, this approach essentially delegates all logic to the
tzinfo objects, leaving datetime as nothing more than a container for,
well, the datetime -- I.e not really much more than a namedtuple
holding year, month, day, hour, min, sec, usec.

This may well be the best way to move forward while maintaining
backward compatibility, but it sure seems like a confusing mixture of
functionality, and, while it can support both different calendars and
different calendar math, it seems like it will get ugly to mix and
match. ( though maybe it can be done with multiple inheritance).

However, from an object oriented design perspective, I see:

A timezone is a particular definition-- it is essentially a set of
(fairly arbitrary, political, and changing) rules for defining an
offset to UTC for various times and places. That's it, and that's what
the current tzinfo captures.

2) timedelta is a duration in time -- essentially microseconds encoded
in days + microseconds. A very simple class.

3) datetime is a representation of the Proleptic Gregorian calendar.
But as has been pointed out, it is essentially an encoding of
microseconds since year 1 of that calendar ( without leap seconds ).
It contains the logic for converting from the year, month, day
encoding to/from microseconds.

It also holds the logic for doing datetime arithmetic. As I understand it,
to do addition and subtraction, it convert to milliseconds from year 1,
does the math, then converts back.


If you want a new timezone, then a new tzinfo object makes sense.

But if you want a different calendar (french revolutionary, or maybe
gregorian with leap seconds...) then what you want is new datetime object
(maybe a datetime subclass), not a new tzinfo object.

If you want math to be handled differently, then you also want a new
datetime object. And, in fact, the current datetime object does "strict"
math (duration arithmetic) just fine -- what it doesn't do is handle the
time zones in a particularly useful way when doing math. (i.e is then does
Period arithmetic, but only in a very limited way.)

So is there any reason to delegate everything to the tzinfo object???

If one were to make a new datetime object to support leap seconds, or some
other calendar, wouldn't you be able to use it with the tzinfo objects
provided by pytz, etc?

So what do we want to be able to support?

1) "strict" or duration arithmetic on time zone aware datetimes:
  - so what this really needs is change in the datetime logic, it should:
     - convert to UTC
     - do the math
     - convert back to the desired timezone.
But we don't want to change current behavior, so we could either:
  - have an optional flag (strict=True) on the tzinfo object that datetime
would check, so it knows what kind of arithmetic you want. -- I don't think
the flag really belongs there, but since this is only an issue where tzinfo
objects are involved it makes some sense to put it there.

2) Full featured Period arithmetic:
   the current -- you can get period arithmetic, but only with units of
days, and only if the timezones are the same (I think...) -- is, shall we
say, not as useful as it could be. I don't need it personally, but I
understand that dateutils provides a lot of it. So we need to just make
sure we don't break datetutils with any of this -- and I can't see how
adding a "strict" flag to a tzinfo object would cause a problem.

3) Leap seconds:

OK - maybe no one will ever get around to doing this, but it seems adding
them to a new datetime object would be a fine way to do that -- and that
datetime object would still work with pytz, datetutils, etc....

4) Other Calendars: Julian, French Revolutionary, climate modeling 360 day,

These sure seem to belong in new datetime objects/subclasses.

So what am I missing???

(BTW, this is all assuming that the datetime/tzinfo protocol is adapted to
accommodate the ambiguous times in DST boundaries...)


On Sat, Aug 8, 2015 at 3:15 PM, Alexander Belopolsky <
alexander.belopolsky at gmail.com> wrote:

> PEP: 500
> Title: A protocol for delegating datetime methods to their
>        tzinfo implementations
> Version: $Revision$
> Last-Modified: $Date$
> Author: Alexander Belopolsky <alexander.belopolsky at gmail.com>
> Discussions-To: Datetime-SIG <datetime-sig at python.org>
> Status: Draft
> Type: Standards Track
> Content-Type: text/x-rst
> Requires: 495
> Created: 08-Aug-2015
> Abstract
> ========
> This PEP specifies a new protocol (PDDM - "A Protocol for Delegating
> Datetime Methods") that can be used by concrete implementations of the
> ``datetime.tzinfo`` interface to override aware datetime arithmetics,
> formatting and parsing.  We describe changes to the
> ``datetime.datetime`` class to support the new protocol and propose a
> new abstract class ``datetime.tzstrict`` that implements parts of this
> protocol necessary to make aware datetime instances to follow "strict"
> arithmetic rules.
> Rationale
> =========
> As of Python 3.5, aware datetime instances that share a ``tzinfo``
> object follow the rules of arithmetics that are induced by a simple
> bijection between (year, month, day, hour, minute, second,
> microsecond) 7-tuples and large integers.  In this arithmetics, the
> difference between YEAR-11-02T12:00 and YEAR-11-01T12:00 is always 24
> hours, even though in the US/Eastern timezone, for example, there are
> 25 hours between 2014-11-01T12:00 and 2014-11-02T12:00 because the
> local clocks were rolled back one hour at 2014-11-02T02:00,
> introducing an extra hour in the night between 2014-11-01 and
> 2014-11-02.
> Many business applications requre the use of Python's simplified view
> of local dates.  No self-respecting car rental company will charge its
> customers more for a week that straddles the end of DST than for any
> other week or require that they return the car an hour early.
> Therefore, changing the current rules for aware datetime arithmetics
> will not only create a backward compatibility nightmare, it will
> eliminate support for legitimate and common use cases.
> Since it is impossible to choose universal rules for local time
> arithmetics, we propose to delegate implementation of those rules to
> the classes that implement ``datetime.tzinfo`` interface.  With such
> delegation in place, users will be able to choose between different
> arithmetics by simply picking instances of different classes for the
> value of ``tzinfo``.
> Protocol
> ========
> Subtraction of datetime
> -----------------------
> A ``tzinfo`` subclass supporting the PDDM, may define a method called
> ``__datetime_diff__`` that should take two ``datetime.datetime``
> instances and return a ``datetime.timedelta`` instance representing
> the time elapced from the time represented by the first datetime
> instance to another.
> Addition
> --------
> A ``tzinfo`` subclass supporting the PDDM, may define a method called
> ``__datetime_add__`` that should take two arguments--a datetime and a
> timedelta instances--and return a datetime instance.
> Subtraction of timedelta
> ------------------------
> A ``tzinfo`` subclass supporting the PDDM, may define a method called
> ``__datetime_sub__`` that should take two arguments--a datetime and a
> timedelta instances--and return a datetime instance.
> Formatting
> ----------
> A ``tzinfo`` subclass supporting the PDDM, may define methods called
> ``__datetime_isoformat__`` and ``__datetime_strftime__``.
> The ``__datetime_isoformat__`` method should take a datetime instance
> and an optional separator and produce a string representation of the
> given datetime instance.
> The ``__datetime_strftime__`` method should take a datetime instance
> and a format string and produce a string representation of the given
> datetime instance formatted according to the given format.
> Parsing
> -------
> A ``tzinfo`` subclass supporting the PDDM, may define a class method
> called ``__datetime_strptime__`` and register the "canonical" names of
> the timezones that it implements with a registry. **TODO** Describe a
> registry.
> Changes to datetime methods
> ===========================
> Subtraction
> -----------
> ::
>    class datetime:
>        def __sub__(self, other):
>            if isinstance(other, datetime):
>                try:
>                    self_diff = self.tzinfo.__datetime_diff__
>                except AttributeError:
>                    self_diff = None
>                try:
>                    other_diff = self.tzinfo.__datetime_diff__
>                except AttributeError:
>                    other_diff = None
>                if self_diff is not None:
>                    if self_diff is not other_diff and
> self_diff.__func__ is not other_diff.__func__:
>                        raise ValueError("Cannot find difference of two
> datetimes with "
>                                         "different
> tzinfo.__datetime_diff__ implementations.")
>                    return self_diff(self, other)
>            elif isinstance(other, timedelta):
>                try:
>                    sub = self.tzinfo.__datetime_sub__
>                except AttributeError:
>                    pass
>                else:
>                    return sub(self, other)
>                return self + -other
>            else:
>                return NotImplemented
>            # current implementation
> Addition
> --------
> Addition of a timedelta to a datetime instance will be delegated to the
> ``self.tzinfo.__datetime_add__`` method whenever it is defined.
> Strict arithmetics
> ==================
> A new abstract subclass of ``datetime.tzinfo`` class called
> ``datetime.tzstrict``
> will be added to the ``datetime`` module.  This subclass will not
> implement the
> ``utcoffset()``, ``tzname()`` or ``dst()`` methods, but will implement
> some of the
> methods of the PDDM.
> The PDDM methods implemented by ``tzstrict`` will be equivalent to the
> following::
>   class tzstrict(tzinfo):
>       def __datetime_diff__(self, dt1, dt2):
>           utc_dt1 = dt1.astimezone(timezone.utc)
>           utc_dt2 = dt2.astimezone(timezone.utc)
>           return utc_dt2 - utc_dt1
>       def __datetime_add__(self, dt, delta):
>           utc_dt = dt.astimezone(timezone.utc)
>           return (utc_dt + delta).astimezone(self)
>       def __datetime_sub__(self, dt, delta):
>           utc_dt = dt.astimezone(timezone.utc)
>           return (utc_dt - delta).astimezone(self)
> Parsing and formatting
> ----------------------
> Datetime methods ``strftime`` and ``isoformat`` will delegate to the
> namesake
> methods of their ``tzinfo`` members whenever those methods are defined.
> When the ``datetime.strptime`` method is given a format string that
> contains a ``%Z`` instruction, it will lookup the ``tzinfo``
> implementation in the registry by the given timezone name and call its
> ``__datetime_strptime__`` method.
> Applications
> ============
> This PEP will enable third party implementation of many different
> timekeeping schemes including:
> * Julian / Microsoft Excel calendar.
> * "Right" timezones with the leap second support.
> * French revolutionary calendar (with a lot of work).
> Copyright
> =========
> This document has been placed in the public domain.
> _______________________________________________
> Datetime-SIG mailing list
> Datetime-SIG at python.org
> https://mail.python.org/mailman/listinfo/datetime-sig
> The PSF Code of Conduct applies to this mailing list:
> https://www.python.org/psf/codeofconduct/


Christopher Barker, Ph.D.

Emergency Response Division
NOAA/NOS/OR&R            (206) 526-6959   voice
7600 Sand Point Way NE   (206) 526-6329   fax
Seattle, WA  98115       (206) 526-6317   main reception

Chris.Barker at noaa.gov
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://mail.python.org/pipermail/datetime-sig/attachments/20150810/3208665f/attachment-0001.html>

More information about the Datetime-SIG mailing list