]> granicus.if.org Git - python/commitdiff
datetime.timedelta is now subclassable in Python. The new test shows
authorTim Peters <tim.peters@gmail.com>
Sat, 17 May 2003 15:57:00 +0000 (15:57 +0000)
committerTim Peters <tim.peters@gmail.com>
Sat, 17 May 2003 15:57:00 +0000 (15:57 +0000)
one good use:  a subclass adding a method to express the duration as
a number of hours (or minutes, or whatever else you want to add).  The
native breakdown into days+seconds+us is often clumsy.  Incidentally
moved a large chunk of object-initialization code closer to the top of
the file, to avoid worse forward-reference trickery.

Lib/test/test_datetime.py
Misc/NEWS
Modules/datetimemodule.c

index c4978f3777768d867ba7a0e46b2db5c4bdca071a..cca0c9d9c59f96333e568e8552d68bf1bd20a38f 100644 (file)
@@ -443,6 +443,37 @@ class TestTimeDelta(HarmlessMixedComparison):
         self.failUnless(timedelta(microseconds=1))
         self.failUnless(not timedelta(0))
 
+    def test_subclass_timedelta(self):
+
+        class T(timedelta):
+            def from_td(td):
+                return T(td.days, td.seconds, td.microseconds)
+            from_td = staticmethod(from_td)
+
+            def as_hours(self):
+                sum = (self.days * 24 +
+                       self.seconds / 3600.0 +
+                       self.microseconds / 3600e6)
+                return round(sum)
+
+        t1 = T(days=1)
+        self.assert_(type(t1) is T)
+        self.assertEqual(t1.as_hours(), 24)
+
+        t2 = T(days=-1, seconds=-3600)
+        self.assert_(type(t2) is T)
+        self.assertEqual(t2.as_hours(), -25)
+
+        t3 = t1 + t2
+        self.assert_(type(t3) is timedelta)
+        t4 = T.from_td(t3)
+        self.assert_(type(t4) is T)
+        self.assertEqual(t3.days, t4.days)
+        self.assertEqual(t3.seconds, t4.seconds)
+        self.assertEqual(t3.microseconds, t4.microseconds)
+        self.assertEqual(str(t3), str(t4))
+        self.assertEqual(t4.as_hours(), -1)
+
 #############################################################################
 # date tests
 
index e182845729f1399e5399769750e1ffba08bbf20b..02f845da0fdc890379599c0aeabeb726cf32a7f5 100644 (file)
--- a/Misc/NEWS
+++ b/Misc/NEWS
@@ -26,8 +26,8 @@ Core and builtins
 Extension modules
 -----------------
 
-- The datetime.datetime and datetime.time classes are now properly
-  subclassable.
+- The datetime module classes datetime, time, and timedelta are now
+  properly subclassable.
 
 - _tkinter.{get|set}busywaitinterval was added.
 
index 36793382fe5c82b8145e5647d3f345c0bfd42820..e85c955dc566888714e81c5200cf4c9359e794b6 100644 (file)
@@ -561,6 +561,168 @@ normalize_datetime(int *year, int *month, int *day,
        return normalize_date(year, month, day);
 }
 
