classification
Title: datetimetz constructors behave counterintuitively (2.3a1)
Type: Stage:
Components: Library (Lib) Versions: Python 2.3
process
Status: closed Resolution: fixed
Dependencies: Superseder:
Assigned To: tim.peters Nosy List: gvanrossum, pje, tim.peters
Priority: normal Keywords:

Created on 2003-01-02 00:06 by pje, last changed 2003-01-23 21:50 by tim.peters. This issue is now closed.

Messages (6)
msg13785 - (view) Author: PJ Eby (pje) * (Python committer) Date: 2003-01-02 00:06
datetimetz.fromtimestamp(timestamp,tzinfo) creates a
datetime which has the localtime() value of timestamp,
with the tzinfo attached as its timezone.  But this
produces a counterintuitive result.  Instead of getting
the UTC time represented by timestamp, converted to the
tzinfo timezone, one only ends up with a correct value
if tzinfo is a perfect match for whatever 'localtime'
is.  This seems useless as well as counterintuitive,
since one must therefore do this:

datetimetz.fromtimestamp(stamp,localtime_tz).astimezone(tzinfo)

to get what's desired.  Or, one can do:

datetimetz.utcfromtimestamp(stamp).replace(tzinfo=utc).astimezone(tzinfo)

Both of these seem unreasonably complex ways of doing
something simple: render this UTC timestamp as a
datetime in a specified timezone.

Similarly, I would expect datetimetz.now(tzinfo) to
return the current time in the zone specified by
tzinfo, but again this is not what it does.  Similarly,
you must do:

datetimetz.now(localtime_tz).astimezone(tzinfo)

or:

datetimetz.utcnow().replace(tzinfo=utc).astimezone(tzinfo)

to get the current time in the zone specified by
tzinfo. Given that these constructors *know* what the
UTC time is, why don't they behave more meaningfully? 
It seems to me that finding the current time in a
timezone, or rendering a timestamp as it would be seen
in a timezone, are common enough use cases, but I
cannot think of any use cases for what the constructors
actually do instead.
msg13786 - (view) Author: Tim Peters (tim.peters) * (Python committer) Date: 2003-01-02 01:14
Logged In: YES 
user_id=31435

I always figured the use case for fromtimestamp(stamp, tz) 
was indeed so the user had a dead-easy way to attach 
their own local time tzinfo class to a timestamp, and that's 
all.  I would find the behavior you describe counterintuitive 
instead:  if I want a conversion, I'd rather ask for one 
explicitly (via astimezone()).
msg13787 - (view) Author: PJ Eby (pje) * (Python committer) Date: 2003-01-02 03:40
Logged In: YES 
user_id=56214

But in your use case, if the timezone they attach is in fact
equivalent to localtime, then the conversion would be a
no-op anyway, right?  So how would a conversion in the
general case be wrong?

At the very least, this is a documentation bug.  If these
constructors only produce meaningful results for a tzinfo
that's exactly equivalent to the system's localtime(), then
the docs should say so.  Otherwise, someone can write code
that appears to work, for example, on their own machine, and
then fails when run on a machine with a different concept of
localtime().

Now, if you can actually come up with a sensible use case
for *wrongly* translating a UTC-based timestamp to a
particular timezone, with the degree of wrongness depending
on the difference between localtime() and the specified
timezone, that might justify the constructor behavior.  :) 
As it is, I find it hard to believe that whatever you come
up with will be a more common need than rendering a UTC
timestamp as a datetime value in a specified timezone.
msg13788 - (view) Author: Tim Peters (tim.peters) * (Python committer) Date: 2003-01-02 04:47
Logged In: YES 
user_id=31435

I think Guido has more energy for this than I have (I'm 
pretty much burned out on this module), so assigned it to 
him.

At the time fromtimestamp() was added, there wasn't any 
reasonable way to simply attach a tzinfo object to a 
datetimetz.  About the best you could do is

new = datetimetz(old.year, old.month, old.day, old.hour, 
old.minute, old.second, old.microsecond, tzinfo)

So the use case of *simply* getting a local datetimetz out 
of a timestamp was quite a pain.  
datetimetz.fromtimestamp grew an optional tzinfo arg to 
ease that pain.

There was no notion of timezone conversion at that time 
either, so of course none of the methods were intended to 
do any sort of conversion.  Note that the primary 
constructor-- datetimetz --also takes all of its arguments as 
being in local time.  It would strike me as inconsistent if 
fromtimestamp() didn't also take its argument as being in 
local time.

Things changed since then, and I at least agree the docs 
should be clarified.  But rather than make the constructors 
even more complicated, I'd rather drop the optional tzinfo 
arguments from fromtimestamp() and now().  There are at 
least two easy ways to attach a tzinfo tz to a datetimetz 
now:

new = old.astimezone(tz)  # no conversion if old was naive
new = old.replace(tzinfo=tz) # no conversion ever

and there's also an explict time zone conversion method.
msg13789 - (view) Author: Guido van Rossum (gvanrossum) * (Python committer) Date: 2003-01-02 16:46
Logged In: YES 
user_id=6380

I think that Phillip's proposal is reasonable: when you give
a tzinfo argument to datetimetz.fromtimestamp(), it should
*convert* to the given tzinfo. I think the proper semantics
are specified by this expression:

datetimetz.utcfromtimestamp(stamp).replace(tzinfo=utc).astimezone(tzinfo)

The rule for now() follows from this.

BTW, I think it was a mistake to let astimezone() convert
from/to naive times. It should only be used for conversion
between tz-aware times; conversion from/to naive time should
be done using dt.replace(tzinfo=...).
msg13790 - (view) Author: Tim Peters (tim.peters) * (Python committer) Date: 2003-01-23 21:50
Logged In: YES 
user_id=31435

now() and fromtimestamp() now do "the right thing" with their 
optional tzinfo argument.  The argument name was changed 
to "tz", BTW, as "tzinfo" had enough meanings already.  
Appropriate changes were checked into the Python 
datetimemodule.c and Zope3 _datetime.py implementations, 
+ their test suites and LaTeX docs.

Guido, your complaint about astimezone() was incidentally 
fixed in checkins prior to this -- astimezone() no longer 
supports converting between naive and aware datetimes, and 
also complains if the tzinfo object's utcoffset() or dst() 
methods return None.
History
Date User Action Args
2003-01-02 00:06:49pjecreate