The tzinfo methods utcoffset() and dst() must return a timedelta object

(or None) now.  In 2.3a1 they could also return an int or long, but that
was an unhelpfully redundant leftover from an earlier version wherein
they couldn't return a timedelta.  TOOWTDI.
This commit is contained in:
Tim Peters 2003-01-02 21:28:08 +00:00
parent 4abd5f0fce
commit 397301eccb
5 changed files with 84 additions and 77 deletions

View File

@ -231,18 +231,19 @@ Supported operations:
{(1)} {(1)}
\lineiii{\var{t1} = \var{t2} // \var{i}} \lineiii{\var{t1} = \var{t2} // \var{i}}
{The floor is computed and the remainder (if any) is thrown away.} {The floor is computed and the remainder (if any) is thrown away.}
{(2)} {(3)}
\lineiii{+\var{t1}} \lineiii{+\var{t1}}
{Returns a \class{timedelta} object with the same value.} {Returns a \class{timedelta} object with the same value.}
{} {(2)}
\lineiii{-\var{t1}} \lineiii{-\var{t1}}
{equivalent to \class{timedelta}(-\var{t1.days}, -\var{t1.seconds}, {equivalent to \class{timedelta}(-\var{t1.days}, -\var{t1.seconds},
-\var{t1.microseconds}),and to \var{t1}* -1.} -\var{t1.microseconds}),and to \var{t1}* -1.}
{(1)(3)} {(1)(4)}
\lineiii{abs(\var{t})} \lineiii{abs(\var{t})}
{equivalent to +\var{t} when \code{t.days >= 0}, and to {equivalent to +\var{t} when \code{t.days >= 0}, and to
-\var{t} when \code{t.days < 0}.} -\var{t} when \code{t.days < 0}.
{(1)} overflow.}
{(2)}
\end{tableiii} \end{tableiii}
\noindent \noindent
Notes: Notes:
@ -252,9 +253,12 @@ Notes:
This is exact, but may overflow. This is exact, but may overflow.
\item[(2)] \item[(2)]
Division by 0 raises \exception{ZeroDivisionError}. This is exact, and cannot overflow.
\item[(3)] \item[(3)]
Division by 0 raises \exception{ZeroDivisionError}.
\item[(4)]
-\var{timedelta.max} is not representable as a \class{timedelta} object. -\var{timedelta.max} is not representable as a \class{timedelta} object.
\end{description} \end{description}
@ -883,11 +887,10 @@ implement all of them.
\class{tzinfo} object represents both time zone and DST adjustments, \class{tzinfo} object represents both time zone and DST adjustments,
\method{utcoffset()} should return their sum. If the UTC offset \method{utcoffset()} should return their sum. If the UTC offset
isn't known, return \code{None}. Else the value returned must be isn't known, return \code{None}. Else the value returned must be
an integer, in the range -1439 to 1439 inclusive (1440 = 24*60; a \class{timedelta} object specifying a whole number of minutes in the
the magnitude of the offset must be less than one day), or a range -1439 to 1439 inclusive (1440 = 24*60; the magnitude of the offset
\class{timedelta} object representing a whole number of minutes must be less than one day). Most implementations of
in the same range. Most implementations of \method{utcoffset()} \method{utcoffset()} will probably look like one of these two:
will probably look like one of these two:
\begin{verbatim} \begin{verbatim}
return CONSTANT # fixed-offset class return CONSTANT # fixed-offset class
@ -896,8 +899,6 @@ implement all of them.
If \method{utcoffset()} does not return \code{None}, If \method{utcoffset()} does not return \code{None},
\method{dst()} should not return \code{None} either. \method{dst()} should not return \code{None} either.
\end{methoddesc} \end{methoddesc}
@ -905,7 +906,7 @@ implement all of them.
Return the daylight savings time (DST) adjustment, in minutes east of Return the daylight savings time (DST) adjustment, in minutes east of
UTC, or \code{None} if DST information isn't known. Return \code{0} if UTC, or \code{None} if DST information isn't known. Return \code{0} if
DST is not in effect. DST is not in effect.
If DST is in effect, return the offset as an integer or If DST is in effect, return the offset as a
\class{timedelta} object (see \method{utcoffset()} for details). \class{timedelta} object (see \method{utcoffset()} for details).
Note that DST offset, if applicable, has Note that DST offset, if applicable, has
already been added to the UTC offset returned by already been added to the UTC offset returned by

View File

@ -1,16 +1,18 @@
from datetime import tzinfo from datetime import tzinfo, timedelta
ZERO = timedelta(0)
class UTC(tzinfo): class UTC(tzinfo):
"""UTC""" """UTC"""
def utcoffset(self, dt): def utcoffset(self, dt):
return 0 return ZERO
def tzname(self, dt): def tzname(self, dt):
return "UTC" return "UTC"
def dst(self, dt): def dst(self, dt):
return 0 return ZERO
class FixedOffset(tzinfo): class FixedOffset(tzinfo):
"""Fixed offset in minutes east from UTC.""" """Fixed offset in minutes east from UTC."""
@ -26,8 +28,7 @@ class FixedOffset(tzinfo):
return self.__name return self.__name
def dst(self, dt): def dst(self, dt):
# It depends on more than we know in an example. return ZERO
return None # Indicate we don't know
import time import time
@ -43,9 +44,9 @@ class LocalTime(tzinfo):
def utcoffset(self, dt): def utcoffset(self, dt):
if self._isdst(dt): if self._isdst(dt):
return -time.timezone/60 return timedelta(seconds=-time.timezone)
else: else:
return -time.altzone/60 return timedelta(seconds=-time.altzone)
def tzname(self, dt): def tzname(self, dt):
return time.tzname[self._isdst(dt)] return time.tzname[self._isdst(dt)]

View File

@ -26,6 +26,10 @@ class TestModule(unittest.TestCase):
class FixedOffset(tzinfo): class FixedOffset(tzinfo):
def __init__(self, offset, name, dstoffset=42): def __init__(self, offset, name, dstoffset=42):
if isinstance(offset, int):
offset = timedelta(minutes=offset)
if isinstance(dstoffset, int):
dstoffset = timedelta(minutes=dstoffset)
self.__offset = offset self.__offset = offset
self.__name = name self.__name = name
self.__dstoffset = dstoffset self.__dstoffset = dstoffset
@ -72,9 +76,9 @@ class TestTZInfo(unittest.TestCase):
fo = FixedOffset(3, "Three") fo = FixedOffset(3, "Three")
self.failUnless(isinstance(fo, tzinfo)) self.failUnless(isinstance(fo, tzinfo))
for dt in datetime.now(), None: for dt in datetime.now(), None:
self.assertEqual(fo.utcoffset(dt), 3) self.assertEqual(fo.utcoffset(dt), timedelta(minutes=3))
self.assertEqual(fo.tzname(dt), "Three") self.assertEqual(fo.tzname(dt), "Three")
self.assertEqual(fo.dst(dt), 42) self.assertEqual(fo.dst(dt), timedelta(minutes=42))
def test_pickling_base(self): def test_pickling_base(self):
import pickle, cPickle import pickle, cPickle
@ -94,10 +98,11 @@ class TestTZInfo(unittest.TestCase):
import pickle, cPickle import pickle, cPickle
# Make sure we can pickle/unpickle an instance of a subclass. # Make sure we can pickle/unpickle an instance of a subclass.
orig = PicklableFixedOffset(-300, 'cookie') offset = timedelta(minutes=-300)
orig = PicklableFixedOffset(offset, 'cookie')
self.failUnless(isinstance(orig, tzinfo)) self.failUnless(isinstance(orig, tzinfo))
self.failUnless(type(orig) is PicklableFixedOffset) self.failUnless(type(orig) is PicklableFixedOffset)
self.assertEqual(orig.utcoffset(None), -300) self.assertEqual(orig.utcoffset(None), offset)
self.assertEqual(orig.tzname(None), 'cookie') self.assertEqual(orig.tzname(None), 'cookie')
for pickler in pickle, cPickle: for pickler in pickle, cPickle:
for binary in 0, 1: for binary in 0, 1:
@ -105,7 +110,7 @@ class TestTZInfo(unittest.TestCase):
derived = pickler.loads(green) derived = pickler.loads(green)
self.failUnless(isinstance(derived, tzinfo)) self.failUnless(isinstance(derived, tzinfo))
self.failUnless(type(derived) is PicklableFixedOffset) self.failUnless(type(derived) is PicklableFixedOffset)
self.assertEqual(derived.utcoffset(None), -300) self.assertEqual(derived.utcoffset(None), offset)
self.assertEqual(derived.tzname(None), 'cookie') self.assertEqual(derived.tzname(None), 'cookie')
############################################################################# #############################################################################
@ -1562,7 +1567,8 @@ class TZInfoBase(unittest.TestCase):
# A datetimetz passes itself on, a timetz passes None. # A datetimetz passes itself on, a timetz passes None.
class introspective(tzinfo): class introspective(tzinfo):
def tzname(self, dt): return dt and "real" or "none" def tzname(self, dt): return dt and "real" or "none"
def utcoffset(self, dt): return dt and 42 or -42 def utcoffset(self, dt):
return timedelta(minutes = dt and 42 or -42)
dst = utcoffset dst = utcoffset
obj = cls(1, 2, 3, tzinfo=introspective()) obj = cls(1, 2, 3, tzinfo=introspective())
@ -1593,7 +1599,7 @@ class TZInfoBase(unittest.TestCase):
def test_utc_offset_out_of_bounds(self): def test_utc_offset_out_of_bounds(self):
class Edgy(tzinfo): class Edgy(tzinfo):
def __init__(self, offset): def __init__(self, offset):
self.offset = offset self.offset = timedelta(minutes=offset)
def utcoffset(self, dt): def utcoffset(self, dt):
return self.offset return self.offset
@ -1629,23 +1635,19 @@ class TZInfoBase(unittest.TestCase):
self.failUnless(t.dst() is None) self.failUnless(t.dst() is None)
self.failUnless(t.tzname() is None) self.failUnless(t.tzname() is None)
class C2(tzinfo):
def utcoffset(self, dt): return -1439
def dst(self, dt): return 1439
def tzname(self, dt): return "aname"
class C3(tzinfo): class C3(tzinfo):
def utcoffset(self, dt): return timedelta(minutes=-1439) def utcoffset(self, dt): return timedelta(minutes=-1439)
def dst(self, dt): return timedelta(minutes=1439) def dst(self, dt): return timedelta(minutes=1439)
def tzname(self, dt): return "aname" def tzname(self, dt): return "aname"
for t in cls(1, 1, 1, tzinfo=C2()), cls(1, 1, 1, tzinfo=C3()): t = cls(1, 1, 1, tzinfo=C3())
self.assertEqual(t.utcoffset(), timedelta(minutes=-1439)) self.assertEqual(t.utcoffset(), timedelta(minutes=-1439))
self.assertEqual(t.dst(), timedelta(minutes=1439)) self.assertEqual(t.dst(), timedelta(minutes=1439))
self.assertEqual(t.tzname(), "aname") self.assertEqual(t.tzname(), "aname")
# Wrong types. # Wrong types.
class C4(tzinfo): class C4(tzinfo):
def utcoffset(self, dt): return "aname" def utcoffset(self, dt): return "aname"
def dst(self, dt): return () def dst(self, dt): return 7
def tzname(self, dt): return 0 def tzname(self, dt): return 0
t = cls(1, 1, 1, tzinfo=C4()) t = cls(1, 1, 1, tzinfo=C4())
self.assertRaises(TypeError, t.utcoffset) self.assertRaises(TypeError, t.utcoffset)
@ -1653,15 +1655,12 @@ class TZInfoBase(unittest.TestCase):
self.assertRaises(TypeError, t.tzname) self.assertRaises(TypeError, t.tzname)
# Offset out of range. # Offset out of range.
class C5(tzinfo):
def utcoffset(self, dt): return -1440
def dst(self, dt): return 1440
class C6(tzinfo): class C6(tzinfo):
def utcoffset(self, dt): return timedelta(hours=-24) def utcoffset(self, dt): return timedelta(hours=-24)
def dst(self, dt): return timedelta(hours=24) def dst(self, dt): return timedelta(hours=24)
for t in cls(1, 1, 1, tzinfo=C5()), cls(1, 1, 1, tzinfo=C6()): t = cls(1, 1, 1, tzinfo=C6())
self.assertRaises(ValueError, t.utcoffset) self.assertRaises(ValueError, t.utcoffset)
self.assertRaises(ValueError, t.dst) self.assertRaises(ValueError, t.dst)
# Not a whole number of minutes. # Not a whole number of minutes.
class C7(tzinfo): class C7(tzinfo):
@ -1679,9 +1678,11 @@ class TZInfoBase(unittest.TestCase):
class OperandDependentOffset(tzinfo): class OperandDependentOffset(tzinfo):
def utcoffset(self, t): def utcoffset(self, t):
if t.minute < 10: if t.minute < 10:
return t.minute # d0 and d1 equal after adjustment # d0 and d1 equal after adjustment
return timedelta(minutes=t.minute)
else: else:
return 59 # d2 off in the weeds # d2 off in the weeds
return timedelta(minutes=59)
base = cls(8, 9, 10, tzinfo=OperandDependentOffset()) base = cls(8, 9, 10, tzinfo=OperandDependentOffset())
d0 = base.replace(minute=3) d0 = base.replace(minute=3)
@ -1937,9 +1938,9 @@ class TestTimeTZ(TestTime, TZInfoBase):
# In timetz w/ identical tzinfo objects, utcoffset is ignored. # In timetz w/ identical tzinfo objects, utcoffset is ignored.
class Varies(tzinfo): class Varies(tzinfo):
def __init__(self): def __init__(self):
self.offset = 22 self.offset = timedelta(minutes=22)
def utcoffset(self, t): def utcoffset(self, t):
self.offset += 1 self.offset += timedelta(minutes=1)
return self.offset return self.offset
v = Varies() v = Varies()
@ -2028,7 +2029,8 @@ class TestDateTimeTZ(TestDateTime, TZInfoBase):
# Try a bogus uctoffset. # Try a bogus uctoffset.
class Bogus(tzinfo): class Bogus(tzinfo):
def utcoffset(self, dt): return 1440 # out of bounds def utcoffset(self, dt):
return timedelta(minutes=1440) # out of bounds
t1 = self.theclass(2, 2, 2, tzinfo=Bogus()) t1 = self.theclass(2, 2, 2, tzinfo=Bogus())
t2 = self.theclass(2, 2, 2, tzinfo=FixedOffset(0, "")) t2 = self.theclass(2, 2, 2, tzinfo=FixedOffset(0, ""))
self.assertRaises(ValueError, lambda: t1 == t2) self.assertRaises(ValueError, lambda: t1 == t2)
@ -2259,6 +2261,8 @@ class TestDateTimeTZ(TestDateTime, TZInfoBase):
# DST flag. # DST flag.
class DST(tzinfo): class DST(tzinfo):
def __init__(self, dstvalue): def __init__(self, dstvalue):
if isinstance(dstvalue, int):
dstvalue = timedelta(minutes=dstvalue)
self.dstvalue = dstvalue self.dstvalue = dstvalue
def dst(self, dt): def dst(self, dt):
return self.dstvalue return self.dstvalue
@ -2291,6 +2295,8 @@ class TestDateTimeTZ(TestDateTime, TZInfoBase):
def test_utctimetuple(self): def test_utctimetuple(self):
class DST(tzinfo): class DST(tzinfo):
def __init__(self, dstvalue): def __init__(self, dstvalue):
if isinstance(dstvalue, int):
dstvalue = timedelta(minutes=dstvalue)
self.dstvalue = dstvalue self.dstvalue = dstvalue
def dst(self, dt): def dst(self, dt):
return self.dstvalue return self.dstvalue
@ -2303,7 +2309,7 @@ class TestDateTimeTZ(TestDateTime, TZInfoBase):
class UOFS(DST): class UOFS(DST):
def __init__(self, uofs, dofs=None): def __init__(self, uofs, dofs=None):
DST.__init__(self, dofs) DST.__init__(self, dofs)
self.uofs = uofs self.uofs = timedelta(minutes=uofs)
def utcoffset(self, dt): def utcoffset(self, dt):
return self.uofs return self.uofs
@ -2454,9 +2460,11 @@ class TestDateTimeTZ(TestDateTime, TZInfoBase):
class OperandDependentOffset(tzinfo): class OperandDependentOffset(tzinfo):
def utcoffset(self, t): def utcoffset(self, t):
if t.minute < 10: if t.minute < 10:
return t.minute # d0 and d1 equal after adjustment # d0 and d1 equal after adjustment
return timedelta(minutes=t.minute)
else: else:
return 59 # d2 off in the weeds # d2 off in the weeds
return timedelta(minutes=59)
base = cls(8, 9, 10, 11, 12, 13, 14, tzinfo=OperandDependentOffset()) base = cls(8, 9, 10, 11, 12, 13, 14, tzinfo=OperandDependentOffset())
d0 = base.replace(minute=3) d0 = base.replace(minute=3)
@ -2502,9 +2510,9 @@ class TestDateTimeTZ(TestDateTime, TZInfoBase):
# In datetimetz w/ identical tzinfo objects, utcoffset is ignored. # In datetimetz w/ identical tzinfo objects, utcoffset is ignored.
class Varies(tzinfo): class Varies(tzinfo):
def __init__(self): def __init__(self):
self.offset = 22 self.offset = timedelta(minutes=22)
def utcoffset(self, t): def utcoffset(self, t):
self.offset += 1 self.offset += timedelta(minutes=1)
return self.offset return self.offset
v = Varies() v = Varies()

View File

@ -27,9 +27,14 @@ Extension modules
microsecond <http://www.python.org/sf/661086>. microsecond <http://www.python.org/sf/661086>.
In dt.asdatetime(tz), if tz.utcoffset(dt) returns a duration, In dt.asdatetime(tz), if tz.utcoffset(dt) returns a duration,
ValueError is raised of tz.dst(dt) returns None (2.3a1 treated it ValueError is raised if tz.dst(dt) returns None (2.3a1 treated it
as 0 instead). as 0 instead).
The tzinfo methods utcoffset() and dst() must return a timedelta object
(or None) now. In 2.3a1 they could also return an int or long, but that
was an unhelpfully redundant leftover from an earlier version wherein
they couldn't return a timedelta. TOOWTDI.
Library Library
------- -------

View File

@ -629,11 +629,9 @@ replace_tzinfo(PyObject *self, PyObject *newtzinfo)
/* Call getattr(tzinfo, name)(tzinfoarg), and extract an int from the /* Call getattr(tzinfo, name)(tzinfoarg), and extract an int from the
* result. tzinfo must be an instance of the tzinfo class. If the method * result. tzinfo must be an instance of the tzinfo class. If the method
* returns None, this returns 0 and sets *none to 1. If the method doesn't * returns None, this returns 0 and sets *none to 1. If the method doesn't
* return a Python int or long or timedelta, TypeError is raised and this * return None or timedelta, TypeError is raised and this returns -1. If it
* returns -1. If it returns an int or long, but is outside the valid * returnsa timedelta and the value is out of range or isn't a whole number
* range for a UTC minute offset, or it returns a timedelta and the value is * of minutes, ValueError is raised and this returns -1.
* out of range or isn't a whole number of minutes, ValueError is raised and
* this returns -1.
* Else *none is set to 0 and the integer method result is returned. * Else *none is set to 0 and the integer method result is returned.
*/ */
static int static int
@ -641,7 +639,7 @@ call_utc_tzinfo_method(PyObject *tzinfo, char *name, PyObject *tzinfoarg,
int *none) int *none)
{ {
PyObject *u; PyObject *u;
long result = -1; /* Py{Int,Long}_AsLong return long */ int result = -1;
assert(tzinfo != NULL); assert(tzinfo != NULL);
assert(PyTZInfo_Check(tzinfo)); assert(PyTZInfo_Check(tzinfo));
@ -656,12 +654,6 @@ call_utc_tzinfo_method(PyObject *tzinfo, char *name, PyObject *tzinfoarg,
result = 0; result = 0;
*none = 1; *none = 1;
} }
else if (PyInt_Check(u))
result = PyInt_AS_LONG(u);
else if (PyLong_Check(u))
result = PyLong_AsLong(u);
else if (PyDelta_Check(u)) { else if (PyDelta_Check(u)) {
const int days = GET_TD_DAYS(u); const int days = GET_TD_DAYS(u);
if (days < -1 || days > 0) if (days < -1 || days > 0)
@ -683,7 +675,7 @@ call_utc_tzinfo_method(PyObject *tzinfo, char *name, PyObject *tzinfoarg,
} }
else { else {
PyErr_Format(PyExc_TypeError, PyErr_Format(PyExc_TypeError,
"tzinfo.%s() must return None, integer or " "tzinfo.%s() must return None or "
"timedelta, not '%s'", "timedelta, not '%s'",
name, u->ob_type->tp_name); name, u->ob_type->tp_name);
} }
@ -696,16 +688,16 @@ call_utc_tzinfo_method(PyObject *tzinfo, char *name, PyObject *tzinfoarg,
name, result); name, result);
result = -1; result = -1;
} }
return (int)result; return result;
} }
/* Call tzinfo.utcoffset(tzinfoarg), and extract an integer from the /* Call tzinfo.utcoffset(tzinfoarg), and extract an integer from the
* result. tzinfo must be an instance of the tzinfo class. If utcoffset() * result. tzinfo must be an instance of the tzinfo class. If utcoffset()
* returns None, call_utcoffset returns 0 and sets *none to 1. If uctoffset() * returns None, call_utcoffset returns 0 and sets *none to 1. If uctoffset()
& doesn't return a Python int or long, TypeError is raised and this * doesn't return None or timedelta, TypeError is raised and this returns -1.
* returns -1. If utcoffset() returns an int outside the legitimate range * If utcoffset() returns an invalid timedelta (out of range, or not a whole
* for a UTC offset, ValueError is raised and this returns -1. Else * # of minutes), ValueError is raised and this returns -1. Else *none is
* *none is set to 0 and the offset is returned. * set to 0 and the offset is returned (as int # of minutes east of UTC).
*/ */
static int static int
call_utcoffset(PyObject *tzinfo, PyObject *tzinfoarg, int *none) call_utcoffset(PyObject *tzinfo, PyObject *tzinfoarg, int *none)
@ -745,10 +737,10 @@ offset_as_timedelta(PyObject *tzinfo, char *name, PyObject *tzinfoarg) {
/* Call tzinfo.dst(tzinfoarg), and extract an integer from the /* Call tzinfo.dst(tzinfoarg), and extract an integer from the
* result. tzinfo must be an instance of the tzinfo class. If dst() * result. tzinfo must be an instance of the tzinfo class. If dst()
* returns None, call_dst returns 0 and sets *none to 1. If dst() * returns None, call_dst returns 0 and sets *none to 1. If dst()
& doesn't return a Python int or long, TypeError is raised and this & doesn't return None or timedelta, TypeError is raised and this
* returns -1. If dst() returns an int outside the legitimate range * returns -1. If dst() returns an invalid timedelta for for a UTC offset,
* for a UTC offset, ValueError is raised and this returns -1. Else * ValueError is raised and this returns -1. Else *none is set to 0 and
* *none is set to 0 and the offset is returned. * the offset is returned (as an int # of minutes east of UTC).
*/ */
static int static int
call_dst(PyObject *tzinfo, PyObject *tzinfoarg, int *none) call_dst(PyObject *tzinfo, PyObject *tzinfoarg, int *none)