PEP: 500 Title: A protocol for delegating datetime methods to their
tzinfo implementations Version: $Revision$ Last-Modified: $Date$ Author:
Alexander Belopolsky <alexander.belopolsky@gmail.com>, Tim Peters
<tim.peters@gmail.com> Discussions-To: Datetime-SIG
<datetime-sig@python.org> Status: Rejected Type: Standards Track
Content-Type: text/x-rst Requires: 495 Created: 08-Aug-2015 Resolution:
https://mail.python.org/pipermail/datetime-sig/2015-August/000354.html

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 require 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 elapsed
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.