+/* ---------------------------------------------------------------------------
+ * Basic object allocation:  tp_alloc implementations.  These allocate
+ * Python objects of the right size and type, and do the Python object-
+ * initialization bit.  If there's not enough memory, they return NULL after
+ * setting MemoryError.  All data members remain uninitialized trash.
+ *
+ * We abuse the tp_alloc "nitems" argument to communicate whether a tzinfo
+ * member is needed.  This is ugly.
+ */
+
+static PyObject *
+time_alloc(PyTypeObject *type, int aware)
+{
+       PyObject *self;
+
+       self = (PyObject *)
+               PyObject_MALLOC(aware ?
+                               sizeof(PyDateTime_Time) :
+                               sizeof(_PyDateTime_BaseTime));
+       if (self == NULL)
+               return (PyObject *)PyErr_NoMemory();
+       PyObject_INIT(self, type);
+       return self;
+}
+
+static PyObject *
+datetime_alloc(PyTypeObject *type, int aware)
+{
+       PyObject *self;
+
+       self = (PyObject *)
+               PyObject_MALLOC(aware ?
+                               sizeof(PyDateTime_DateTime) :
+                               sizeof(_PyDateTime_BaseDateTime));
+       if (self == NULL)
+               return (PyObject *)PyErr_NoMemory();
+       PyObject_INIT(self, type);
+       return self;
+}
+
+/* ---------------------------------------------------------------------------
+ * Helpers for setting object fields.  These work on pointers to the
+ * appropriate base class.
+ */
+
+/* For date and datetime. */
+static void
+set_date_fields(PyDateTime_Date *self, int y, int m, int d)
+{
+       self->hashcode = -1;
+       SET_YEAR(self, y);
+       SET_MONTH(self, m);
+       SET_DAY(self, d);
+}
+
+/* ---------------------------------------------------------------------------
+ * Create various objects, mostly without range checking.
+ */
+
+/* Create a date instance with no range checking. */
+static PyObject *
+new_date_ex(int year, int month, int day, PyTypeObject *type)
+{
+       PyDateTime_Date *self;
+
+       self = (PyDateTime_Date *) (type->tp_alloc(type, 0));
+       if (self != NULL)
+               set_date_fields(self, year, month, day);
+       return (PyObject *) self;
+}
+
+#define new_date(year, month, day) \
+       new_date_ex(year, month, day, &PyDateTime_DateType)
+
+/* Create a datetime instance with no range checking. */
+static PyObject *
+new_datetime_ex(int year, int month, int day, int hour, int minute,
+            int second, int usecond, PyObject *tzinfo, PyTypeObject *type)
+{
+       PyDateTime_DateTime *self;
+       char aware = tzinfo != Py_None;
+
+       self = (PyDateTime_DateTime *) (type->tp_alloc(type, aware));
+       if (self != NULL) {
+               self->hastzinfo = aware;
+               set_date_fields((PyDateTime_Date *)self, year, month, day);
+               DATE_SET_HOUR(self, hour);
+               DATE_SET_MINUTE(self, minute);
+               DATE_SET_SECOND(self, second);
+               DATE_SET_MICROSECOND(self, usecond);
+               if (aware) {
+                       Py_INCREF(tzinfo);
+                       self->tzinfo = tzinfo;
+               }
+       }
+       return (PyObject *)self;
+}
+
+#define new_datetime(y, m, d, hh, mm, ss, us, tzinfo)          \
+       new_datetime_ex(y, m, d, hh, mm, ss, us, tzinfo,        \
+                       &PyDateTime_DateTimeType)
+
+/* Create a time instance with no range checking. */
+static PyObject *
+new_time_ex(int hour, int minute, int second, int usecond,
+           PyObject *tzinfo, PyTypeObject *type)
+{
+       PyDateTime_Time *self;
+       char aware = tzinfo != Py_None;
+
+       self = (PyDateTime_Time *) (type->tp_alloc(type, aware));
+       if (self != NULL) {
+               self->hastzinfo = aware;
+               self->hashcode = -1;
+               TIME_SET_HOUR(self, hour);
+               TIME_SET_MINUTE(self, minute);
+               TIME_SET_SECOND(self, second);
+               TIME_SET_MICROSECOND(self, usecond);
+               if (aware) {
+                       Py_INCREF(tzinfo);
+                       self->tzinfo = tzinfo;
+               }
+       }
+       return (PyObject *)self;
+}
+
+#define new_time(hh, mm, ss, us, tzinfo)               \
+       new_time_ex(hh, mm, ss, us, tzinfo, &PyDateTime_TimeType)
+
+/* Create a timedelta instance.  Normalize the members iff normalize is
+ * true.  Passing false is a speed optimization, if you know for sure
+ * that seconds and microseconds are already in their proper ranges.  In any
+ * case, raises OverflowError and returns NULL if the normalized days is out
+ * of range).
+ */
+static PyObject *
+new_delta_ex(int days, int seconds, int microseconds, int normalize,
+            PyTypeObject *type)
+{
+       PyDateTime_Delta *self;
+
+       if (normalize)
+               normalize_d_s_us(&days, &seconds, &microseconds);
+       assert(0 <= seconds && seconds < 24*3600);
+       assert(0 <= microseconds && microseconds < 1000000);
+
+       if (check_delta_day_range(days) < 0)
+               return NULL;
+
+       self = (PyDateTime_Delta *) (type->tp_alloc(type, 0));
+       if (self != NULL) {
+               self->hashcode = -1;
+               SET_TD_DAYS(self, days);
+               SET_TD_SECONDS(self, seconds);
+               SET_TD_MICROSECONDS(self, microseconds);
+       }
+       return (PyObject *) self;
+}
+
+#define new_delta(d, s, us, normalize) \
+       new_delta_ex(d, s, us, normalize, &PyDateTime_DeltaType)
+
 /* ---------------------------------------------------------------------------
  * tzinfo helpers.
  */
