mirror of
https://github.com/python/cpython.git
synced 2024-11-28 08:20:55 +01:00
datetime.timedelta is now subclassable in Python. The new test shows
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.
This commit is contained in:
parent
108c40c74c
commit
b0c854d6a7
@ -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
|
||||
|
||||
|
@ -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.
|
||||
|
||||
|
@ -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, µseconds);
|
||||
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, µseconds);
|
||||
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 */
|
||||
|
Loading…
Reference in New Issue
Block a user