From 2a44a8d3320ee7bcf5d718b5bdac550c6d34db4c Mon Sep 17 00:00:00 2001 From: Tim Peters Date: Thu, 23 Jan 2003 20:53:10 +0000 Subject: [PATCH] SF bug 660872: datetimetz constructors behave counterintuitively (2.3a1). This gives much the same treatment to datetime.fromtimestamp(stamp, tz) as the last batch of checkins gave to datetime.now(tz): do "the obvious" thing with the tz argument instead of a senseless thing. --- Doc/lib/libdatetime.tex | 29 +++++++++++++++++++---------- Lib/test/test_datetime.py | 16 +++++++++++++++- Misc/NEWS | 4 ++++ Modules/datetimemodule.c | 32 ++++++++++++++++++++------------ 4 files changed, 58 insertions(+), 23 deletions(-) diff --git a/Doc/lib/libdatetime.tex b/Doc/lib/libdatetime.tex index b890024bfa4..058d6d519ae 100644 --- a/Doc/lib/libdatetime.tex +++ b/Doc/lib/libdatetime.tex @@ -534,26 +534,35 @@ Other constructors, all class methods: \cfunction{gettimeofday()} function). Else \var{tz} must be an instance of a class \class{tzinfo} subclass, - and the current date and time are translated to \var{tz}'s time + and the current date and time are converted to \var{tz}'s time zone. In this case the result is equivalent to - \code{\var{tz}.fromutc(datetime.utcnow().replace(tzinfo=\var{tz})}. + \code{\var{tz}.fromutc(datetime.utcnow().replace(tzinfo=\var{tz}))}. See also \method{today()}, \method{utcnow()}. \end{methoddesc} \begin{methoddesc}{utcnow}{} Return the current UTC date and time, with \member{tzinfo} \code{None}. - This is like \method{now()}, but returns the current UTC date and time, + This is like \method{now()}, but returns the current UTC date and time, as a naive \class{datetime} object. See also \method{now()}. \end{methoddesc} -\begin{methoddesc}{fromtimestamp}{timestamp} - Return the local \class{datetime} corresponding to the \POSIX{} - timestamp, such as is returned by \function{time.time()}. This - may raise \exception{ValueError}, if the timestamp is out of the - range of values supported by the platform C - \cfunction{localtime()} function. It's common for this to be - restricted to years in 1970 through 2038. +\begin{methoddesc}{fromtimestamp}{timestamp, tz=None} + Return the local date and time corresponding to the \POSIX{} + timestamp, such as is returned by \function{time.time()}. + If optional argument \var{tz} is \code{None} or not specified, the + timestamp is converted to the platform's local date and time, and + the returned \class{datetime} object is naive. + + Else \var{tz} must be an instance of a class \class{tzinfo} subclass, + and the timestamp is converted to \var{tz}'s time zone. In this case + the result is equivalent to + \code{\var{tz}.fromutc(datetime.utcfromtimestamp(\var{timestamp}).replace(tzinfo=\var{tz}))}. + + \method{fromtimestamp()} may raise \exception{ValueError}, if the + timestamp is out of the range of values supported by the platform C + \cfunction{localtime()} or \cfunction(gmtime()} functions. It's common + for this to be restricted to years in 1970 through 2038. Note that on non-POSIX systems that include leap seconds in their notion of a timestamp, leap seconds are ignored by \method{fromtimestamp()}, and then it's possible to have two timestamps diff --git a/Lib/test/test_datetime.py b/Lib/test/test_datetime.py index 7d503e019e4..0b9597a07a8 100644 --- a/Lib/test/test_datetime.py +++ b/Lib/test/test_datetime.py @@ -2266,7 +2266,7 @@ class TestDateTimeTZ(TestDateTime, TZInfoBase): # Try with and without naming the keyword. off42 = FixedOffset(42, "42") another = meth(ts, off42) - again = meth(ts, tzinfo=off42) + again = meth(ts, tz=off42) self.failUnless(another.tzinfo is again.tzinfo) self.assertEqual(another.utcoffset(), timedelta(minutes=42)) # Bad argument with and w/o naming the keyword. @@ -2279,6 +2279,20 @@ class TestDateTimeTZ(TestDateTime, TZInfoBase): # Too few args. self.assertRaises(TypeError, meth) + # Try to make sure tz= actually does some conversion. + timestamp = 1000000000 # 2001-09-09 01:46:40 UTC, give or take + utc = FixedOffset(0, "utc", 0) + expected = datetime(2001, 9, 9, 1, 46, 40) + got = datetime.utcfromtimestamp(timestamp) + # We don't support leap seconds, but maybe the platfrom insists + # on using them, so don't demand exact equality). + self.failUnless(abs(got - expected) < timedelta(minutes=1)) + + est = FixedOffset(-5*60, "est", 0) + expected -= timedelta(hours=5) + got = datetime.fromtimestamp(timestamp, est).replace(tzinfo=None) + self.failUnless(abs(got - expected) < timedelta(minutes=1)) + def test_tzinfo_utcnow(self): meth = self.theclass.utcnow # Ensure it doesn't require tzinfo (i.e., that this doesn't blow up). diff --git a/Misc/NEWS b/Misc/NEWS index 39ebd9360e8..bfcddd93e59 100644 --- a/Misc/NEWS +++ b/Misc/NEWS @@ -93,6 +93,10 @@ Extension modules a tz argument, now() continues to return the current local date and time, as a naive datetime object. + datetime.fromtimestamp(): Like datetime.now() above, this had less than + useful behavior when the optional tinzo argument was specified. See + also SF bug report . + The constructors building a datetime from a timestamp could raise ValueError if the platform C localtime()/gmtime() inserted "leap seconds". Leap seconds are ignored now. On such platforms, it's diff --git a/Modules/datetimemodule.c b/Modules/datetimemodule.c index d81d5636f7d..aeccfda9422 100644 --- a/Modules/datetimemodule.c +++ b/Modules/datetimemodule.c @@ -3682,8 +3682,7 @@ datetime_now(PyObject *cls, PyObject *args, PyObject *kw) if (self != NULL && tzinfo != Py_None) { /* Convert UTC to tzinfo's zone. */ PyObject *temp = self; - self = PyObject_CallMethod(tzinfo, "fromutc", - "O", self); + self = PyObject_CallMethod(tzinfo, "fromutc", "O", self); Py_DECREF(temp); } return self; @@ -3702,17 +3701,26 @@ datetime_utcnow(PyObject *cls, PyObject *dummy) static PyObject * datetime_fromtimestamp(PyObject *cls, PyObject *args, PyObject *kw) { - PyObject *self = NULL; + PyObject *self; double timestamp; PyObject *tzinfo = Py_None; - static char *keywords[] = {"timestamp", "tzinfo", NULL}; + static char *keywords[] = {"timestamp", "tz", NULL}; - if (PyArg_ParseTupleAndKeywords(args, kw, "d|O:fromtimestamp", - keywords, ×tamp, &tzinfo)) { - if (check_tzinfo_subclass(tzinfo) < 0) - return NULL; - self = datetime_from_timestamp(cls, localtime, timestamp, - tzinfo); + if (! PyArg_ParseTupleAndKeywords(args, kw, "d|O:fromtimestamp", + keywords, ×tamp, &tzinfo)) + return NULL; + if (check_tzinfo_subclass(tzinfo) < 0) + return NULL; + + self = datetime_from_timestamp(cls, + tzinfo == Py_None ? localtime : gmtime, + timestamp, + tzinfo); + if (self != NULL && tzinfo != Py_None) { + /* Convert UTC to tzinfo's zone. */ + PyObject *temp = self; + self = PyObject_CallMethod(tzinfo, "fromutc", "O", self); + Py_DECREF(temp); } return self; } @@ -4404,7 +4412,7 @@ static PyMethodDef datetime_methods[] = { {"now", (PyCFunction)datetime_now, METH_KEYWORDS | METH_CLASS, - PyDoc_STR("[tzinfo] -> new datetime with local day and time.")}, + PyDoc_STR("[tz] -> new datetime with tz's locl day and time.")}, {"utcnow", (PyCFunction)datetime_utcnow, METH_NOARGS | METH_CLASS, @@ -4412,7 +4420,7 @@ static PyMethodDef datetime_methods[] = { {"fromtimestamp", (PyCFunction)datetime_fromtimestamp, METH_KEYWORDS | METH_CLASS, - PyDoc_STR("timestamp[, tzinfo] -> local time from POSIX timestamp.")}, + PyDoc_STR("timestamp[, tz] -> tz's local time from POSIX timestamp.")}, {"utcfromtimestamp", (PyCFunction)datetime_utcfromtimestamp, METH_VARARGS | METH_CLASS,