self.failUnless(not cls(0))
self.failUnless(not cls())
-
-class TestTimeTZ(TestTime):
-
- theclass = timetz
-
- def test_empty(self):
- t = self.theclass()
- self.assertEqual(t.hour, 0)
- self.assertEqual(t.minute, 0)
- self.assertEqual(t.second, 0)
- self.assertEqual(t.microsecond, 0)
- self.failUnless(t.tzinfo is None)
+# 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):
def test_bad_tzinfo_classes(self):
- tz = self.theclass
- self.assertRaises(TypeError, tz, tzinfo=12)
+ 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, tz, tzinfo=NiceTry)
+ self.assertRaises(TypeError, cls, 1, 1, 1, tzinfo=NiceTry)
class BetterTry(tzinfo):
def __init__(self): pass
def utcoffset(self, dt): pass
b = BetterTry()
- t = tz(tzinfo=b)
+ 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
+
+ def test_empty(self):
+ t = self.theclass()
+ self.assertEqual(t.hour, 0)
+ self.assertEqual(t.minute, 0)
+ self.assertEqual(t.second, 0)
+ self.assertEqual(t.microsecond, 0)
+ self.failUnless(t.tzinfo is None)
+
def test_zones(self):
est = FixedOffset(-300, "EST", 1)
utc = FixedOffset(0, "UTC", -2)
self.failUnless(t4.tzinfo is None)
self.assertEqual(t5.tzinfo, utc)
- self.assertEqual(t1.utcoffset(), -300)
- self.assertEqual(t2.utcoffset(), 0)
- self.assertEqual(t3.utcoffset(), 60)
+ self.assertEqual(t1.utcoffset(), timedelta(minutes=-300))
+ self.assertEqual(t2.utcoffset(), timedelta(minutes=0))
+ self.assertEqual(t3.utcoffset(), timedelta(minutes=60))
self.failUnless(t4.utcoffset() is None)
self.assertRaises(TypeError, t1.utcoffset, "no args")
self.failUnless(t4.tzname() is None)
self.assertRaises(TypeError, t1.tzname, "no args")
- self.assertEqual(t1.dst(), 1)
- self.assertEqual(t2.dst(), -2)
- self.assertEqual(t3.dst(), 3)
+ self.assertEqual(t1.dst(), timedelta(minutes=1))
+ self.assertEqual(t2.dst(), timedelta(minutes=-2))
+ self.assertEqual(t3.dst(), timedelta(minutes=3))
self.failUnless(t4.dst() is None)
self.assertRaises(TypeError, t1.dst, "no args")
t2 = self.theclass(23, 48, 6, 100, tzinfo=FixedOffset(-1010, ""))
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):
import pickle, cPickle
derived.__setstate__(state)
self.assertEqual(orig, derived)
self.failUnless(isinstance(derived.tzinfo, PicklableFixedOffset))
- self.assertEqual(derived.utcoffset(), -300)
+ self.assertEqual(derived.utcoffset(), timedelta(minutes=-300))
self.assertEqual(derived.tzname(), 'cookie')
for pickler in pickle, cPickle:
self.assertEqual(orig, derived)
self.failUnless(isinstance(derived.tzinfo,
PicklableFixedOffset))
- self.assertEqual(derived.utcoffset(), -300)
+ self.assertEqual(derived.utcoffset(), timedelta(minutes=-300))
self.assertEqual(derived.tzname(), 'cookie')
def test_more_bool(self):
t = cls(0, tzinfo=FixedOffset(-24*60, ""))
self.assertRaises(ValueError, lambda: bool(t))
-class TestDateTimeTZ(TestDateTime):
-
+class TestDateTimeTZ(TestDateTime, TZInfoBase):
theclass = datetimetz
def test_trivial(self):
t2 = self.theclass(2, 2, 2, tzinfo=FixedOffset(0, ""))
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):
import pickle, cPickle
derived.__setstate__(state)
self.assertEqual(orig, derived)
self.failUnless(isinstance(derived.tzinfo, PicklableFixedOffset))
- self.assertEqual(derived.utcoffset(), -300)
+ self.assertEqual(derived.utcoffset(), timedelta(minutes=-300))
self.assertEqual(derived.tzname(), 'cookie')
for pickler in pickle, cPickle:
self.assertEqual(orig, derived)
self.failUnless(isinstance(derived.tzinfo,
PicklableFixedOffset))
- self.assertEqual(derived.utcoffset(), -300)
+ self.assertEqual(derived.utcoffset(), timedelta(minutes=-300))
self.assertEqual(derived.tzname(), 'cookie')
def test_extreme_hashes(self):
self.assertEqual(t1.tzinfo, est)
self.assertEqual(t2.tzinfo, utc)
self.assertEqual(t3.tzinfo, met)
- self.assertEqual(t1.utcoffset(), -300)
- self.assertEqual(t2.utcoffset(), 0)
- self.assertEqual(t3.utcoffset(), 60)
+ self.assertEqual(t1.utcoffset(), timedelta(minutes=-300))
+ self.assertEqual(t2.utcoffset(), timedelta(minutes=0))
+ self.assertEqual(t3.utcoffset(), timedelta(minutes=60))
self.assertEqual(t1.tzname(), "EST")
self.assertEqual(t2.tzname(), "UTC")
self.assertEqual(t3.tzname(), "MET")
# (nowaware base - nowawareplus base) +
# (nowawareplus offset - nowaware offset) =
# -delta + nowawareplus offset - nowaware offset
- expected = timedelta(minutes=nowawareplus.utcoffset() -
- nowaware.utcoffset()) - delta
+ expected = nowawareplus.utcoffset() - nowaware.utcoffset() - delta
self.assertEqual(got, expected)
# Try max possible difference.
another = meth(off42)
again = meth(tzinfo=off42)
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.
self.assertRaises(TypeError, meth, 16)
self.assertRaises(TypeError, meth, tzinfo=16)
another = meth(ts, off42)
again = meth(ts, tzinfo=off42)
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.
self.assertRaises(TypeError, meth, ts, 16)
self.assertRaises(TypeError, meth, ts, tzinfo=16)
* 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
* return NULL, which is NOT AN ERROR. There are no error returns here,
* and the caller must not decref the result.
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.
* 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, TypeError is raised and this returns -1.
- * If it does return an int or long, but is outside the valid range for
- * a UTC minute offset, ValueError is raised and this returns -1.
+ * 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.
* Else *none is set to 0 and the integer method result is returned.
*/
static int
assert(tzinfoarg != NULL);
*none = 0;
- u = PyObject_CallMethod(tzinfo, name, "O", tzinfoarg);
+ u = call_tzinfo_method(tzinfoarg, tzinfo, name);
if (u == NULL)
return -1;
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)
+ 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 {
PyErr_Format(PyExc_TypeError,
- "tzinfo.%s() must return None or int or long",
- name);
+ "tzinfo.%s() must return None, integer or "
+ "timedelta, not '%s'",
+ name, u->ob_type->tp_name);
goto Done;
}
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
* 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()
return call_utc_tzinfo_method(tzinfo, "dst", tzinfoarg, none);
}
-/* Call tzinfo.tzname(tzinfoarg), and return the result. tzinfo must be
- * an instance of the tzinfo class. If tzname() doesn't return None or
- * a string, TypeError is raised and this returns NULL.
+/* Call tzinfo.tzname(self), and return the result. tzinfo must be
+ * an instance of the tzinfo class or None. If tzinfo isn't None, and
+ * tzname() doesn't return None ora string, TypeError is raised and this
+ * returns NULL.
*/
static PyObject *
-call_tzname(PyObject *tzinfo, PyObject *tzinfoarg)
+call_tzname(PyObject *self, PyObject *tzinfo)
{
PyObject *result;
+ assert(self != NULL);
assert(tzinfo != NULL);
- assert(PyTZInfo_Check(tzinfo));
- assert(tzinfoarg != NULL);
+ assert(check_tzinfo_subclass(tzinfo) >= 0);
- result = PyObject_CallMethod(tzinfo, "tzname", "O", tzinfoarg);
- if (result != NULL && result != Py_None && !PyString_Check(result)) {
- PyErr_Format(PyExc_TypeError, ".tzinfo.tzname() must "
+ if (tzinfo == Py_None) {
+ result = Py_None;
+ 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'",
result->ob_type->tp_name);
Py_DECREF(result);
/* date,
* datetime,
* datetimetz with None tzinfo,
- * datetimetz where utcoffset() return None
+ * datetimetz where utcoffset() returns None
* time,
* timetz with None tzinfo,
* timetz where utcoffset() returns None
Zreplacement = PyString_FromString("");
if (Zreplacement == NULL) goto Done;
if (tzinfo != Py_None && tzinfo != NULL) {
- PyObject *temp = call_tzname(tzinfo,
- object);
+ PyObject *temp = call_tzname(object,
+ tzinfo);
if (temp == NULL) goto Done;
if (temp != Py_None) {
assert(PyString_Check(temp));
}
/*
- * Indirect access to tzinfo methods. One more "convenience function" and
- * it won't be possible to find the useful methods anymore <0.5 wink>.
+ * Indirect access to tzinfo methods.
*/
-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. */
static PyObject *
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");
+timetz_dst(PyDateTime_TimeTZ *self, PyObject *unused) {
+ return offset_as_timedelta((PyObject *)self, self->tzinfo, "dst");
}
static PyObject *
-timetz_dst(PyDateTime_TimeTZ *self, PyObject *unused) {
- return timetz_convienience(self, "dst");
+timetz_tzname(PyDateTime_TimeTZ *self, PyObject *unused) {
+ return call_tzname((PyObject *)self, self->tzinfo);
}
/*
* 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. */
static PyObject *
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");
+datetimetz_dst(PyDateTime_DateTimeTZ *self, PyObject *unused) {
+ return offset_as_timedelta((PyObject *)self, self->tzinfo, "dst");
}
static PyObject *
-datetimetz_dst(PyDateTime_DateTimeTZ *self, PyObject *unused) {
- return datetimetz_convienience(self, "dst");
+datetimetz_tzname(PyDateTime_DateTimeTZ *self, PyObject *unused) {
+ return call_tzname((PyObject *)self, self->tzinfo);
}
/*