Implemented a Wiki suggestion:
{timetz,datetimetz}.{utcoffset,dst}() now return a timedelta (or None) instead of an int (or None). tzinfo.{utcoffset,dst)() can now return a timedelta (or an int, or None). Curiously, this was much easier to do in the C implementation than in the Python implementation (which lives in the Zope3 code tree) -- the C code already had lots of hair to extract C ints from offset objects, and used C ints internally.
This commit is contained in:
parent
18091540db
commit
855fe88b24
|
@ -1458,9 +1458,110 @@ class TestTime(unittest.TestCase):
|
||||||
self.failUnless(not cls(0))
|
self.failUnless(not cls(0))
|
||||||
self.failUnless(not cls())
|
self.failUnless(not cls())
|
||||||
|
|
||||||
|
# A mixin for classes with a tzinfo= argument. Subclasses must define
|
||||||
|
# theclass as a class atribute, and theclass(1, 1, 1, tzinfo=whatever)
|
||||||
|
# must be legit (which is true for timetz and datetimetz).
|
||||||
|
class TZInfoBase(unittest.TestCase):
|
||||||
|
|
||||||
class TestTimeTZ(TestTime):
|
def test_bad_tzinfo_classes(self):
|
||||||
|
cls = self.theclass
|
||||||
|
self.assertRaises(TypeError, cls, 1, 1, 1, tzinfo=12)
|
||||||
|
|
||||||
|
class NiceTry(object):
|
||||||
|
def __init__(self): pass
|
||||||
|
def utcoffset(self, dt): pass
|
||||||
|
self.assertRaises(TypeError, cls, 1, 1, 1, tzinfo=NiceTry)
|
||||||
|
|
||||||
|
class BetterTry(tzinfo):
|
||||||
|
def __init__(self): pass
|
||||||
|
def utcoffset(self, dt): pass
|
||||||
|
b = BetterTry()
|
||||||
|
t = cls(1, 1, 1, tzinfo=b)
|
||||||
|
self.failUnless(t.tzinfo is b)
|
||||||
|
|
||||||
|
def test_utc_offset_out_of_bounds(self):
|
||||||
|
class Edgy(tzinfo):
|
||||||
|
def __init__(self, offset):
|
||||||
|
self.offset = offset
|
||||||
|
def utcoffset(self, dt):
|
||||||
|
return self.offset
|
||||||
|
|
||||||
|
cls = self.theclass
|
||||||
|
for offset, legit in ((-1440, False),
|
||||||
|
(-1439, True),
|
||||||
|
(1439, True),
|
||||||
|
(1440, False)):
|
||||||
|
if cls is timetz:
|
||||||
|
t = cls(1, 2, 3, tzinfo=Edgy(offset))
|
||||||
|
elif cls is datetimetz:
|
||||||
|
t = cls(6, 6, 6, 1, 2, 3, tzinfo=Edgy(offset))
|
||||||
|
if legit:
|
||||||
|
aofs = abs(offset)
|
||||||
|
h, m = divmod(aofs, 60)
|
||||||
|
tag = "%c%02d:%02d" % (offset < 0 and '-' or '+', h, m)
|
||||||
|
if isinstance(t, datetimetz):
|
||||||
|
t = t.timetz()
|
||||||
|
self.assertEqual(str(t), "01:02:03" + tag)
|
||||||
|
else:
|
||||||
|
self.assertRaises(ValueError, str, t)
|
||||||
|
|
||||||
|
def test_tzinfo_classes(self):
|
||||||
|
cls = self.theclass
|
||||||
|
class C1(tzinfo):
|
||||||
|
def utcoffset(self, dt): return None
|
||||||
|
def dst(self, dt): return None
|
||||||
|
def tzname(self, dt): return None
|
||||||
|
for t in (cls(1, 1, 1),
|
||||||
|
cls(1, 1, 1, tzinfo=None),
|
||||||
|
cls(1, 1, 1, tzinfo=C1())):
|
||||||
|
self.failUnless(t.utcoffset() is None)
|
||||||
|
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")
|
||||||
|
|
||||||
|
# Wrong types.
|
||||||
|
class C4(tzinfo):
|
||||||
|
def utcoffset(self, dt): return "aname"
|
||||||
|
def dst(self, dt): return ()
|
||||||
|
def tzname(self, dt): return 0
|
||||||
|
t = cls(1, 1, 1, tzinfo=C4())
|
||||||
|
self.assertRaises(TypeError, t.utcoffset)
|
||||||
|
self.assertRaises(TypeError, t.dst)
|
||||||
|
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)
|
||||||
|
|
||||||
|
# Not a whole number of minutes.
|
||||||
|
class C7(tzinfo):
|
||||||
|
def utcoffset(self, dt): return timedelta(seconds=61)
|
||||||
|
def dst(self, dt): return timedelta(microseconds=-81)
|
||||||
|
t = cls(1, 1, 1, tzinfo=C7())
|
||||||
|
self.assertRaises(ValueError, t.utcoffset)
|
||||||
|
self.assertRaises(ValueError, t.dst)
|
||||||
|
|
||||||
|
|
||||||
|
class TestTimeTZ(TestTime, TZInfoBase):
|
||||||
theclass = timetz
|
theclass = timetz
|
||||||
|
|
||||||
def test_empty(self):
|
def test_empty(self):
|
||||||
|
@ -1471,22 +1572,6 @@ class TestTimeTZ(TestTime):
|
||||||
self.assertEqual(t.microsecond, 0)
|
self.assertEqual(t.microsecond, 0)
|
||||||
self.failUnless(t.tzinfo is None)
|
self.failUnless(t.tzinfo is None)
|
||||||
|
|
||||||
def test_bad_tzinfo_classes(self):
|
|
||||||
tz = self.theclass
|
|
||||||
self.assertRaises(TypeError, tz, tzinfo=12)
|
|
||||||
|
|
||||||
class NiceTry(object):
|
|
||||||
def __init__(self): pass
|
|
||||||
def utcoffset(self, dt): pass
|
|
||||||
self.assertRaises(TypeError, tz, tzinfo=NiceTry)
|
|
||||||
|
|
||||||
class BetterTry(tzinfo):
|
|
||||||
def __init__(self): pass
|
|
||||||
def utcoffset(self, dt): pass
|
|
||||||
b = BetterTry()
|
|
||||||
t = tz(tzinfo=b)
|
|
||||||
self.failUnless(t.tzinfo is b)
|
|
||||||
|
|
||||||
def test_zones(self):
|
def test_zones(self):
|
||||||
est = FixedOffset(-300, "EST", 1)
|
est = FixedOffset(-300, "EST", 1)
|
||||||
utc = FixedOffset(0, "UTC", -2)
|
utc = FixedOffset(0, "UTC", -2)
|
||||||
|
@ -1503,9 +1588,9 @@ class TestTimeTZ(TestTime):
|
||||||
self.failUnless(t4.tzinfo is None)
|
self.failUnless(t4.tzinfo is None)
|
||||||
self.assertEqual(t5.tzinfo, utc)
|
self.assertEqual(t5.tzinfo, utc)
|
||||||
|
|
||||||
self.assertEqual(t1.utcoffset(), -300)
|
self.assertEqual(t1.utcoffset(), timedelta(minutes=-300))
|
||||||
self.assertEqual(t2.utcoffset(), 0)
|
self.assertEqual(t2.utcoffset(), timedelta(minutes=0))
|
||||||
self.assertEqual(t3.utcoffset(), 60)
|
self.assertEqual(t3.utcoffset(), timedelta(minutes=60))
|
||||||
self.failUnless(t4.utcoffset() is None)
|
self.failUnless(t4.utcoffset() is None)
|
||||||
self.assertRaises(TypeError, t1.utcoffset, "no args")
|
self.assertRaises(TypeError, t1.utcoffset, "no args")
|
||||||
|
|
||||||
|
@ -1515,9 +1600,9 @@ class TestTimeTZ(TestTime):
|
||||||
self.failUnless(t4.tzname() is None)
|
self.failUnless(t4.tzname() is None)
|
||||||
self.assertRaises(TypeError, t1.tzname, "no args")
|
self.assertRaises(TypeError, t1.tzname, "no args")
|
||||||
|
|
||||||
self.assertEqual(t1.dst(), 1)
|
self.assertEqual(t1.dst(), timedelta(minutes=1))
|
||||||
self.assertEqual(t2.dst(), -2)
|
self.assertEqual(t2.dst(), timedelta(minutes=-2))
|
||||||
self.assertEqual(t3.dst(), 3)
|
self.assertEqual(t3.dst(), timedelta(minutes=3))
|
||||||
self.failUnless(t4.dst() is None)
|
self.failUnless(t4.dst() is None)
|
||||||
self.assertRaises(TypeError, t1.dst, "no args")
|
self.assertRaises(TypeError, t1.dst, "no args")
|
||||||
|
|
||||||
|
@ -1578,26 +1663,6 @@ class TestTimeTZ(TestTime):
|
||||||
t2 = self.theclass(23, 48, 6, 100, tzinfo=FixedOffset(-1010, ""))
|
t2 = self.theclass(23, 48, 6, 100, tzinfo=FixedOffset(-1010, ""))
|
||||||
self.assertEqual(hash(t1), hash(t2))
|
self.assertEqual(hash(t1), hash(t2))
|
||||||
|
|
||||||
def test_utc_offset_out_of_bounds(self):
|
|
||||||
class Edgy(tzinfo):
|
|
||||||
def __init__(self, offset):
|
|
||||||
self.offset = offset
|
|
||||||
def utcoffset(self, dt):
|
|
||||||
return self.offset
|
|
||||||
|
|
||||||
for offset, legit in ((-1440, False),
|
|
||||||
(-1439, True),
|
|
||||||
(1439, True),
|
|
||||||
(1440, False)):
|
|
||||||
t = timetz(1, 2, 3, tzinfo=Edgy(offset))
|
|
||||||
if legit:
|
|
||||||
aofs = abs(offset)
|
|
||||||
h, m = divmod(aofs, 60)
|
|
||||||
tag = "%c%02d:%02d" % (offset < 0 and '-' or '+', h, m)
|
|
||||||
self.assertEqual(str(t), "01:02:03" + tag)
|
|
||||||
else:
|
|
||||||
self.assertRaises(ValueError, str, t)
|
|
||||||
|
|
||||||
def test_pickling(self):
|
def test_pickling(self):
|
||||||
import pickle, cPickle
|
import pickle, cPickle
|
||||||
|
|
||||||
|
@ -1623,7 +1688,7 @@ class TestTimeTZ(TestTime):
|
||||||
derived.__setstate__(state)
|
derived.__setstate__(state)
|
||||||
self.assertEqual(orig, derived)
|
self.assertEqual(orig, derived)
|
||||||
self.failUnless(isinstance(derived.tzinfo, PicklableFixedOffset))
|
self.failUnless(isinstance(derived.tzinfo, PicklableFixedOffset))
|
||||||
self.assertEqual(derived.utcoffset(), -300)
|
self.assertEqual(derived.utcoffset(), timedelta(minutes=-300))
|
||||||
self.assertEqual(derived.tzname(), 'cookie')
|
self.assertEqual(derived.tzname(), 'cookie')
|
||||||
|
|
||||||
for pickler in pickle, cPickle:
|
for pickler in pickle, cPickle:
|
||||||
|
@ -1633,7 +1698,7 @@ class TestTimeTZ(TestTime):
|
||||||
self.assertEqual(orig, derived)
|
self.assertEqual(orig, derived)
|
||||||
self.failUnless(isinstance(derived.tzinfo,
|
self.failUnless(isinstance(derived.tzinfo,
|
||||||
PicklableFixedOffset))
|
PicklableFixedOffset))
|
||||||
self.assertEqual(derived.utcoffset(), -300)
|
self.assertEqual(derived.utcoffset(), timedelta(minutes=-300))
|
||||||
self.assertEqual(derived.tzname(), 'cookie')
|
self.assertEqual(derived.tzname(), 'cookie')
|
||||||
|
|
||||||
def test_more_bool(self):
|
def test_more_bool(self):
|
||||||
|
@ -1664,8 +1729,7 @@ class TestTimeTZ(TestTime):
|
||||||
t = cls(0, tzinfo=FixedOffset(-24*60, ""))
|
t = cls(0, tzinfo=FixedOffset(-24*60, ""))
|
||||||
self.assertRaises(ValueError, lambda: bool(t))
|
self.assertRaises(ValueError, lambda: bool(t))
|
||||||
|
|
||||||
class TestDateTimeTZ(TestDateTime):
|
class TestDateTimeTZ(TestDateTime, TZInfoBase):
|
||||||
|
|
||||||
theclass = datetimetz
|
theclass = datetimetz
|
||||||
|
|
||||||
def test_trivial(self):
|
def test_trivial(self):
|
||||||
|
@ -1744,22 +1808,6 @@ class TestDateTimeTZ(TestDateTime):
|
||||||
t2 = self.theclass(2, 2, 2, tzinfo=FixedOffset(0, ""))
|
t2 = self.theclass(2, 2, 2, tzinfo=FixedOffset(0, ""))
|
||||||
self.assertRaises(ValueError, lambda: t1 == t1)
|
self.assertRaises(ValueError, lambda: t1 == t1)
|
||||||
|
|
||||||
def test_bad_tzinfo_classes(self):
|
|
||||||
tz = self.theclass
|
|
||||||
self.assertRaises(TypeError, tz, 1, 2, 3, tzinfo=12)
|
|
||||||
|
|
||||||
class NiceTry(object):
|
|
||||||
def __init__(self): pass
|
|
||||||
def utcoffset(self, dt): pass
|
|
||||||
self.assertRaises(TypeError, tz, 1, 2, 3, tzinfo=NiceTry)
|
|
||||||
|
|
||||||
class BetterTry(tzinfo):
|
|
||||||
def __init__(self): pass
|
|
||||||
def utcoffset(self, dt): pass
|
|
||||||
b = BetterTry()
|
|
||||||
t = tz(1, 2, 3, tzinfo=b)
|
|
||||||
self.failUnless(t.tzinfo is b)
|
|
||||||
|
|
||||||
def test_pickling(self):
|
def test_pickling(self):
|
||||||
import pickle, cPickle
|
import pickle, cPickle
|
||||||
|
|
||||||
|
@ -1785,7 +1833,7 @@ class TestDateTimeTZ(TestDateTime):
|
||||||
derived.__setstate__(state)
|
derived.__setstate__(state)
|
||||||
self.assertEqual(orig, derived)
|
self.assertEqual(orig, derived)
|
||||||
self.failUnless(isinstance(derived.tzinfo, PicklableFixedOffset))
|
self.failUnless(isinstance(derived.tzinfo, PicklableFixedOffset))
|
||||||
self.assertEqual(derived.utcoffset(), -300)
|
self.assertEqual(derived.utcoffset(), timedelta(minutes=-300))
|
||||||
self.assertEqual(derived.tzname(), 'cookie')
|
self.assertEqual(derived.tzname(), 'cookie')
|
||||||
|
|
||||||
for pickler in pickle, cPickle:
|
for pickler in pickle, cPickle:
|
||||||
|
@ -1795,7 +1843,7 @@ class TestDateTimeTZ(TestDateTime):
|
||||||
self.assertEqual(orig, derived)
|
self.assertEqual(orig, derived)
|
||||||
self.failUnless(isinstance(derived.tzinfo,
|
self.failUnless(isinstance(derived.tzinfo,
|
||||||
PicklableFixedOffset))
|
PicklableFixedOffset))
|
||||||
self.assertEqual(derived.utcoffset(), -300)
|
self.assertEqual(derived.utcoffset(), timedelta(minutes=-300))
|
||||||
self.assertEqual(derived.tzname(), 'cookie')
|
self.assertEqual(derived.tzname(), 'cookie')
|
||||||
|
|
||||||
def test_extreme_hashes(self):
|
def test_extreme_hashes(self):
|
||||||
|
@ -1822,9 +1870,9 @@ class TestDateTimeTZ(TestDateTime):
|
||||||
self.assertEqual(t1.tzinfo, est)
|
self.assertEqual(t1.tzinfo, est)
|
||||||
self.assertEqual(t2.tzinfo, utc)
|
self.assertEqual(t2.tzinfo, utc)
|
||||||
self.assertEqual(t3.tzinfo, met)
|
self.assertEqual(t3.tzinfo, met)
|
||||||
self.assertEqual(t1.utcoffset(), -300)
|
self.assertEqual(t1.utcoffset(), timedelta(minutes=-300))
|
||||||
self.assertEqual(t2.utcoffset(), 0)
|
self.assertEqual(t2.utcoffset(), timedelta(minutes=0))
|
||||||
self.assertEqual(t3.utcoffset(), 60)
|
self.assertEqual(t3.utcoffset(), timedelta(minutes=60))
|
||||||
self.assertEqual(t1.tzname(), "EST")
|
self.assertEqual(t1.tzname(), "EST")
|
||||||
self.assertEqual(t2.tzname(), "UTC")
|
self.assertEqual(t2.tzname(), "UTC")
|
||||||
self.assertEqual(t3.tzname(), "MET")
|
self.assertEqual(t3.tzname(), "MET")
|
||||||
|
@ -1914,8 +1962,7 @@ class TestDateTimeTZ(TestDateTime):
|
||||||
# (nowaware base - nowawareplus base) +
|
# (nowaware base - nowawareplus base) +
|
||||||
# (nowawareplus offset - nowaware offset) =
|
# (nowawareplus offset - nowaware offset) =
|
||||||
# -delta + nowawareplus offset - nowaware offset
|
# -delta + nowawareplus offset - nowaware offset
|
||||||
expected = timedelta(minutes=nowawareplus.utcoffset() -
|
expected = nowawareplus.utcoffset() - nowaware.utcoffset() - delta
|
||||||
nowaware.utcoffset()) - delta
|
|
||||||
self.assertEqual(got, expected)
|
self.assertEqual(got, expected)
|
||||||
|
|
||||||
# Try max possible difference.
|
# Try max possible difference.
|
||||||
|
@ -1935,7 +1982,7 @@ class TestDateTimeTZ(TestDateTime):
|
||||||
another = meth(off42)
|
another = meth(off42)
|
||||||
again = meth(tzinfo=off42)
|
again = meth(tzinfo=off42)
|
||||||
self.failUnless(another.tzinfo is again.tzinfo)
|
self.failUnless(another.tzinfo is again.tzinfo)
|
||||||
self.assertEqual(another.utcoffset(), 42)
|
self.assertEqual(another.utcoffset(), timedelta(minutes=42))
|
||||||
# Bad argument with and w/o naming the keyword.
|
# Bad argument with and w/o naming the keyword.
|
||||||
self.assertRaises(TypeError, meth, 16)
|
self.assertRaises(TypeError, meth, 16)
|
||||||
self.assertRaises(TypeError, meth, tzinfo=16)
|
self.assertRaises(TypeError, meth, tzinfo=16)
|
||||||
|
@ -1955,7 +2002,7 @@ class TestDateTimeTZ(TestDateTime):
|
||||||
another = meth(ts, off42)
|
another = meth(ts, off42)
|
||||||
again = meth(ts, tzinfo=off42)
|
again = meth(ts, tzinfo=off42)
|
||||||
self.failUnless(another.tzinfo is again.tzinfo)
|
self.failUnless(another.tzinfo is again.tzinfo)
|
||||||
self.assertEqual(another.utcoffset(), 42)
|
self.assertEqual(another.utcoffset(), timedelta(minutes=42))
|
||||||
# Bad argument with and w/o naming the keyword.
|
# Bad argument with and w/o naming the keyword.
|
||||||
self.assertRaises(TypeError, meth, ts, 16)
|
self.assertRaises(TypeError, meth, ts, 16)
|
||||||
self.assertRaises(TypeError, meth, ts, tzinfo=16)
|
self.assertRaises(TypeError, meth, ts, tzinfo=16)
|
||||||
|
|
|
@ -549,6 +549,40 @@ normalize_datetime(int *year, int *month, int *day,
|
||||||
* tzinfo helpers.
|
* tzinfo helpers.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
/* Ensure that p is None or of a tzinfo subclass. Return 0 if OK; if not
|
||||||
|
* raise TypeError and return -1.
|
||||||
|
*/
|
||||||
|
static int
|
||||||
|
check_tzinfo_subclass(PyObject *p)
|
||||||
|
{
|
||||||
|
if (p == Py_None || PyTZInfo_Check(p))
|
||||||
|
return 0;
|
||||||
|
PyErr_Format(PyExc_TypeError,
|
||||||
|
"tzinfo argument must be None or of a tzinfo subclass, "
|
||||||
|
"not type '%s'",
|
||||||
|
p->ob_type->tp_name);
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Return tzinfo.methname(self), without any checking of results.
|
||||||
|
* If tzinfo is None, returns None.
|
||||||
|
*/
|
||||||
|
static PyObject *
|
||||||
|
call_tzinfo_method(PyObject *self, PyObject *tzinfo, char *methname)
|
||||||
|
{
|
||||||
|
PyObject *result;
|
||||||
|
|
||||||
|
assert(self && tzinfo && methname);
|
||||||
|
assert(check_tzinfo_subclass(tzinfo) >= 0);
|
||||||
|
if (tzinfo == Py_None) {
|
||||||
|
result = Py_None;
|
||||||
|
Py_INCREF(result);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
result = PyObject_CallMethod(tzinfo, methname, "O", self);
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
/* If self has a tzinfo member, return a BORROWED reference to it. Else
|
/* If self has a tzinfo member, return a BORROWED reference to it. Else
|
||||||
* return NULL, which is NOT AN ERROR. There are no error returns here,
|
* return NULL, which is NOT AN ERROR. There are no error returns here,
|
||||||
* and the caller must not decref the result.
|
* and the caller must not decref the result.
|
||||||
|
@ -566,28 +600,15 @@ get_tzinfo_member(PyObject *self)
|
||||||
return tzinfo;
|
return tzinfo;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Ensure that p is None or of a tzinfo subclass. Return 0 if OK; if not
|
|
||||||
* raise TypeError and return -1.
|
|
||||||
*/
|
|
||||||
static int
|
|
||||||
check_tzinfo_subclass(PyObject *p)
|
|
||||||
{
|
|
||||||
if (p == Py_None || PyTZInfo_Check(p))
|
|
||||||
return 0;
|
|
||||||
PyErr_Format(PyExc_TypeError,
|
|
||||||
"tzinfo argument must be None or of a tzinfo subclass, "
|
|
||||||
"not type '%s'",
|
|
||||||
p->ob_type->tp_name);
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Internal helper.
|
/* Internal helper.
|
||||||
* 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, TypeError is raised and this returns -1.
|
* return a Python int or long or timedelta, TypeError is raised and this
|
||||||
* If it does return an int or long, but is outside the valid range for
|
* returns -1. If it returns an int or long, but is outside the valid
|
||||||
* a UTC minute offset, ValueError is raised and this returns -1.
|
* 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.
|
||||||
* 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
|
||||||
|
@ -602,7 +623,7 @@ call_utc_tzinfo_method(PyObject *tzinfo, char *name, PyObject *tzinfoarg,
|
||||||
assert(tzinfoarg != NULL);
|
assert(tzinfoarg != NULL);
|
||||||
|
|
||||||
*none = 0;
|
*none = 0;
|
||||||
u = PyObject_CallMethod(tzinfo, name, "O", tzinfoarg);
|
u = call_tzinfo_method(tzinfoarg, tzinfo, name);
|
||||||
if (u == NULL)
|
if (u == NULL)
|
||||||
return -1;
|
return -1;
|
||||||
|
|
||||||
|
@ -614,12 +635,35 @@ call_utc_tzinfo_method(PyObject *tzinfo, char *name, PyObject *tzinfoarg,
|
||||||
|
|
||||||
if (PyInt_Check(u))
|
if (PyInt_Check(u))
|
||||||
result = PyInt_AS_LONG(u);
|
result = PyInt_AS_LONG(u);
|
||||||
|
|
||||||
else if (PyLong_Check(u))
|
else if (PyLong_Check(u))
|
||||||
result = PyLong_AsLong(u);
|
result = PyLong_AsLong(u);
|
||||||
|
|
||||||
|
else if (PyDelta_Check(u)) {
|
||||||
|
const int days = GET_TD_DAYS(u);
|
||||||
|
if (days < -1 || days > 0)
|
||||||
|
result = 24*60; /* trigger ValueError below */
|
||||||
|
else {
|
||||||
|
/* next line can't overflow because we know days
|
||||||
|
* is -1 or 0 now
|
||||||
|
*/
|
||||||
|
int ss = days * 24 * 3600 + GET_TD_SECONDS(u);
|
||||||
|
result = divmod(ss, 60, &ss);
|
||||||
|
if (ss || GET_TD_MICROSECONDS(u)) {
|
||||||
|
PyErr_Format(PyExc_ValueError,
|
||||||
|
"tzinfo.%s() must return a "
|
||||||
|
"whole number of minutes",
|
||||||
|
name);
|
||||||
|
result = -1;
|
||||||
|
goto Done;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
else {
|
else {
|
||||||
PyErr_Format(PyExc_TypeError,
|
PyErr_Format(PyExc_TypeError,
|
||||||
"tzinfo.%s() must return None or int or long",
|
"tzinfo.%s() must return None, integer or "
|
||||||
name);
|
"timedelta, not '%s'",
|
||||||
|
name, u->ob_type->tp_name);
|
||||||
goto Done;
|
goto Done;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -649,6 +693,32 @@ call_utcoffset(PyObject *tzinfo, PyObject *tzinfoarg, int *none)
|
||||||
return call_utc_tzinfo_method(tzinfo, "utcoffset", tzinfoarg, none);
|
return call_utc_tzinfo_method(tzinfo, "utcoffset", tzinfoarg, none);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static PyObject *new_delta(int d, int sec, int usec, int normalize);
|
||||||
|
|
||||||
|
/* Call tzinfo.name(self) and return the offset as a timedelta or None. */
|
||||||
|
static PyObject *
|
||||||
|
offset_as_timedelta(PyObject *self, PyObject *tzinfo, char *name) {
|
||||||
|
PyObject *result;
|
||||||
|
|
||||||
|
if (tzinfo == Py_None) {
|
||||||
|
result = Py_None;
|
||||||
|
Py_INCREF(result);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
int none;
|
||||||
|
int offset = call_utc_tzinfo_method(tzinfo, name, self, &none);
|
||||||
|
if (offset < 0 && PyErr_Occurred())
|
||||||
|
return NULL;
|
||||||
|
if (none) {
|
||||||
|
result = Py_None;
|
||||||
|
Py_INCREF(result);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
result = new_delta(0, offset * 60, 0, 1);
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
/* 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()
|
||||||
|
@ -663,22 +733,29 @@ call_dst(PyObject *tzinfo, PyObject *tzinfoarg, int *none)
|
||||||
return call_utc_tzinfo_method(tzinfo, "dst", tzinfoarg, none);
|
return call_utc_tzinfo_method(tzinfo, "dst", tzinfoarg, none);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Call tzinfo.tzname(tzinfoarg), and return the result. tzinfo must be
|
/* Call tzinfo.tzname(self), and return the result. tzinfo must be
|
||||||
* an instance of the tzinfo class. If tzname() doesn't return None or
|
* an instance of the tzinfo class or None. If tzinfo isn't None, and
|
||||||
* a string, TypeError is raised and this returns NULL.
|
* tzname() doesn't return None ora string, TypeError is raised and this
|
||||||
|
* returns NULL.
|
||||||
*/
|
*/
|
||||||
static PyObject *
|
static PyObject *
|
||||||
call_tzname(PyObject *tzinfo, PyObject *tzinfoarg)
|
call_tzname(PyObject *self, PyObject *tzinfo)
|
||||||
{
|
{
|
||||||
PyObject *result;
|
PyObject *result;
|
||||||
|
|
||||||
|
assert(self != NULL);
|
||||||
assert(tzinfo != NULL);
|
assert(tzinfo != NULL);
|
||||||
assert(PyTZInfo_Check(tzinfo));
|
assert(check_tzinfo_subclass(tzinfo) >= 0);
|
||||||
assert(tzinfoarg != NULL);
|
|
||||||
|
|
||||||
result = PyObject_CallMethod(tzinfo, "tzname", "O", tzinfoarg);
|
if (tzinfo == Py_None) {
|
||||||
if (result != NULL && result != Py_None && !PyString_Check(result)) {
|
result = Py_None;
|
||||||
PyErr_Format(PyExc_TypeError, ".tzinfo.tzname() must "
|
Py_INCREF(result);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
result = PyObject_CallMethod(tzinfo, "tzname", "O", self);
|
||||||
|
|
||||||
|
if (result != NULL && result != Py_None && ! PyString_Check(result)) {
|
||||||
|
PyErr_Format(PyExc_TypeError, "tzinfo.tzname() must "
|
||||||
"return None or a string, not '%s'",
|
"return None or a string, not '%s'",
|
||||||
result->ob_type->tp_name);
|
result->ob_type->tp_name);
|
||||||
Py_DECREF(result);
|
Py_DECREF(result);
|
||||||
|
@ -699,7 +776,7 @@ typedef enum {
|
||||||
/* date,
|
/* date,
|
||||||
* datetime,
|
* datetime,
|
||||||
* datetimetz with None tzinfo,
|
* datetimetz with None tzinfo,
|
||||||
* datetimetz where utcoffset() return None
|
* datetimetz where utcoffset() returns None
|
||||||
* time,
|
* time,
|
||||||
* timetz with None tzinfo,
|
* timetz with None tzinfo,
|
||||||
* timetz where utcoffset() returns None
|
* timetz where utcoffset() returns None
|
||||||
|
@ -919,8 +996,8 @@ wrap_strftime(PyObject *object, PyObject *format, PyObject *timetuple)
|
||||||
Zreplacement = PyString_FromString("");
|
Zreplacement = PyString_FromString("");
|
||||||
if (Zreplacement == NULL) goto Done;
|
if (Zreplacement == NULL) goto Done;
|
||||||
if (tzinfo != Py_None && tzinfo != NULL) {
|
if (tzinfo != Py_None && tzinfo != NULL) {
|
||||||
PyObject *temp = call_tzname(tzinfo,
|
PyObject *temp = call_tzname(object,
|
||||||
object);
|
tzinfo);
|
||||||
if (temp == NULL) goto Done;
|
if (temp == NULL) goto Done;
|
||||||
if (temp != Py_None) {
|
if (temp != Py_None) {
|
||||||
assert(PyString_Check(temp));
|
assert(PyString_Check(temp));
|
||||||
|
@ -3917,38 +3994,24 @@ timetz_dealloc(PyDateTime_TimeTZ *self)
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Indirect access to tzinfo methods. One more "convenience function" and
|
* Indirect access to tzinfo methods.
|
||||||
* it won't be possible to find the useful methods anymore <0.5 wink>.
|
|
||||||
*/
|
*/
|
||||||
|
|
||||||
static PyObject *
|
|
||||||
timetz_convienience(PyDateTime_TimeTZ *self, char *name)
|
|
||||||
{
|
|
||||||
PyObject *result;
|
|
||||||
|
|
||||||
if (self->tzinfo == Py_None) {
|
|
||||||
result = Py_None;
|
|
||||||
Py_INCREF(result);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
result = PyObject_CallMethod(self->tzinfo, name, "O", self);
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* These are all METH_NOARGS, so don't need to check the arglist. */
|
/* These are all METH_NOARGS, so don't need to check the arglist. */
|
||||||
static PyObject *
|
static PyObject *
|
||||||
timetz_utcoffset(PyDateTime_TimeTZ *self, PyObject *unused) {
|
timetz_utcoffset(PyDateTime_TimeTZ *self, PyObject *unused) {
|
||||||
return timetz_convienience(self, "utcoffset");
|
return offset_as_timedelta((PyObject *)self, self->tzinfo,
|
||||||
}
|
"utcoffset");
|
||||||
|
|
||||||
static PyObject *
|
|
||||||
timetz_tzname(PyDateTime_TimeTZ *self, PyObject *unused) {
|
|
||||||
return timetz_convienience(self, "tzname");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static PyObject *
|
static PyObject *
|
||||||
timetz_dst(PyDateTime_TimeTZ *self, PyObject *unused) {
|
timetz_dst(PyDateTime_TimeTZ *self, PyObject *unused) {
|
||||||
return timetz_convienience(self, "dst");
|
return offset_as_timedelta((PyObject *)self, self->tzinfo, "dst");
|
||||||
|
}
|
||||||
|
|
||||||
|
static PyObject *
|
||||||
|
timetz_tzname(PyDateTime_TimeTZ *self, PyObject *unused) {
|
||||||
|
return call_tzname((PyObject *)self, self->tzinfo);
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
|
@ -4325,37 +4388,21 @@ datetimetz_dealloc(PyDateTime_DateTimeTZ *self)
|
||||||
* Indirect access to tzinfo methods.
|
* Indirect access to tzinfo methods.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
/* Internal helper.
|
|
||||||
* Call a tzinfo object's method, or return None if tzinfo is None.
|
|
||||||
*/
|
|
||||||
static PyObject *
|
|
||||||
datetimetz_convienience(PyDateTime_DateTimeTZ *self, char *name)
|
|
||||||
{
|
|
||||||
PyObject *result;
|
|
||||||
|
|
||||||
if (self->tzinfo == Py_None) {
|
|
||||||
result = Py_None;
|
|
||||||
Py_INCREF(result);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
result = PyObject_CallMethod(self->tzinfo, name, "O", self);
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* These are all METH_NOARGS, so don't need to check the arglist. */
|
/* These are all METH_NOARGS, so don't need to check the arglist. */
|
||||||
static PyObject *
|
static PyObject *
|
||||||
datetimetz_utcoffset(PyDateTime_DateTimeTZ *self, PyObject *unused) {
|
datetimetz_utcoffset(PyDateTime_DateTimeTZ *self, PyObject *unused) {
|
||||||
return datetimetz_convienience(self, "utcoffset");
|
return offset_as_timedelta((PyObject *)self, self->tzinfo,
|
||||||
}
|
"utcoffset");
|
||||||
|
|
||||||
static PyObject *
|
|
||||||
datetimetz_tzname(PyDateTime_DateTimeTZ *self, PyObject *unused) {
|
|
||||||
return datetimetz_convienience(self, "tzname");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static PyObject *
|
static PyObject *
|
||||||
datetimetz_dst(PyDateTime_DateTimeTZ *self, PyObject *unused) {
|
datetimetz_dst(PyDateTime_DateTimeTZ *self, PyObject *unused) {
|
||||||
return datetimetz_convienience(self, "dst");
|
return offset_as_timedelta((PyObject *)self, self->tzinfo, "dst");
|
||||||
|
}
|
||||||
|
|
||||||
|
static PyObject *
|
||||||
|
datetimetz_tzname(PyDateTime_DateTimeTZ *self, PyObject *unused) {
|
||||||
|
return call_tzname((PyObject *)self, self->tzinfo);
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
|
|
Loading…
Reference in New Issue