@@ -695,8 +857,6 @@ call_utcoffset(PyObject *tzinfo, PyObject *tzinfoarg, int *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(tzinfoarg), and return the offset as a timedelta or None.
  */
 static PyObject *
@@ -1234,165 +1394,6 @@ cmperror(PyObject *a, PyObject *b)
        return NULL;
 }
 
-/* ---------------------------------------------------------------------------
- * Basic object allocation:  tp_alloc implementatiosn.  These allocate
- * Python objects of the right size and type, and do the Python object-
- * initialization bit.  If there's not enough memory, they return NULL after
- * setting MemoryError.  All data members remain uninitialized trash.
- *
- * We abuse the tp_alloc "nitems" argument to communicate whether a tzinfo
- * member is needed.  This is ugly.
- */
-
-static PyObject *
-time_alloc(PyTypeObject *type, int aware)
-{
-       PyObject *self;
-
-       self = (PyObject *)
-               PyObject_MALLOC(aware ?
-                               sizeof(PyDateTime_Time) :
-                               sizeof(_PyDateTime_BaseTime));
-       if (self == NULL)
-               return (PyObject *)PyErr_NoMemory();
-       PyObject_INIT(self, type);
-       return self;
-}
-
-static PyObject *
-datetime_alloc(PyTypeObject *type, int aware)
-{
-       PyObject *self;
-
-       self = (PyObject *)
-               PyObject_MALLOC(aware ?
-                               sizeof(PyDateTime_DateTime) :
-                               sizeof(_PyDateTime_BaseDateTime));
-       if (self == NULL)
-               return (PyObject *)PyErr_NoMemory();
-       PyObject_INIT(self, type);
-       return self;
-}
-
-/* ---------------------------------------------------------------------------
- * Helpers for setting object fields.  These work on pointers to the
- * appropriate base class.
- */
-
-/* For date and datetime. */
-static void
-set_date_fields(PyDateTime_Date *self, int y, int m, int d)
-{
-       self->hashcode = -1;
-       SET_YEAR(self, y);
-       SET_MONTH(self, m);
-       SET_DAY(self, d);
-}
-
-/* ---------------------------------------------------------------------------
- * Create various objects, mostly without range checking.
- */
-
-/* Create a date instance with no range checking. */
-static PyObject *
-new_date_ex(int year, int month, int day, PyTypeObject *type)
-{
-       PyDateTime_Date *self;
-
-       self = (PyDateTime_Date *) (type->tp_alloc(type, 0));
-       if (self != NULL)
-               set_date_fields(self, year, month, day);
-       return (PyObject *) self;
-}
-
-#define new_date(year, month, day) \
-       new_date_ex(year, month, day, &PyDateTime_DateType)
-
-/* Create a datetime instance with no range checking. */
-static PyObject *
-new_datetime_ex(int year, int month, int day, int hour, int minute,
-            int second, int usecond, PyObject *tzinfo, PyTypeObject *type)
-{
-       PyDateTime_DateTime *self;
-       char aware = tzinfo != Py_None;
-
-       self = (PyDateTime_DateTime *) (type->tp_alloc(type, aware));
-       if (self != NULL) {
-               self->hastzinfo = aware;
-               set_date_fields((PyDateTime_Date *)self, year, month, day);
-               DATE_SET_HOUR(self, hour);
-               DATE_SET_MINUTE(self, minute);
-               DATE_SET_SECOND(self, second);
-               DATE_SET_MICROSECOND(self, usecond);
-               if (aware) {
-                       Py_INCREF(tzinfo);
-                       self->tzinfo = tzinfo;
-               }
-       }
-       return (PyObject *)self;
-}
-
-#define new_datetime(y, m, d, hh, mm, ss, us, tzinfo)          \
-       new_datetime_ex(y, m, d, hh, mm, ss, us, tzinfo,        \
-                       &PyDateTime_DateTimeType)
-
-/* Create a time instance with no range checking. */
-static PyObject *
-new_time_ex(int hour, int minute, int second, int usecond,
-           PyObject *tzinfo, PyTypeObject *type)
-{
-       PyDateTime_Time *self;
-       char aware = tzinfo != Py_None;
-
-       self = (PyDateTime_Time *) (type->tp_alloc(type, aware));
-       if (self != NULL) {
-               self->hastzinfo = aware;
-               self->hashcode = -1;
-               TIME_SET_HOUR(self, hour);
-               TIME_SET_MINUTE(self, minute);
-               TIME_SET_SECOND(self, second);
-               TIME_SET_MICROSECOND(self, usecond);
-               if (aware) {
-                       Py_INCREF(tzinfo);
-                       self->tzinfo = tzinfo;
-               }
-       }
-       return (PyObject *)self;
-}
-
-#define new_time(hh, mm, ss, us, tzinfo)               \
-       new_time_ex(hh, mm, ss, us, tzinfo, &PyDateTime_TimeType)
-
-/* Create a timedelta instance.  Normalize the members iff normalize is
- * true.  Passing false is a speed optimization, if you know for sure
- * that seconds and microseconds are already in their proper ranges.  In any
- * case, raises OverflowError and returns NULL if the normalized days is out
- * of range).
- */
-static PyObject *
-new_delta(int days, int seconds, int microseconds, int normalize)
-{
-       PyDateTime_Delta *self;
-
-       if (normalize)
-               normalize_d_s_us(&days, &seconds, &microseconds);
-       assert(0 <= seconds && seconds < 24*3600);
-       assert(0 <= microseconds && microseconds < 1000000);
-
-       if (check_delta_day_range(days) < 0)
-               return NULL;
-
-       self = PyObject_New(PyDateTime_Delta, &PyDateTime_DeltaType);
-       if (self != NULL) {
-               self->hashcode = -1;
-               SET_TD_DAYS(self, days);
-               SET_TD_SECONDS(self, seconds);
-               SET_TD_MICROSECONDS(self, microseconds);
-       }
-       return (PyObject *) self;
-}
-
-
 /* ---------------------------------------------------------------------------
  * Cached Python objects; these are set by the module init function.
  */
