From 397301eccb945ea98d03d3022882900a9fd2046f Mon Sep 17 00:00:00 2001 From: Tim Peters Date: Thu, 2 Jan 2003 21:28:08 +0000 Subject: 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. --- Doc/lib/libdatetime.tex | 29 ++++++++++--------- Doc/lib/tzinfo-examples.py | 15 +++++----- Lib/test/test_datetime.py | 72 +++++++++++++++++++++++++--------------------- Misc/NEWS | 9 ++++-- Modules/datetimemodule.c | 36 +++++++++-------------- 5 files changed, 84 insertions(+), 77 deletions(-) diff --git a/Doc/lib/libdatetime.tex b/Doc/lib/libdatetime.tex index 60fa678..d251e0f 100644 --- a/Doc/lib/libdatetime.tex +++ b/Doc/lib/libdatetime.tex @@ -231,18 +231,19 @@ Supported operations: {(1)} \lineiii{\var{t1} = \var{t2} // \var{i}} {The floor is computed and the remainder (if any) is thrown away.} - {(2)} + {(3)} \lineiii{+\var{t1}} {Returns a \class{timedelta} object with the same value.} - {} + {(2)} \lineiii{-\var{t1}} {equivalent to \class{timedelta}(-\var{t1.days}, -\var{t1.seconds}, -\var{t1.microseconds}),and to \var{t1}* -1.} - {(1)(3)} + {(1)(4)} \lineiii{abs(\var{t})} {equivalent to +\var{t} when \code{t.days >= 0}, and to - -\var{t} when \code{t.days < 0}.} - {(1)} + -\var{t} when \code{t.days < 0}. + overflow.} + {(2)} \end{tableiii} \noindent Notes: @@ -252,9 +253,12 @@ Notes: This is exact, but may overflow. \item[(2)] - Division by 0 raises \exception{ZeroDivisionError}. + This is exact, and cannot overflow. \item[(3)] + Division by 0 raises \exception{ZeroDivisionError}. + +\item[(4)] -\var{timedelta.max} is not representable as a \class{timedelta} object. \end{description} @@ -883,11 +887,10 @@ implement all of them. \class{tzinfo} object represents both time zone and DST adjustments, \method{utcoffset()} should return their sum. If the UTC offset isn't known, return \code{None}. Else the value returned must be - an integer, in the range -1439 to 1439 inclusive (1440 = 24*60; - the magnitude of the offset must be less than one day), or a - \class{timedelta} object representing a whole number of minutes - in the same range. Most implementations of \method{utcoffset()} - will probably look like one of these two: + a \class{timedelta} object specifying a whole number of minutes in the + range -1439 to 1439 inclusive (1440 = 24*60; the magnitude of the offset + must be less than one day). Most implementations of + \method{utcoffset()} will probably look like one of these two: \begin{verbatim} return CONSTANT # fixed-offset class @@ -896,8 +899,6 @@ implement all of them. If \method{utcoffset()} does not return \code{None}, \method{dst()} should not return \code{None} either. - - \end{methoddesc} @@ -905,7 +906,7 @@ implement all of them. 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 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). Note that DST offset, if applicable, has already been added to the UTC offset returned by diff --git a/Doc/lib/tzinfo-examples.py b/Doc/lib/tzinfo-examples.py index 1d3b3c4..868755a 100644 --- a/Doc/lib/tzinfo-examples.py +++ b/Doc/lib/tzinfo-examples.py @@ -1,16 +1,18 @@ -from datetime import tzinfo +from datetime import tzinfo, timedelta + +ZERO = timedelta(0) class UTC(tzinfo): """UTC""" def utcoffset(self, dt): - return 0 + return ZERO def tzname(self, dt): return "UTC" def dst(self, dt): - return 0 + return ZERO class FixedOffset(tzinfo): """Fixed offset in minutes east from UTC.""" @@ -26,8 +28,7 @@ class FixedOffset(tzinfo): return self.__name def dst(self, dt): - # It depends on more than we know in an example. - return None # Indicate we don't know + return ZERO import time @@ -43,9 +44,9 @@ class LocalTime(tzinfo): def utcoffset(self, dt): if self._isdst(dt): - return -time.timezone/60 + return timedelta(seconds=-time.timezone) else: - return -time.altzone/60 + return timedelta(seconds=-time.altzone) def tzname(self, dt): return time.tzname[self._isdst(dt)] diff --git a/Lib/test/test_datetime.py b/Lib/test/test_datetime.py index 29f81f1..c9f7674 100644 --- a/Lib/test/test_datetime.py +++ b/Lib/test/test_datetime.py @@ -26,6 +26,10 @@ class TestModule(unittest.TestCase): class FixedOffset(tzinfo): 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.__name = name self.__dstoffset = dstoffset @@ -72,9 +76,9 @@ class TestTZInfo(unittest.TestCase): fo = FixedOffset(3, "Three") self.failUnless(isinstance(fo, tzinfo)) 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.dst(dt), 42) + self.assertEqual(fo.dst(dt), timedelta(minutes=42)) def test_pickling_base(self): import pickle, cPickle @@ -94,10 +98,11 @@ class TestTZInfo(unittest.TestCase): import pickle, cPickle # 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(type(orig) is PicklableFixedOffset) - self.assertEqual(orig.utcoffset(None), -300) + self.assertEqual(orig.utcoffset(None), offset) self.assertEqual(orig.tzname(None), 'cookie') for pickler in pickle, cPickle: for binary in 0, 1: @@ -105,7 +110,7 @@ class TestTZInfo(unittest.TestCase): derived = pickler.loads(green) self.failUnless(isinstance(derived, tzinfo)) self.failUnless(type(derived) is PicklableFixedOffset) - self.assertEqual(derived.utcoffset(None), -300) + self.assertEqual(derived.utcoffset(None), offset) self.assertEqual(derived.tzname(None), 'cookie') ############################################################################# @@ -1562,7 +1567,8 @@ class TZInfoBase(unittest.TestCase): # A datetimetz passes itself on, a timetz passes None. class introspective(tzinfo): 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 obj = cls(1, 2, 3, tzinfo=introspective()) @@ -1593,7 +1599,7 @@ class TZInfoBase(unittest.TestCase): def test_utc_offset_out_of_bounds(self): class Edgy(tzinfo): def __init__(self, offset): - self.offset = offset + self.offset = timedelta(minutes=offset) def utcoffset(self, dt): return self.offset @@ -1629,23 +1635,19 @@ class TZInfoBase(unittest.TestCase): self.failUnless(t.dst() 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): def utcoffset(self, dt): return timedelta(minutes=-1439) def dst(self, dt): return timedelta(minutes=1439) def tzname(self, dt): return "aname" - for t in cls(1, 1, 1, tzinfo=C2()), cls(1, 1, 1, tzinfo=C3()): - self.assertEqual(t.utcoffset(), timedelta(minutes=-1439)) - self.assertEqual(t.dst(), timedelta(minutes=1439)) - self.assertEqual(t.tzname(), "aname") + t = cls(1, 1, 1, tzinfo=C3()) + self.assertEqual(t.utcoffset(), timedelta(minutes=-1439)) + self.assertEqual(t.dst(), timedelta(minutes=1439)) + self.assertEqual(t.tzname(), "aname") # Wrong types. class C4(tzinfo): def utcoffset(self, dt): return "aname" - def dst(self, dt): return () + def dst(self, dt): return 7 def tzname(self, dt): return 0 t = cls(1, 1, 1, tzinfo=C4()) self.assertRaises(TypeError, t.utcoffset) @@ -1653,15 +1655,12 @@ class TZInfoBase(unittest.TestCase): self.assertRaises(TypeError, t.tzname) # Offset out of range. - class C5(tzinfo): - def utcoffset(self, dt): return -1440 - def dst(self, dt): return 1440 class C6(tzinfo): def utcoffset(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()): - self.assertRaises(ValueError, t.utcoffset) - self.assertRaises(ValueError, t.dst) + t = cls(1, 1, 1, tzinfo=C6()) + self.assertRaises(ValueError, t.utcoffset) + self.assertRaises(ValueError, t.dst) # Not a whole number of minutes. class C7(tzinfo): @@ -1679,9 +1678,11 @@ class TZInfoBase(unittest.TestCase): class OperandDependentOffset(tzinfo): def utcoffset(self, t): 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: - return 59 # d2 off in the weeds + # d2 off in the weeds + return timedelta(minutes=59) base = cls(8, 9, 10, tzinfo=OperandDependentOffset()) d0 = base.replace(minute=3) @@ -1937,9 +1938,9 @@ class TestTimeTZ(TestTime, TZInfoBase): # In timetz w/ identical tzinfo objects, utcoffset is ignored. class Varies(tzinfo): def __init__(self): - self.offset = 22 + self.offset = timedelta(minutes=22) def utcoffset(self, t): - self.offset += 1 + self.offset += timedelta(minutes=1) return self.offset v = Varies() @@ -2028,7 +2029,8 @@ class TestDateTimeTZ(TestDateTime, TZInfoBase): # Try a bogus uctoffset. 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()) t2 = self.theclass(2, 2, 2, tzinfo=FixedOffset(0, "")) self.assertRaises(ValueError, lambda: t1 == t2) @@ -2259,6 +2261,8 @@ class TestDateTimeTZ(TestDateTime, TZInfoBase): # DST flag. class DST(tzinfo): def __init__(self, dstvalue): + if isinstance(dstvalue, int): + dstvalue = timedelta(minutes=dstvalue) self.dstvalue = dstvalue def dst(self, dt): return self.dstvalue @@ -2291,6 +2295,8 @@ class TestDateTimeTZ(TestDateTime, TZInfoBase): def test_utctimetuple(self): class DST(tzinfo): def __init__(self, dstvalue): + if isinstance(dstvalue, int): + dstvalue = timedelta(minutes=dstvalue) self.dstvalue = dstvalue def dst(self, dt): return self.dstvalue @@ -2303,7 +2309,7 @@ class TestDateTimeTZ(TestDateTime, TZInfoBase): class UOFS(DST): def __init__(self, uofs, dofs=None): DST.__init__(self, dofs) - self.uofs = uofs + self.uofs = timedelta(minutes=uofs) def utcoffset(self, dt): return self.uofs @@ -2454,9 +2460,11 @@ class TestDateTimeTZ(TestDateTime, TZInfoBase): class OperandDependentOffset(tzinfo): def utcoffset(self, t): 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: - 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()) d0 = base.replace(minute=3) @@ -2502,9 +2510,9 @@ class TestDateTimeTZ(TestDateTime, TZInfoBase): # In datetimetz w/ identical tzinfo objects, utcoffset is ignored. class Varies(tzinfo): def __init__(self): - self.offset = 22 + self.offset = timedelta(minutes=22) def utcoffset(self, t): - self.offset += 1 + self.offset += timedelta(minutes=1) return self.offset v = Varies() diff --git a/Misc/NEWS b/Misc/NEWS index b9c076d..246d171 100644 --- a/Misc/NEWS +++ b/Misc/NEWS @@ -27,9 +27,14 @@ Extension modules microsecond . 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). - + + 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 ------- diff --git a/Modules/datetimemodule.c b/Modules/datetimemodule.c index 96c3e6d..4f53ece 100644 --- a/Modules/datetimemodule.c +++ b/Modules/datetimemodule.c @@ -629,11 +629,9 @@ replace_tzinfo(PyObject *self, PyObject *newtzinfo) /* Call getattr(tzinfo, name)(tzinfoarg), and extract an int from the * 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 - * return a Python int or long or timedelta, TypeError is raised and this - * returns -1. If it returns an int or long, but is outside the valid - * range for a UTC minute offset, or it returns a timedelta and the value is - * out of range or isn't a whole number of minutes, ValueError is raised and - * this returns -1. + * return None or timedelta, TypeError is raised and this returns -1. If it + * returnsa timedelta and the value is 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. */ static int @@ -641,7 +639,7 @@ call_utc_tzinfo_method(PyObject *tzinfo, char *name, PyObject *tzinfoarg, int *none) { PyObject *u; - long result = -1; /* Py{Int,Long}_AsLong return long */ + int result = -1; assert(tzinfo != NULL); assert(PyTZInfo_Check(tzinfo)); @@ -656,12 +654,6 @@ call_utc_tzinfo_method(PyObject *tzinfo, char *name, PyObject *tzinfoarg, result = 0; *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)) { const int days = GET_TD_DAYS(u); if (days < -1 || days > 0) @@ -683,7 +675,7 @@ call_utc_tzinfo_method(PyObject *tzinfo, char *name, PyObject *tzinfoarg, } else { PyErr_Format(PyExc_TypeError, - "tzinfo.%s() must return None, integer or " + "tzinfo.%s() must return None or " "timedelta, not '%s'", name, u->ob_type->tp_name); } @@ -696,16 +688,16 @@ call_utc_tzinfo_method(PyObject *tzinfo, char *name, PyObject *tzinfoarg, name, result); result = -1; } - return (int)result; + return result; } /* Call tzinfo.utcoffset(tzinfoarg), and extract an integer from the * 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() - & doesn't return a Python int or long, TypeError is raised and this - * returns -1. If utcoffset() returns an int outside the legitimate range - * for a UTC offset, ValueError is raised and this returns -1. Else - * *none is set to 0 and the offset is returned. + * doesn't return None or timedelta, TypeError is raised and this returns -1. + * If utcoffset() returns an invalid timedelta (out of range, or not a whole + * # of minutes), ValueError is raised and this returns -1. Else *none is + * set to 0 and the offset is returned (as int # of minutes east of UTC). */ static int 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 * 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() - & doesn't return a Python int or long, TypeError is raised and this - * returns -1. If dst() returns an int outside the legitimate range - * for a UTC offset, ValueError is raised and this returns -1. Else - * *none is set to 0 and the offset is returned. + & doesn't return None or timedelta, TypeError is raised and this + * returns -1. If dst() returns an invalid timedelta for for a UTC offset, + * ValueError is raised and this returns -1. Else *none is set to 0 and + * the offset is returned (as an int # of minutes east of UTC). */ static int call_dst(PyObject *tzinfo, PyObject *tzinfoarg, int *none) -- cgit v0.12