[Zope3-dev] DateTime and tzinfo signatures, etc.

Guido van Rossum guido@python.org
Mon, 18 Mar 2002 12:24:43 -0500


[Phillip]
> Let me suggest an API for the arithmetic, too, assuming immutable
> datetime objects.  Let the constructor signature be:
> 
>     dt = datetime(year,month,day,hour,minute,second,microsecond,tzinfo=None)
> 
>          OR
> 
>     dt = datetime(olddatetime, **keywordargs)
> 
> That is, you can construct a new datetime from an existing one, but modify 
> any set of fields using keyword arguments.

I'm not sure I like that API very much, but I can't explain why not.

> Thus, "calendar-based" 
> arithmetic is as simple as:
> 
>     dt = datetime(someDate, month=someDate.month+1)
> 
> I believe that just about all calendar-oriented arithmetic can be
> conducted in this way.  If we bring back int/float casting to UTC
> ticks, then absolute duration arithmetic is also possible.  (See
> below for a leap seconds caveat, however.)

This would require you to do something about normalization in the API.
E.g. April 31 would have to be either normalized to May 1 or truncated
to April 30.  Ditto for all other fields -- we could no longer require
that all arguments are in range (as the current constructor does).

All in all, I'd rather copy the approach of mxDateTime, which has a
separate "relative datetime" object that represents a tuple (years,
months, days, etc.) to be added.  E.g.

 dt = datetime(2002, 3, 31, 12, 0) # March 31, 2002, 12 noon
 r = relativedatetime(months=1) # not its real name :-)
 dt = dt+r # April 30, 2002, 12 noon

> Changing timezones when copying a datetime is a little tricky, since
> it requires conversion to ticks and back, unless either the origin
> or destination tzinfo is None, in which case no conversion is
> necessary and fields are copied as-is.

Since a relativedatetime object doesn't have a tz field, this issue
disappears.  If you want to do conversion between timezones, you must
use a different API.

I propose:

    tz.convert(dt)

where tz is a tzinfo object, and dt is either a (naive) datetime
object or a datetimetz object with some specific tzinfo object.  This
converts dt to a corresponding datetimetz object.  If dt has a tzinfo
object attached to it, the return represents the same time point (as
seen in UTC) translated to the timezone.  But if dt is a naive
datetime object or a datetimetz instance whose tzinfo is None, it
simply interprets dt in tz's local time.  Examples:

    # Suppose EST() represents the EST timezone, and UTC() represents UTC

    dt1 = datetimetz(2002, 3, 18, 12, 0, tz=EST()) # March 18, 2002, noon EST
    dt2 = UTC().convert(dt1)
    assert dt1 == dt2 # Same time point
    print dt2 # 2002-03-18 17:00:00Z

    dt1 = datetime(2002, 3, 18, 12, 0) # March 18, 2002, noon naive time
    dt2 = UTC().convert(dt1)
    assert dt1 != dt2
    print dt2 # 2002-03-18 12:00:00Z

If this is considered too strange (the different interpretation for
naive time), then maybe there should be two different methods,
e.g. tz.convert(dt) which requires the presence of a tz object on dt,
and tz.aslocal(dt) which requires dt to be naive time.

> For semantic clarity, it might be best to allow only the tzinfo *or*
> the calendar fields to be changed in a single operation, by the way,
> since otherwise it's not clear whether the calendar fields should be
> changed before or after zone conversion.

Another reason to pick a different API.

> Alternately, the constructor could be:
> 
> dt = datetime(tzinfo, year, month, day, hour, minute, second, microsecond)
> 
> Where tzinfo could be either a tzinfo object, an existing datetime,
> or None.  If tzinfo is a tzinfo object or None, it is used to create
> a new datetime.  If it's an existing datetime, then it's copied and
> any keyword arguments supplied are used to modify the fields.
> Downside to this approach: the signature isn't "sideways compatible"
> with other Python datetime libraries such as mxDateTime.

-1.

But maybe the various conversions should be class methods of the
datetime and datetimetz classes rather than methods of the tzinfo
object, so that they can be used for other subclasses.

> >I like this idea.  I almost like it enough to make the tzinfo object
> >part of the datetime base class, rather than relegating it to a
> >subclass.
> 
> Ty and I are willing to supply code to read tzcode "zoneinfo"
> databases.  We propose that there be an interface for a "zoneinfo
> database" which takes tzcode time zone names (e.g. "US/Indiana") and

No, *not* US/Indiana!!!  That's not a single zone!  Please!!!

> returns a tzinfo object.  And, that the module supplying timezone
> information offer both a default zoneinfo database object, and an
> API to create a zoneinfo database object by specifying the path to
> the zoneinfo files.  In this way, one could use either the local
> platform zoneinfo database, or a standard Python one for maximal
> cross-platform compatibility.

I'd be happy to make sure that you can provide your own zone
database.  I'm reluctant to provide a zone database with Python.  For
Zope, I'd propose to continue to provide the zone database that it
currently ships (DateTimeZone.py).

> Because the tzcode format can support leap seconds if supplied with
> the appropriate database, we suggest using UTC seconds+microseconds
> as an intermediate format during timezone conversions.  If tzinfo
> objects (and therefore datetime objects) support converting to/from
> UTC seconds+microseconds, then one can also do "absolute duration"
> arithmetic by manipulating the dates in this form.

Python's datetime objects will not support leap seconds.  A naive
datetime object that is understood to mean UTC should be just fine as
an intermediate representation.

> Note that converting a date between a tzinfo object which doesn't
> support leap seconds and one which does will result in an
> inaccuracy, so it may be desirable to include a leap second flag on
> tzinfo objects so that an invalid conversion can be caught and a
> TypeError thrown.

Python's datetime objects will not support leap seconds in any way,
shape or form.  A tzinfo object that does support leap seconds is on
its own, but I don't see the point since Python will never represent a
time as a number of seconds since some epoch.  (If you want to get a
POSIX time_t value, you'll have to convert first to local time, then
to a struct tm, and then use mktime().)

> I think that this approach, all in all, can be made to support the
> vast majority of use cases that have been so far expressed which
> deal with absolute moments in time.  I think also that most of the
> use cases that deal with appointments, timespans, recurring events,
> standalone date or standalone time, etc., can be built using the
> basic moment-in-time datetime objects as building blocks.

I think that my approach can also support all use cases.  If you
disagree, please add your use case to the Wiki:

http://www.zope.org/Members/fdrake/DateTimeWiki/UseCases

--Guido van Rossum (home page: http://www.python.org/~guido/)