From 37f398282bf74b11e6167f7c7af75960e553dab9 Mon Sep 17 00:00:00 2001 From: Tim Peters Date: Fri, 10 Jan 2003 03:49:02 +0000 Subject: Got rid of the timetz type entirely. This was a bit trickier than I hoped it would be, but not too bad. A test had to change: time.__setstate__() can no longer add a non-None tzinfo member to a time object that didn't already have one, since storage for a tzinfo member doesn't exist in that case. --- Include/datetime.h | 29 +- Lib/test/test_datetime.py | 2 +- Modules/datetimemodule.c | 892 +++++++++++++++++++--------------------------- 3 files changed, 394 insertions(+), 529 deletions(-) diff --git a/Include/datetime.h b/Include/datetime.h index 7bfbf0b..5c31162 100644 --- a/Include/datetime.h +++ b/Include/datetime.h @@ -27,6 +27,11 @@ /* # of bytes for year, month, day, hour, minute, second, and usecond. */ #define _PyDateTime_DATETIME_DATASIZE 10 +#define _PyTZINFO_HEAD \ + PyObject_HEAD \ + long hashcode; \ + char hastzinfo; /* boolean flag */ + typedef struct { PyObject_HEAD @@ -49,20 +54,23 @@ typedef struct PyObject *tzinfo; } PyDateTime_DateTimeTZ; + + +#define _PyDateTime_TIMEHEAD \ + _PyTZINFO_HEAD \ + unsigned char data[_PyDateTime_TIME_DATASIZE]; + typedef struct { - PyObject_HEAD - long hashcode; - unsigned char data[_PyDateTime_TIME_DATASIZE]; -} PyDateTime_Time; + _PyDateTime_TIMEHEAD +} _PyDateTime_BaseTime; /* hastzinfo false */ typedef struct { - PyObject_HEAD - long hashcode; - unsigned char data[_PyDateTime_TIME_DATASIZE]; + _PyDateTime_TIMEHEAD PyObject *tzinfo; -} PyDateTime_TimeTZ; +} PyDateTime_Time; /* hastzinfo true */ + typedef struct { @@ -92,7 +100,7 @@ typedef struct (((PyDateTime_DateTime*)o)->data[8] << 8) | \ ((PyDateTime_DateTime*)o)->data[9]) -/* Apply for time and timetz instances. */ +/* Apply for time instances. */ #define PyDateTime_TIME_GET_HOUR(o) (((PyDateTime_Time*)o)->data[0]) #define PyDateTime_TIME_GET_MINUTE(o) (((PyDateTime_Time*)o)->data[1]) #define PyDateTime_TIME_GET_SECOND(o) (((PyDateTime_Time*)o)->data[2]) @@ -113,9 +121,6 @@ typedef struct #define PyTime_Check(op) PyObject_TypeCheck(op, &PyDateTime_TimeType) #define PyTime_CheckExact(op) ((op)->ob_type == &PyDateTime_TimeType) -#define PyTimeTZ_Check(op) PyObject_TypeCheck(op, &PyDateTime_TimeTZType) -#define PyTimeTZ_CheckExact(op) ((op)->ob_type == &PyDateTime_TimeTZType) - #define PyDelta_Check(op) PyObject_TypeCheck(op, &PyDateTime_DeltaType) #define PyDelta_CheckExact(op) ((op)->ob_type == &PyDateTime_DeltaType) diff --git a/Lib/test/test_datetime.py b/Lib/test/test_datetime.py index 614fed6..0cda5d0 100644 --- a/Lib/test/test_datetime.py +++ b/Lib/test/test_datetime.py @@ -1857,7 +1857,7 @@ class TestTimeTZ(TestTime, TZInfoBase): tinfo = PicklableFixedOffset(-300, 'cookie') orig = self.theclass(5, 6, 7, tzinfo=tinfo) state = orig.__getstate__() - derived = self.theclass() + derived = self.theclass(tzinfo=FixedOffset(0, "UTC", 0)) derived.__setstate__(state) self.assertEqual(orig, derived) self.failUnless(isinstance(derived.tzinfo, PicklableFixedOffset)) diff --git a/Modules/datetimemodule.c b/Modules/datetimemodule.c index 642c11f..5840fb8 100644 --- a/Modules/datetimemodule.c +++ b/Modules/datetimemodule.c @@ -82,7 +82,6 @@ static PyTypeObject PyDateTime_DateTimeTZType; static PyTypeObject PyDateTime_DeltaType; static PyTypeObject PyDateTime_TimeType; static PyTypeObject PyDateTime_TZInfoType; -static PyTypeObject PyDateTime_TimeTZType; /* --------------------------------------------------------------------------- * Math utilities. @@ -607,8 +606,8 @@ get_tzinfo_member(PyObject *self) if (PyDateTimeTZ_Check(self)) tzinfo = ((PyDateTime_DateTimeTZ *)self)->tzinfo; - else if (PyTimeTZ_Check(self)) - tzinfo = ((PyDateTime_TimeTZ *)self)->tzinfo; + else if (PyTime_Check(self) && ((PyDateTime_Time *)self)->hastzinfo) + tzinfo = ((PyDateTime_Time *)self)->tzinfo; return tzinfo; } @@ -783,8 +782,8 @@ typedef enum { /* an exception has been set; the caller should pass it on */ OFFSET_ERROR, - /* type isn't date, datetime, datetimetz subclass, time, or - * timetz subclass + /* type isn't date, datetime, datetimetz subclass, or time + * subclass */ OFFSET_UNKNOWN, @@ -792,13 +791,13 @@ typedef enum { * datetime, * datetimetz with None tzinfo, * datetimetz where utcoffset() returns None - * time, - * timetz with None tzinfo, - * timetz where utcoffset() returns None + * time with !hastzinfo + * time with None tzinfo, + * time where utcoffset() returns None */ OFFSET_NAIVE, - /* timetz where utcoffset() doesn't return None, + /* time where utcoffset() doesn't return None, * datetimetz where utcoffset() doesn't return None */ OFFSET_AWARE, @@ -1264,7 +1263,7 @@ set_datetime_time_fields(PyDateTime_Date *self, int h, int m, int s, int us) DATE_SET_MICROSECOND(self, us); } -/* For time and timetz. */ +/* For time. */ static void set_time_fields(PyDateTime_Time *self, int h, int m, int s, int us) { @@ -1327,30 +1326,23 @@ new_datetimetz(int year, int month, int day, int hour, int minute, /* Create a time instance with no range checking. */ static PyObject * -new_time(int hour, int minute, int second, int usecond) +new_time(int hour, int minute, int second, int usecond, PyObject *tzinfo) { PyDateTime_Time *self; - - self = PyObject_New(PyDateTime_Time, &PyDateTime_TimeType); - if (self != NULL) - set_time_fields(self, hour, minute, second, usecond); - return (PyObject *) self; -} - -/* Create a timetz instance with no range checking. */ -static PyObject * -new_timetz(int hour, int minute, int second, int usecond, PyObject *tzinfo) -{ - PyDateTime_TimeTZ *self; - - self = PyObject_New(PyDateTime_TimeTZ, &PyDateTime_TimeTZType); - if (self != NULL) { - set_time_fields((PyDateTime_Time *)self, - hour, minute, second, usecond); + char aware = tzinfo != Py_None; + + self = (PyDateTime_Time *)PyObject_MALLOC(aware ? + sizeof(PyDateTime_Time) : + sizeof(_PyDateTime_BaseTime)); + if (self == NULL) + return PyErr_NoMemory(); + self->hastzinfo = aware; + set_time_fields(self, hour, minute, second, usecond); + if (aware) { Py_INCREF(tzinfo); self->tzinfo = tzinfo; } - return (PyObject *) self; + return (PyObject *)PyObject_INIT(self, &PyDateTime_TimeType); } /* Create a timedelta instance. Normalize the members iff normalize is @@ -1401,7 +1393,7 @@ static PyObject *seconds_per_day = NULL; /* 3600*24 as Python int */ static PyObject *date_unpickler_object = NULL; static PyObject *datetimetz_unpickler_object = NULL; static PyObject *tzinfo_unpickler_object = NULL; -static PyObject *timetz_unpickler_object = NULL; +static PyObject *time_unpickler_object = NULL; /* --------------------------------------------------------------------------- * Class implementations. @@ -2953,7 +2945,7 @@ datetime_utcnow(PyObject *cls, PyObject *dummy) } /* Return new datetime or datetimetz from date/datetime/datetimetz and - * time/timetz arguments. + * time arguments. */ static PyObject * datetime_combine(PyObject *cls, PyObject *args, PyObject *kw) @@ -2974,9 +2966,11 @@ datetime_combine(PyObject *cls, PyObject *args, PyObject *kw) TIME_GET_MINUTE(time), TIME_GET_SECOND(time), TIME_GET_MICROSECOND(time)); - if (result && PyTimeTZ_Check(time) && PyDateTimeTZ_Check(result)) { + if (result && + ((PyDateTime_Time *)time)->hastzinfo && + PyDateTimeTZ_Check(result)) { /* Copy the tzinfo field. */ - replace_tzinfo(result, ((PyDateTime_TimeTZ *)time)->tzinfo); + replace_tzinfo(result, ((PyDateTime_Time *)time)->tzinfo); } return result; } @@ -3343,11 +3337,11 @@ datetime_getdate(PyDateTime_DateTime *self) static PyObject * datetime_gettime(PyDateTime_DateTime *self) { - return new_timetz(DATE_GET_HOUR(self), - DATE_GET_MINUTE(self), - DATE_GET_SECOND(self), - DATE_GET_MICROSECOND(self), - Py_None); + return new_time(DATE_GET_HOUR(self), + DATE_GET_MINUTE(self), + DATE_GET_SECOND(self), + DATE_GET_MICROSECOND(self), + Py_None); } static PyMethodDef datetime_methods[] = { @@ -3398,7 +3392,7 @@ static PyMethodDef datetime_methods[] = { PyDoc_STR("Return datetime with new specified fields.")}, {"astimezone", (PyCFunction)datetime_astimezone, METH_KEYWORDS, - PyDoc_STR("tz -> datetimetz with same date & time, and tzinfo=tz\n")}, + PyDoc_STR("tz -> datetime with same date & time, and tzinfo=tz\n")}, {NULL, NULL} }; @@ -3465,10 +3459,136 @@ statichere PyTypeObject PyDateTime_DateTimeType = { }; /* + * PyDateTime_TZInfo implementation. + */ + +/* This is a pure abstract base class, so doesn't do anything beyond + * raising NotImplemented exceptions. Real tzinfo classes need + * to derive from this. This is mostly for clarity, and for efficiency in + * datetimetz and time constructors (their tzinfo arguments need to + * be subclasses of this tzinfo class, which is easy and quick to check). + * + * Note: For reasons having to do with pickling of subclasses, we have + * to allow tzinfo objects to be instantiated. This wasn't an issue + * in the Python implementation (__init__() could raise NotImplementedError + * there without ill effect), but doing so in the C implementation hit a + * brick wall. + */ + +static PyObject * +tzinfo_nogo(const char* methodname) +{ + PyErr_Format(PyExc_NotImplementedError, + "a tzinfo subclass must implement %s()", + methodname); + return NULL; +} + +/* Methods. A subclass must implement these. */ + +static PyObject* +tzinfo_tzname(PyDateTime_TZInfo *self, PyObject *dt) +{ + return tzinfo_nogo("tzname"); +} + +static PyObject* +tzinfo_utcoffset(PyDateTime_TZInfo *self, PyObject *dt) +{ + return tzinfo_nogo("utcoffset"); +} + +static PyObject* +tzinfo_dst(PyDateTime_TZInfo *self, PyObject *dt) +{ + return tzinfo_nogo("dst"); +} + +/* + * Pickle support. This is solely so that tzinfo subclasses can use + * pickling -- tzinfo itself is supposed to be uninstantiable. The + * pickler and unpickler functions are given module-level private + * names, and registered with copy_reg, by the module init function. + */ + +static PyObject* +tzinfo_pickler(PyDateTime_TZInfo *self) { + return Py_BuildValue("O()", tzinfo_unpickler_object); +} + +static PyObject* +tzinfo_unpickler(PyObject * unused) { + return PyType_GenericNew(&PyDateTime_TZInfoType, NULL, NULL); +} + + +static PyMethodDef tzinfo_methods[] = { + {"tzname", (PyCFunction)tzinfo_tzname, METH_O, + PyDoc_STR("datetime -> string name of time zone.")}, + + {"utcoffset", (PyCFunction)tzinfo_utcoffset, METH_O, + PyDoc_STR("datetime -> minutes east of UTC (negative for " + "west of UTC).")}, + + {"dst", (PyCFunction)tzinfo_dst, METH_O, + PyDoc_STR("datetime -> DST offset in minutes east of UTC.")}, + + {NULL, NULL} +}; + +static char tzinfo_doc[] = +PyDoc_STR("Abstract base class for time zone info objects."); + + statichere PyTypeObject PyDateTime_TZInfoType = { + PyObject_HEAD_INIT(NULL) + 0, /* ob_size */ + "datetime.tzinfo", /* tp_name */ + sizeof(PyDateTime_TZInfo), /* tp_basicsize */ + 0, /* tp_itemsize */ + 0, /* tp_dealloc */ + 0, /* tp_print */ + 0, /* tp_getattr */ + 0, /* tp_setattr */ + 0, /* tp_compare */ + 0, /* tp_repr */ + 0, /* tp_as_number */ + 0, /* tp_as_sequence */ + 0, /* tp_as_mapping */ + 0, /* tp_hash */ + 0, /* tp_call */ + 0, /* tp_str */ + PyObject_GenericGetAttr, /* tp_getattro */ + 0, /* tp_setattro */ + 0, /* tp_as_buffer */ + Py_TPFLAGS_DEFAULT | Py_TPFLAGS_CHECKTYPES | + Py_TPFLAGS_BASETYPE, /* tp_flags */ + tzinfo_doc, /* tp_doc */ + 0, /* tp_traverse */ + 0, /* tp_clear */ + 0, /* tp_richcompare */ + 0, /* tp_weaklistoffset */ + 0, /* tp_iter */ + 0, /* tp_iternext */ + tzinfo_methods, /* tp_methods */ + 0, /* tp_members */ + 0, /* tp_getset */ + 0, /* tp_base */ + 0, /* tp_dict */ + 0, /* tp_descr_get */ + 0, /* tp_descr_set */ + 0, /* tp_dictoffset */ + 0, /* tp_init */ + 0, /* tp_alloc */ + PyType_GenericNew, /* tp_new */ + 0, /* tp_free */ +}; + +/* * PyDateTime_Time implementation. */ -/* Accessor properties. */ +/* Accessor properties. + */ static PyObject * time_hour(PyDateTime_Time *self, void *unused) @@ -3482,6 +3602,7 @@ time_minute(PyDateTime_Time *self, void *unused) return PyInt_FromLong(TIME_GET_MINUTE(self)); } +/* The name time_second conflicted with some platform header file. */ static PyObject * py_time_second(PyDateTime_Time *self, void *unused) { @@ -3494,17 +3615,29 @@ time_microsecond(PyDateTime_Time *self, void *unused) return PyInt_FromLong(TIME_GET_MICROSECOND(self)); } +static PyObject * +time_tzinfo(PyDateTime_Time *self, void *unused) +{ + PyObject *result = self->hastzinfo ? self->tzinfo : Py_None; + Py_INCREF(result); + return result; +} + static PyGetSetDef time_getset[] = { {"hour", (getter)time_hour}, {"minute", (getter)time_minute}, {"second", (getter)py_time_second}, {"microsecond", (getter)time_microsecond}, + {"tzinfo", (getter)time_tzinfo}, {NULL} }; -/* Constructors. */ +/* + * Constructors. + */ -static char *time_kws[] = {"hour", "minute", "second", "microsecond", NULL}; +static char *time_kws[] = {"hour", "minute", "second", "microsecond", + "tzinfo", NULL}; static PyObject * time_new(PyTypeObject *type, PyObject *args, PyObject *kw) @@ -3514,18 +3647,58 @@ time_new(PyTypeObject *type, PyObject *args, PyObject *kw) int minute = 0; int second = 0; int usecond = 0; + PyObject *tzinfo = Py_None; - - if (PyArg_ParseTupleAndKeywords(args, kw, "|iiii", time_kws, - &hour, &minute, &second, &usecond)) { + if (PyArg_ParseTupleAndKeywords(args, kw, "|iiiiO", time_kws, + &hour, &minute, &second, &usecond, + &tzinfo)) { if (check_time_args(hour, minute, second, usecond) < 0) return NULL; - self = new_time(hour, minute, second, usecond); + if (check_tzinfo_subclass(tzinfo) < 0) + return NULL; + self = new_time(hour, minute, second, usecond, tzinfo); } return self; } -/* Various ways to turn a time into a string. */ +/* + * Destructor. + */ + +static void +time_dealloc(PyDateTime_Time *self) +{ + if (self->hastzinfo) + Py_XDECREF(self->tzinfo); + self->ob_type->tp_free((PyObject *)self); +} + +/* + * Indirect access to tzinfo methods. + */ + +/* These are all METH_NOARGS, so don't need to check the arglist. */ +static PyObject * +time_utcoffset(PyDateTime_Time *self, PyObject *unused) { + return offset_as_timedelta(self->hastzinfo ? self->tzinfo : Py_None, + "utcoffset", Py_None); +} + +static PyObject * +time_dst(PyDateTime_Time *self, PyObject *unused) { + return offset_as_timedelta(self->hastzinfo ? self->tzinfo : Py_None, + "dst", Py_None); +} + +static PyObject * +time_tzname(PyDateTime_Time *self, PyObject *unused) { + return call_tzname(self->hastzinfo ? self->tzinfo : Py_None, + Py_None); +} + +/* + * Various ways to turn a time into a string. + */ static PyObject * time_repr(PyDateTime_Time *self) @@ -3536,6 +3709,7 @@ time_repr(PyDateTime_Time *self) int m = TIME_GET_MINUTE(self); int s = TIME_GET_SECOND(self); int us = TIME_GET_MICROSECOND(self); + PyObject *result = NULL; if (us) PyOS_snprintf(buffer, sizeof(buffer), @@ -3546,7 +3720,10 @@ time_repr(PyDateTime_Time *self) else PyOS_snprintf(buffer, sizeof(buffer), "%s(%d, %d)", typename, h, m); - return PyString_FromString(buffer); + result = PyString_FromString(buffer); + if (result != NULL && self->hastzinfo) + result = append_keyword_tzinfo(result, self->tzinfo); + return result; } static PyObject * @@ -3558,7 +3735,8 @@ time_str(PyDateTime_Time *self) static PyObject * time_isoformat(PyDateTime_Time *self) { - char buffer[100]; + char buf[100]; + PyObject *result; /* Reuse the time format code from the datetime type. */ PyDateTime_DateTime datetime; PyDateTime_DateTime *pdatetime = &datetime; @@ -3568,8 +3746,19 @@ time_isoformat(PyDateTime_Time *self) self->data, _PyDateTime_TIME_DATASIZE); - isoformat_time(pdatetime, buffer, sizeof(buffer)); - return PyString_FromString(buffer); + isoformat_time(pdatetime, buf, sizeof(buf)); + result = PyString_FromString(buf); + if (result == NULL || ! self->hastzinfo || self->tzinfo == Py_None) + return result; + + /* We need to append the UTC offset. */ + if (format_utcoffset(buf, sizeof(buf), ":", self->tzinfo, + Py_None) < 0) { + Py_DECREF(result); + return NULL; + } + PyString_ConcatAndDel(&result, PyString_FromString(buf)); + return result; } static PyObject * @@ -3602,12 +3791,13 @@ time_strftime(PyDateTime_Time *self, PyObject *args, PyObject *kw) return result; } -/* Miscellaneous methods. */ +/* + * Miscellaneous methods. + */ /* This is more natural as a tp_compare, but doesn't work then: for whatever * reason, Python's try_3way_compare ignores tp_compare unless * PyInstance_Check returns true, but these aren't old-style classes. - * Note that this routine handles all comparisons for time and timetz. */ static PyObject * time_richcompare(PyDateTime_Time *self, PyObject *other, int op) @@ -3685,7 +3875,7 @@ time_hash(PyDateTime_Time *self) int minute; assert(n == OFFSET_AWARE); - assert(PyTimeTZ_Check(self)); + assert(self->hastzinfo); hour = divmod(TIME_GET_HOUR(self) * 60 + TIME_GET_MINUTE(self) - offset, 60, @@ -3693,7 +3883,8 @@ time_hash(PyDateTime_Time *self) if (0 <= hour && hour < 24) temp = new_time(hour, minute, TIME_GET_SECOND(self), - TIME_GET_MICROSECOND(self)); + TIME_GET_MICROSECOND(self), + Py_None); else temp = Py_BuildValue("iiii", hour, minute, @@ -3717,12 +3908,13 @@ time_replace(PyDateTime_Time *self, PyObject *args, PyObject *kw) int mm = TIME_GET_MINUTE(self); int ss = TIME_GET_SECOND(self); int us = TIME_GET_MICROSECOND(self); + PyObject *tzinfo = self->hastzinfo ? self->tzinfo : Py_None; - if (! PyArg_ParseTupleAndKeywords(args, kw, "|iiii:replace", + if (! PyArg_ParseTupleAndKeywords(args, kw, "|iiiiO:replace", time_kws, - &hh, &mm, &ss, &us)) + &hh, &mm, &ss, &us, &tzinfo)) return NULL; - tuple = Py_BuildValue("iiii", hh, mm, ss, us); + tuple = Py_BuildValue("iiiiO", hh, mm, ss, us, tzinfo); if (tuple == NULL) return NULL; clone = time_new(self->ob_type, tuple, NULL); @@ -3733,408 +3925,55 @@ time_replace(PyDateTime_Time *self, PyObject *args, PyObject *kw) static int time_nonzero(PyDateTime_Time *self) { - return TIME_GET_HOUR(self) || - TIME_GET_MINUTE(self) || - TIME_GET_SECOND(self) || - TIME_GET_MICROSECOND(self); + int offset; + int none; + + if (TIME_GET_SECOND(self) || TIME_GET_MICROSECOND(self)) { + /* Since utcoffset is in whole minutes, nothing can + * alter the conclusion that this is nonzero. + */ + return 1; + } + offset = 0; + if (self->hastzinfo && self->tzinfo != Py_None) { + offset = call_utcoffset(self->tzinfo, Py_None, &none); + if (offset == -1 && PyErr_Occurred()) + return -1; + } + return (TIME_GET_MINUTE(self) - offset + TIME_GET_HOUR(self)*60) != 0; } -static PyMethodDef time_methods[] = { - {"isoformat", (PyCFunction)time_isoformat, METH_KEYWORDS, - PyDoc_STR("Return string in ISO 8601 format, HH:MM:SS[.mmmmmm].")}, +/* + * Pickle support. Quite a maze! + */ - {"strftime", (PyCFunction)time_strftime, METH_KEYWORDS, - PyDoc_STR("format -> strftime() style string.")}, +/* Let basestate be the non-tzinfo data string. + * If tzinfo is None, this returns (basestate,), else (basestate, tzinfo). + * So it's a tuple in any (non-error) case. + */ +static PyObject * +time_getstate(PyDateTime_Time *self) +{ + PyObject *basestate; + PyObject *result = NULL; - {"replace", (PyCFunction)time_replace, METH_KEYWORDS, - PyDoc_STR("Return datetime with new specified fields.")}, - {NULL, NULL} -}; + basestate = PyString_FromStringAndSize((char *)self->data, + _PyDateTime_TIME_DATASIZE); + if (basestate != NULL) { + if (! self->hastzinfo || self->tzinfo == Py_None) + result = Py_BuildValue("(O)", basestate); + else + result = Py_BuildValue("OO", basestate, self->tzinfo); + Py_DECREF(basestate); + } + return result; +} -static char time_doc[] = -PyDoc_STR("Basic time type."); - -static PyNumberMethods time_as_number = { - 0, /* nb_add */ - 0, /* nb_subtract */ - 0, /* nb_multiply */ - 0, /* nb_divide */ - 0, /* nb_remainder */ - 0, /* nb_divmod */ - 0, /* nb_power */ - 0, /* nb_negative */ - 0, /* nb_positive */ - 0, /* nb_absolute */ - (inquiry)time_nonzero, /* nb_nonzero */ -}; - -statichere PyTypeObject PyDateTime_TimeType = { - PyObject_HEAD_INIT(NULL) - 0, /* ob_size */ - "datetime.time", /* tp_name */ - sizeof(PyDateTime_Time), /* tp_basicsize */ - 0, /* tp_itemsize */ - (destructor)PyObject_Del, /* tp_dealloc */ - 0, /* tp_print */ - 0, /* tp_getattr */ - 0, /* tp_setattr */ - 0, /* tp_compare */ - (reprfunc)time_repr, /* tp_repr */ - &time_as_number, /* tp_as_number */ - 0, /* tp_as_sequence */ - 0, /* tp_as_mapping */ - (hashfunc)time_hash, /* tp_hash */ - 0, /* tp_call */ - (reprfunc)time_str, /* tp_str */ - PyObject_GenericGetAttr, /* tp_getattro */ - 0, /* tp_setattro */ - 0, /* tp_as_buffer */ - Py_TPFLAGS_DEFAULT | Py_TPFLAGS_CHECKTYPES | - Py_TPFLAGS_BASETYPE, /* tp_flags */ - time_doc, /* tp_doc */ - 0, /* tp_traverse */ - 0, /* tp_clear */ - (richcmpfunc)time_richcompare, /* tp_richcompare */ - 0, /* tp_weaklistoffset */ - 0, /* tp_iter */ - 0, /* tp_iternext */ - time_methods, /* tp_methods */ - 0, /* tp_members */ - time_getset, /* tp_getset */ - 0, /* tp_base */ - 0, /* tp_dict */ - 0, /* tp_descr_get */ - 0, /* tp_descr_set */ - 0, /* tp_dictoffset */ - 0, /* tp_init */ - 0, /* tp_alloc */ - time_new, /* tp_new */ - _PyObject_Del, /* tp_free */ -}; - -/* - * PyDateTime_TZInfo implementation. - */ - -/* This is a pure abstract base class, so doesn't do anything beyond - * raising NotImplemented exceptions. Real tzinfo classes need - * to derive from this. This is mostly for clarity, and for efficiency in - * datetimetz and timetz constructors (their tzinfo arguments need to - * be subclasses of this tzinfo class, which is easy and quick to check). - * - * Note: For reasons having to do with pickling of subclasses, we have - * to allow tzinfo objects to be instantiated. This wasn't an issue - * in the Python implementation (__init__() could raise NotImplementedError - * there without ill effect), but doing so in the C implementation hit a - * brick wall. - */ - -static PyObject * -tzinfo_nogo(const char* methodname) -{ - PyErr_Format(PyExc_NotImplementedError, - "a tzinfo subclass must implement %s()", - methodname); - return NULL; -} - -/* Methods. A subclass must implement these. */ - -static PyObject* -tzinfo_tzname(PyDateTime_TZInfo *self, PyObject *dt) -{ - return tzinfo_nogo("tzname"); -} - -static PyObject* -tzinfo_utcoffset(PyDateTime_TZInfo *self, PyObject *dt) -{ - return tzinfo_nogo("utcoffset"); -} - -static PyObject* -tzinfo_dst(PyDateTime_TZInfo *self, PyObject *dt) -{ - return tzinfo_nogo("dst"); -} - -/* - * Pickle support. This is solely so that tzinfo subclasses can use - * pickling -- tzinfo itself is supposed to be uninstantiable. The - * pickler and unpickler functions are given module-level private - * names, and registered with copy_reg, by the module init function. - */ - -static PyObject* -tzinfo_pickler(PyDateTime_TZInfo *self) { - return Py_BuildValue("O()", tzinfo_unpickler_object); -} - -static PyObject* -tzinfo_unpickler(PyObject * unused) { - return PyType_GenericNew(&PyDateTime_TZInfoType, NULL, NULL); -} - - -static PyMethodDef tzinfo_methods[] = { - {"tzname", (PyCFunction)tzinfo_tzname, METH_O, - PyDoc_STR("datetime -> string name of time zone.")}, - - {"utcoffset", (PyCFunction)tzinfo_utcoffset, METH_O, - PyDoc_STR("datetime -> minutes east of UTC (negative for " - "west of UTC).")}, - - {"dst", (PyCFunction)tzinfo_dst, METH_O, - PyDoc_STR("datetime -> DST offset in minutes east of UTC.")}, - - {NULL, NULL} -}; - -static char tzinfo_doc[] = -PyDoc_STR("Abstract base class for time zone info objects."); - - statichere PyTypeObject PyDateTime_TZInfoType = { - PyObject_HEAD_INIT(NULL) - 0, /* ob_size */ - "datetime.tzinfo", /* tp_name */ - sizeof(PyDateTime_TZInfo), /* tp_basicsize */ - 0, /* tp_itemsize */ - 0, /* tp_dealloc */ - 0, /* tp_print */ - 0, /* tp_getattr */ - 0, /* tp_setattr */ - 0, /* tp_compare */ - 0, /* tp_repr */ - 0, /* tp_as_number */ - 0, /* tp_as_sequence */ - 0, /* tp_as_mapping */ - 0, /* tp_hash */ - 0, /* tp_call */ - 0, /* tp_str */ - PyObject_GenericGetAttr, /* tp_getattro */ - 0, /* tp_setattro */ - 0, /* tp_as_buffer */ - Py_TPFLAGS_DEFAULT | Py_TPFLAGS_CHECKTYPES | - Py_TPFLAGS_BASETYPE, /* tp_flags */ - tzinfo_doc, /* tp_doc */ - 0, /* tp_traverse */ - 0, /* tp_clear */ - 0, /* tp_richcompare */ - 0, /* tp_weaklistoffset */ - 0, /* tp_iter */ - 0, /* tp_iternext */ - tzinfo_methods, /* tp_methods */ - 0, /* tp_members */ - 0, /* tp_getset */ - 0, /* tp_base */ - 0, /* tp_dict */ - 0, /* tp_descr_get */ - 0, /* tp_descr_set */ - 0, /* tp_dictoffset */ - 0, /* tp_init */ - 0, /* tp_alloc */ - PyType_GenericNew, /* tp_new */ - 0, /* tp_free */ -}; - -/* - * PyDateTime_TimeTZ implementation. - */ - -/* Accessor properties. Properties for hour, minute, second and microsecond - * are inherited from time. - */ - -static PyObject * -timetz_tzinfo(PyDateTime_TimeTZ *self, void *unused) -{ - Py_INCREF(self->tzinfo); - return self->tzinfo; -} - -static PyGetSetDef timetz_getset[] = { - {"tzinfo", (getter)timetz_tzinfo}, - {NULL} -}; - -/* - * Constructors. - */ - -static char *timetz_kws[] = {"hour", "minute", "second", "microsecond", - "tzinfo", NULL}; - -static PyObject * -timetz_new(PyTypeObject *type, PyObject *args, PyObject *kw) -{ - PyObject *self = NULL; - int hour = 0; - int minute = 0; - int second = 0; - int usecond = 0; - PyObject *tzinfo = Py_None; - - if (PyArg_ParseTupleAndKeywords(args, kw, "|iiiiO", timetz_kws, - &hour, &minute, &second, &usecond, - &tzinfo)) { - if (check_time_args(hour, minute, second, usecond) < 0) - return NULL; - if (check_tzinfo_subclass(tzinfo) < 0) - return NULL; - self = new_timetz(hour, minute, second, usecond, tzinfo); - } - return self; -} - -/* - * Destructor. - */ - -static void -timetz_dealloc(PyDateTime_TimeTZ *self) -{ - Py_XDECREF(self->tzinfo); - self->ob_type->tp_free((PyObject *)self); -} - -/* - * Indirect access to tzinfo methods. - */ - -/* These are all METH_NOARGS, so don't need to check the arglist. */ -static PyObject * -timetz_utcoffset(PyDateTime_TimeTZ *self, PyObject *unused) { - return offset_as_timedelta(self->tzinfo, "utcoffset", Py_None); -} - -static PyObject * -timetz_dst(PyDateTime_TimeTZ *self, PyObject *unused) { - return offset_as_timedelta(self->tzinfo, "dst", Py_None); -} - -static PyObject * -timetz_tzname(PyDateTime_TimeTZ *self, PyObject *unused) { - return call_tzname(self->tzinfo, Py_None); -} - -/* - * Various ways to turn a timetz into a string. - */ - -static PyObject * -timetz_repr(PyDateTime_TimeTZ *self) -{ - PyObject *baserepr = time_repr((PyDateTime_Time *)self); - - if (baserepr == NULL) - return NULL; - return append_keyword_tzinfo(baserepr, self->tzinfo); -} - -/* Note: tp_str is inherited from time. */ - -static PyObject * -timetz_isoformat(PyDateTime_TimeTZ *self) -{ - char buf[100]; - PyObject *result = time_isoformat((PyDateTime_Time *)self); - - if (result == NULL || self->tzinfo == Py_None) - return result; - - /* We need to append the UTC offset. */ - if (format_utcoffset(buf, sizeof(buf), ":", self->tzinfo, - Py_None) < 0) { - Py_DECREF(result); - return NULL; - } - PyString_ConcatAndDel(&result, PyString_FromString(buf)); - return result; -} - -/* Note: strftime() is inherited from time. */ - -/* - * Miscellaneous methods. - */ - -/* Note: tp_richcompare and tp_hash are inherited from time. */ - -static PyObject * -timetz_replace(PyDateTime_TimeTZ *self, PyObject *args, PyObject *kw) -{ - PyObject *clone; - PyObject *tuple; - int hh = TIME_GET_HOUR(self); - int mm = TIME_GET_MINUTE(self); - int ss = TIME_GET_SECOND(self); - int us = TIME_GET_MICROSECOND(self); - PyObject *tzinfo = self->tzinfo; - - if (! PyArg_ParseTupleAndKeywords(args, kw, "|iiiiO:replace", - timetz_kws, - &hh, &mm, &ss, &us, &tzinfo)) - return NULL; - tuple = Py_BuildValue("iiiiO", hh, mm, ss, us, tzinfo); - if (tuple == NULL) - return NULL; - clone = timetz_new(self->ob_type, tuple, NULL); - Py_DECREF(tuple); - return clone; -} - -static int -timetz_nonzero(PyDateTime_TimeTZ *self) -{ - int offset; - int none; - - if (TIME_GET_SECOND(self) || TIME_GET_MICROSECOND(self)) { - /* Since utcoffset is in whole minutes, nothing can - * alter the conclusion that this is nonzero. - */ - return 1; - } - offset = 0; - if (self->tzinfo != Py_None) { - offset = call_utcoffset(self->tzinfo, Py_None, &none); - if (offset == -1 && PyErr_Occurred()) - return -1; - } - return (TIME_GET_MINUTE(self) - offset + TIME_GET_HOUR(self)*60) != 0; -} - -/* - * Pickle support. Quite a maze! - */ - -/* Let basestate be the non-tzinfo data string. - * If tzinfo is None, this returns (basestate,), else (basestate, tzinfo). - * So it's a tuple in any (non-error) case. - */ -static PyObject * -timetz_getstate(PyDateTime_TimeTZ *self) -{ - PyObject *basestate; - PyObject *result = NULL; - - basestate = PyString_FromStringAndSize((char *)self->data, - _PyDateTime_TIME_DATASIZE); - if (basestate != NULL) { - if (self->tzinfo == Py_None) - result = Py_BuildValue("(O)", basestate); - else - result = Py_BuildValue("OO", basestate, self->tzinfo); - Py_DECREF(basestate); - } - return result; -} - -static PyObject * -timetz_setstate(PyDateTime_TimeTZ *self, PyObject *state) -{ - PyObject *basestate; - PyObject *tzinfo = Py_None; +static PyObject * +time_setstate(PyDateTime_Time *self, PyObject *state) +{ + PyObject *basestate; + PyObject *tzinfo = Py_None; if (! PyArg_ParseTuple(state, "O!|O:__setstate__", &PyString_Type, &basestate, @@ -4146,33 +3985,41 @@ timetz_setstate(PyDateTime_TimeTZ *self, PyObject *state) "bad argument to time.__setstate__"); return NULL; } + if (tzinfo != Py_None && ! self->hastzinfo) { + PyErr_SetString(PyExc_ValueError, "time.__setstate__ can't " + "add a non-None tzinfo to a time object that " + "doesn't have one already"); + return NULL; + } memcpy((char *)self->data, PyString_AsString(basestate), _PyDateTime_TIME_DATASIZE); self->hashcode = -1; - Py_INCREF(tzinfo); - Py_XDECREF(self->tzinfo); - self->tzinfo = tzinfo; + if (self->hastzinfo) { + Py_INCREF(tzinfo); + Py_XDECREF(self->tzinfo); + self->tzinfo = tzinfo; + } Py_INCREF(Py_None); return Py_None; } static PyObject * -timetz_pickler(PyObject *module, PyDateTime_TimeTZ *timetz) +time_pickler(PyObject *module, PyDateTime_Time *time) { PyObject *state; PyObject *result = NULL; - if (! PyTimeTZ_CheckExact(timetz)) { + if (! PyTime_CheckExact(time)) { PyErr_Format(PyExc_TypeError, - "bad type passed to timetz pickler: %s", - timetz->ob_type->tp_name); + "bad type passed to time pickler: %s", + time->ob_type->tp_name); return NULL; } - state = timetz_getstate(timetz); + state = time_getstate(time); if (state) { result = Py_BuildValue("O(O)", - timetz_unpickler_object, + time_unpickler_object, state); Py_DECREF(state); } @@ -4180,55 +4027,70 @@ timetz_pickler(PyObject *module, PyDateTime_TimeTZ *timetz) } static PyObject * -timetz_unpickler(PyObject *module, PyObject *arg) +time_unpickler(PyObject *module, PyObject *arg) { - PyDateTime_TimeTZ *self; + PyDateTime_Time *self; + PyObject *tzinfo = Py_None; - self = PyObject_New(PyDateTime_TimeTZ, &PyDateTime_TimeTZType); + /* We don't want to allocate space for tzinfo if it's not needed. + * Figuring that out in advance is irritating, so for now we + * realloc later. + */ + self = PyObject_New(PyDateTime_Time, &PyDateTime_TimeType); if (self != NULL) { PyObject *res; - self->tzinfo = NULL; - res = timetz_setstate(self, arg); + self->tzinfo = Py_None; + Py_INCREF(self->tzinfo); + self->hastzinfo = (char)1; /* true */ + res = time_setstate(self, arg); if (res == NULL) { Py_DECREF(self); return NULL; } Py_DECREF(res); + if (self->tzinfo == Py_None) { + /* shrinking; can't fail */ + Py_DECREF(self->tzinfo); + PyObject_Realloc(self, sizeof(_PyDateTime_BaseTime)); + self->hastzinfo = (char)0; + } } return (PyObject *)self; } -static PyMethodDef timetz_methods[] = { - {"isoformat", (PyCFunction)timetz_isoformat, METH_KEYWORDS, +static PyMethodDef time_methods[] = { + {"isoformat", (PyCFunction)time_isoformat, METH_KEYWORDS, PyDoc_STR("Return string in ISO 8601 format, HH:MM:SS[.mmmmmm]" "[+HH:MM].")}, - {"utcoffset", (PyCFunction)timetz_utcoffset, METH_NOARGS, + {"strftime", (PyCFunction)time_strftime, METH_KEYWORDS, + PyDoc_STR("format -> strftime() style string.")}, + + {"utcoffset", (PyCFunction)time_utcoffset, METH_NOARGS, PyDoc_STR("Return self.tzinfo.utcoffset(self).")}, - {"tzname", (PyCFunction)timetz_tzname, METH_NOARGS, + {"tzname", (PyCFunction)time_tzname, METH_NOARGS, PyDoc_STR("Return self.tzinfo.tzname(self).")}, - {"dst", (PyCFunction)timetz_dst, METH_NOARGS, + {"dst", (PyCFunction)time_dst, METH_NOARGS, PyDoc_STR("Return self.tzinfo.dst(self).")}, - {"replace", (PyCFunction)timetz_replace, METH_KEYWORDS, - PyDoc_STR("Return timetz with new specified fields.")}, + {"replace", (PyCFunction)time_replace, METH_KEYWORDS, + PyDoc_STR("Return time with new specified fields.")}, - {"__setstate__", (PyCFunction)timetz_setstate, METH_O, + {"__setstate__", (PyCFunction)time_setstate, METH_O, PyDoc_STR("__setstate__(state)")}, - {"__getstate__", (PyCFunction)timetz_getstate, METH_NOARGS, + {"__getstate__", (PyCFunction)time_getstate, METH_NOARGS, PyDoc_STR("__getstate__() -> state")}, {NULL, NULL} - }; -static char timetz_doc[] = +static char time_doc[] = PyDoc_STR("Time type."); -static PyNumberMethods timetz_as_number = { +static PyNumberMethods time_as_number = { 0, /* nb_add */ 0, /* nb_subtract */ 0, /* nb_multiply */ @@ -4239,50 +4101,50 @@ static PyNumberMethods timetz_as_number = { 0, /* nb_negative */ 0, /* nb_positive */ 0, /* nb_absolute */ - (inquiry)timetz_nonzero, /* nb_nonzero */ + (inquiry)time_nonzero, /* nb_nonzero */ }; -statichere PyTypeObject PyDateTime_TimeTZType = { +statichere PyTypeObject PyDateTime_TimeType = { PyObject_HEAD_INIT(NULL) 0, /* ob_size */ "datetime.time", /* tp_name */ - sizeof(PyDateTime_TimeTZ), /* tp_basicsize */ + sizeof(PyDateTime_Time), /* tp_basicsize */ 0, /* tp_itemsize */ - (destructor)timetz_dealloc, /* tp_dealloc */ + (destructor)time_dealloc, /* tp_dealloc */ 0, /* tp_print */ 0, /* tp_getattr */ 0, /* tp_setattr */ 0, /* tp_compare */ - (reprfunc)timetz_repr, /* tp_repr */ - &timetz_as_number, /* tp_as_number */ + (reprfunc)time_repr, /* tp_repr */ + &time_as_number, /* tp_as_number */ 0, /* tp_as_sequence */ 0, /* tp_as_mapping */ - 0, /* tp_hash */ + (hashfunc)time_hash, /* tp_hash */ 0, /* tp_call */ - 0, /* tp_str */ + (reprfunc)time_str, /* tp_str */ PyObject_GenericGetAttr, /* tp_getattro */ 0, /* tp_setattro */ 0, /* tp_as_buffer */ Py_TPFLAGS_DEFAULT | Py_TPFLAGS_CHECKTYPES | Py_TPFLAGS_BASETYPE, /* tp_flags */ - timetz_doc, /* tp_doc */ + time_doc, /* tp_doc */ 0, /* tp_traverse */ 0, /* tp_clear */ - 0, /* tp_richcompare */ + (richcmpfunc)time_richcompare, /* tp_richcompare */ 0, /* tp_weaklistoffset */ 0, /* tp_iter */ 0, /* tp_iternext */ - timetz_methods, /* tp_methods */ + time_methods, /* tp_methods */ 0, /* tp_members */ - timetz_getset, /* tp_getset */ - &PyDateTime_TimeType, /* tp_base */ + time_getset, /* tp_getset */ + 0, /* tp_base */ 0, /* tp_dict */ 0, /* tp_descr_get */ 0, /* tp_descr_set */ 0, /* tp_dictoffset */ 0, /* tp_init */ 0, /* tp_alloc */ - timetz_new, /* tp_new */ + time_new, /* tp_new */ _PyObject_Del, /* tp_free */ }; @@ -4789,11 +4651,11 @@ datetimetz_utctimetuple(PyDateTime_DateTimeTZ *self) static PyObject * datetimetz_gettimetz(PyDateTime_DateTimeTZ *self) { - return new_timetz(DATE_GET_HOUR(self), - DATE_GET_MINUTE(self), - DATE_GET_SECOND(self), - DATE_GET_MICROSECOND(self), - self->tzinfo); + return new_time(DATE_GET_HOUR(self), + DATE_GET_MINUTE(self), + DATE_GET_SECOND(self), + DATE_GET_MICROSECOND(self), + self->tzinfo); } /* @@ -5019,8 +4881,8 @@ static PyMethodDef module_methods[] = { {"_date_unpickler", (PyCFunction)date_unpickler, METH_O, NULL}, {"_datetimetz_pickler", (PyCFunction)datetimetz_pickler,METH_O, NULL}, {"_datetimetz_unpickler",(PyCFunction)datetimetz_unpickler,METH_O, NULL}, - {"_timetz_pickler", (PyCFunction)timetz_pickler, METH_O, NULL}, - {"_timetz_unpickler", (PyCFunction)timetz_unpickler, METH_O, NULL}, + {"_time_pickler", (PyCFunction)time_pickler, METH_O, NULL}, + {"_time_unpickler", (PyCFunction)time_unpickler, METH_O, NULL}, {"_tzinfo_pickler", (PyCFunction)tzinfo_pickler, METH_O, NULL}, {"_tzinfo_unpickler", (PyCFunction)tzinfo_unpickler, METH_NOARGS, NULL}, @@ -5054,8 +4916,6 @@ initdatetime(void) return; if (PyType_Ready(&PyDateTime_TZInfoType) < 0) return; - if (PyType_Ready(&PyDateTime_TimeTZType) < 0) - return; if (PyType_Ready(&PyDateTime_DateTimeTZType) < 0) return; @@ -5079,15 +4939,15 @@ initdatetime(void) Py_DECREF(x); Py_DECREF(pickler); - pickler = PyObject_GetAttrString(m, "_timetz_pickler"); + pickler = PyObject_GetAttrString(m, "_time_pickler"); if (pickler == NULL) return; - timetz_unpickler_object = PyObject_GetAttrString(m, - "_timetz_unpickler"); - if (timetz_unpickler_object == NULL) return; + time_unpickler_object = PyObject_GetAttrString(m, + "_time_unpickler"); + if (time_unpickler_object == NULL) return; x = PyObject_CallMethod(copyreg, "pickle", "OOO", - &PyDateTime_TimeTZType, + &PyDateTime_TimeType, pickler, - timetz_unpickler_object); + time_unpickler_object); if (x == NULL) return; Py_DECREF(x); Py_DECREF(pickler); @@ -5160,15 +5020,15 @@ initdatetime(void) return; Py_DECREF(x); - /* timetz values */ - d = PyDateTime_TimeTZType.tp_dict; + /* time values */ + d = PyDateTime_TimeType.tp_dict; - x = new_timetz(0, 0, 0, 0, Py_None); + x = new_time(0, 0, 0, 0, Py_None); if (x == NULL || PyDict_SetItemString(d, "min", x) < 0) return; Py_DECREF(x); - x = new_timetz(23, 59, 59, 999999, Py_None); + x = new_time(23, 59, 59, 999999, Py_None); if (x == NULL || PyDict_SetItemString(d, "max", x) < 0) return; Py_DECREF(x); @@ -5211,8 +5071,8 @@ initdatetime(void) Py_INCREF(&PyDateTime_TZInfoType); PyModule_AddObject(m, "tzinfo", (PyObject *) &PyDateTime_TZInfoType); - Py_INCREF(&PyDateTime_TimeTZType); - PyModule_AddObject(m, "time", (PyObject *) &PyDateTime_TimeTZType); + Py_INCREF(&PyDateTime_TimeType); + PyModule_AddObject(m, "time", (PyObject *) &PyDateTime_TimeType); Py_INCREF(&PyDateTime_DateTimeTZType); PyModule_AddObject(m, "datetime", -- cgit v0.12