@@ -1472,7 +1473,7 @@ Done:
 /* Convert a number of us (as a Python int or long) to a timedelta.
  */
 static PyObject *
-microseconds_to_delta(PyObject *pyus)
+microseconds_to_delta_ex(PyObject *pyus, PyTypeObject *type)
 {
        int us;
        int s;
@@ -1542,7 +1543,7 @@ microseconds_to_delta(PyObject *pyus)
                                "large to fit in a C int");
                goto Done;
        }
-       result = new_delta(d, s, us, 0);
+       result = new_delta_ex(d, s, us, 0, type);
 
 Done:
        Py_XDECREF(tuple);
@@ -1550,6 +1551,9 @@ Done:
        return result;
 }
 
+#define microseconds_to_delta(pymicros)        \
+       microseconds_to_delta_ex(pymicros, &PyDateTime_DeltaType)
+
 static PyObject *
 multiply_int_timedelta(PyObject *intobj, PyDateTime_Delta *delta)
 {
@@ -1924,7 +1928,7 @@ delta_new(PyTypeObject *type, PyObject *args, PyObject *kw)
                CLEANUP;
        }
 
-       self = microseconds_to_delta(x);
+       self = microseconds_to_delta_ex(x, type);
        Py_DECREF(x);
 Done:
        return self;
@@ -2110,7 +2114,8 @@ static PyTypeObject PyDateTime_DeltaType = {
        PyObject_GenericGetAttr,                        /* tp_getattro */
        0,                                              /* tp_setattro */
        0,                                              /* tp_as_buffer */
-       Py_TPFLAGS_DEFAULT | Py_TPFLAGS_CHECKTYPES,     /* tp_flags */
+       Py_TPFLAGS_DEFAULT | Py_TPFLAGS_CHECKTYPES |
+               Py_TPFLAGS_BASETYPE,                    /* tp_flags */
        delta_doc,                                      /* tp_doc */
        0,                                              /* tp_traverse */
        0,                                              /* tp_clear */