diff --git a/Lib/test/datetimetester.py b/Lib/test/datetimetester.py index 1d0c1c5bd23..e8ed79e8b32 100644 --- a/Lib/test/datetimetester.py +++ b/Lib/test/datetimetester.py @@ -1552,6 +1552,50 @@ class TestDate(HarmlessMixedComparison, unittest.TestCase): self.assertEqual(dt1.toordinal(), dt2.toordinal()) self.assertEqual(dt2.newmeth(-7), dt1.year + dt1.month - 7) + def test_subclass_alternate_constructors(self): + # Test that alternate constructors call the constructor + class DateSubclass(self.theclass): + def __new__(cls, *args, **kwargs): + result = self.theclass.__new__(cls, *args, **kwargs) + result.extra = 7 + + return result + + args = (2003, 4, 14) + d_ord = 731319 # Equivalent ordinal date + d_isoformat = '2003-04-14' # Equivalent isoformat() + + base_d = DateSubclass(*args) + self.assertIsInstance(base_d, DateSubclass) + self.assertEqual(base_d.extra, 7) + + # Timestamp depends on time zone, so we'll calculate the equivalent here + ts = datetime.combine(base_d, time(0)).timestamp() + + test_cases = [ + ('fromordinal', (d_ord,)), + ('fromtimestamp', (ts,)), + ('fromisoformat', (d_isoformat,)), + ] + + for constr_name, constr_args in test_cases: + for base_obj in (DateSubclass, base_d): + # Test both the classmethod and method + with self.subTest(base_obj_type=type(base_obj), + constr_name=constr_name): + constr = getattr(base_obj, constr_name) + + dt = constr(*constr_args) + + # Test that it creates the right subclass + self.assertIsInstance(dt, DateSubclass) + + # Test that it's equal to the base object + self.assertEqual(dt, base_d) + + # Test that it called the constructor + self.assertEqual(dt.extra, 7) + def test_pickling_subclass_date(self): args = 6, 7, 23 @@ -2420,6 +2464,54 @@ class TestDateTime(TestDate): self.assertEqual(dt2.newmeth(-7), dt1.year + dt1.month + dt1.second - 7) + def test_subclass_alternate_constructors_datetime(self): + # Test that alternate constructors call the constructor + class DateTimeSubclass(self.theclass): + def __new__(cls, *args, **kwargs): + result = self.theclass.__new__(cls, *args, **kwargs) + result.extra = 7 + + return result + + args = (2003, 4, 14, 12, 30, 15, 123456) + d_isoformat = '2003-04-14T12:30:15.123456' # Equivalent isoformat() + utc_ts = 1050323415.123456 # UTC timestamp + + base_d = DateTimeSubclass(*args) + self.assertIsInstance(base_d, DateTimeSubclass) + self.assertEqual(base_d.extra, 7) + + # Timestamp depends on time zone, so we'll calculate the equivalent here + ts = base_d.timestamp() + + test_cases = [ + ('fromtimestamp', (ts,)), + # See https://bugs.python.org/issue32417 + # ('fromtimestamp', (ts, timezone.utc)), + ('utcfromtimestamp', (utc_ts,)), + ('fromisoformat', (d_isoformat,)), + ('strptime', (d_isoformat, '%Y-%m-%dT%H:%M:%S.%f')), + ('combine', (date(*args[0:3]), time(*args[3:]))), + ] + + for constr_name, constr_args in test_cases: + for base_obj in (DateTimeSubclass, base_d): + # Test both the classmethod and method + with self.subTest(base_obj_type=type(base_obj), + constr_name=constr_name): + constr = getattr(base_obj, constr_name) + + dt = constr(*constr_args) + + # Test that it creates the right subclass + self.assertIsInstance(dt, DateTimeSubclass) + + # Test that it's equal to the base object + self.assertEqual(dt, base_d.replace(tzinfo=None)) + + # Test that it called the constructor + self.assertEqual(dt.extra, 7) + def test_fromisoformat_datetime(self): # Test that isoformat() is reversible base_dates = [ diff --git a/Misc/NEWS.d/next/Library/2017-12-23-14-51-46.bpo-32403.CVFapH.rst b/Misc/NEWS.d/next/Library/2017-12-23-14-51-46.bpo-32403.CVFapH.rst new file mode 100644 index 00000000000..f05d346948a --- /dev/null +++ b/Misc/NEWS.d/next/Library/2017-12-23-14-51-46.bpo-32403.CVFapH.rst @@ -0,0 +1,2 @@ +Improved speed of :class:`datetime.date` and :class:`datetime.datetime` +alternate constructors. diff --git a/Misc/NEWS.d/next/Library/2017-12-23-14-54-05.bpo-32404.yJqtlJ.rst b/Misc/NEWS.d/next/Library/2017-12-23-14-54-05.bpo-32404.yJqtlJ.rst new file mode 100644 index 00000000000..5299820429b --- /dev/null +++ b/Misc/NEWS.d/next/Library/2017-12-23-14-54-05.bpo-32404.yJqtlJ.rst @@ -0,0 +1,2 @@ +Fix bug where :meth:`datetime.datetime.fromtimestamp` did not call __new__ +in :class:`datetime.datetime` subclasses. diff --git a/Modules/_datetimemodule.c b/Modules/_datetimemodule.c index e68c7c0a1c5..d1f48e5bd04 100644 --- a/Modules/_datetimemodule.c +++ b/Modules/_datetimemodule.c @@ -847,6 +847,27 @@ new_date_ex(int year, int month, int day, PyTypeObject *type) #define new_date(year, month, day) \ new_date_ex(year, month, day, &PyDateTime_DateType) +// Forward declaration +static PyObject * new_datetime_ex(int, int, int, int, int, int, int, + PyObject*, PyTypeObject*); + +/* Create date instance with no range checking, or call subclass constructor */ +static PyObject * +new_date_subclass_ex(int year, int month, int day, PyObject *cls) { + PyObject *result; + // We have "fast path" constructors for two subclasses: date and datetime + if ((PyTypeObject *)cls == &PyDateTime_DateType) { + result = new_date_ex(year, month, day, (PyTypeObject *)cls); + } else if ((PyTypeObject *)cls == &PyDateTime_DateTimeType) { + result = new_datetime_ex(year, month, day, 0, 0, 0, 0, Py_None, + (PyTypeObject *)cls); + } else { + result = PyObject_CallFunction(cls, "iii", year, month, day); + } + + return result; +} + /* Create a datetime instance with no range checking. */ static PyObject * new_datetime_ex2(int year, int month, int day, int hour, int minute, @@ -894,6 +915,40 @@ new_datetime_ex(int year, int month, int day, int hour, int minute, new_datetime_ex2(y, m, d, hh, mm, ss, us, tzinfo, fold, \ &PyDateTime_DateTimeType) +static PyObject * +new_datetime_subclass_fold_ex(int year, int month, int day, int hour, int minute, + int second, int usecond, PyObject *tzinfo, + int fold, PyObject *cls) { + PyObject* dt; + if ((PyTypeObject*)cls == &PyDateTime_DateTimeType) { + // Use the fast path constructor + dt = new_datetime(year, month, day, hour, minute, second, usecond, + tzinfo, fold); + } else { + // Subclass + dt = PyObject_CallFunction(cls, "iiiiiiiO", + year, + month, + day, + hour, + minute, + second, + usecond, + tzinfo); + } + + return dt; +} + +static PyObject * +new_datetime_subclass_ex(int year, int month, int day, int hour, int minute, + int second, int usecond, PyObject *tzinfo, + PyObject *cls) { + return new_datetime_subclass_fold_ex(year, month, day, hour, minute, + second, usecond, tzinfo, 0, + cls); +} + /* Create a time instance with no range checking. */ static PyObject * new_time_ex2(int hour, int minute, int second, int usecond, @@ -2743,10 +2798,10 @@ date_local_from_object(PyObject *cls, PyObject *obj) if (_PyTime_localtime(t, &tm) != 0) return NULL; - return PyObject_CallFunction(cls, "iii", - tm.tm_year + 1900, - tm.tm_mon + 1, - tm.tm_mday); + return new_date_subclass_ex(tm.tm_year + 1900, + tm.tm_mon + 1, + tm.tm_mday, + cls); } /* Return new date from current time. @@ -2809,8 +2864,7 @@ date_fromordinal(PyObject *cls, PyObject *args) ">= 1"); else { ord_to_ymd(ordinal, &year, &month, &day); - result = PyObject_CallFunction(cls, "iii", - year, month, day); + result = new_date_subclass_ex(year, month, day, cls); } } return result; @@ -2845,14 +2899,7 @@ date_fromisoformat(PyObject *cls, PyObject *dtstr) { return NULL; } - PyObject *result; - if ( (PyTypeObject*)cls == &PyDateTime_DateType ) { - result = new_date_ex(year, month, day, (PyTypeObject*)cls); - } else { - result = PyObject_CallFunction(cls, "iii", year, month, day); - } - - return result; + return new_date_subclass_ex(year, month, day, cls); } @@ -4596,9 +4643,8 @@ datetime_from_timet_and_us(PyObject *cls, TM_FUNC f, time_t timet, int us, fold = 1; } } - return new_datetime_ex2(year, month, day, hour, - minute, second, us, tzinfo, fold, - (PyTypeObject *)cls); + return new_datetime_subclass_fold_ex(year, month, day, hour, minute, + second, us, tzinfo, fold, cls); } /* Internal helper. @@ -4764,17 +4810,16 @@ datetime_combine(PyObject *cls, PyObject *args, PyObject *kw) else tzinfo = Py_None; } - result = PyObject_CallFunction(cls, "iiiiiiiO", - GET_YEAR(date), - GET_MONTH(date), - GET_DAY(date), - TIME_GET_HOUR(time), - TIME_GET_MINUTE(time), - TIME_GET_SECOND(time), - TIME_GET_MICROSECOND(time), - tzinfo); - if (result) - DATE_SET_FOLD(result, TIME_GET_FOLD(time)); + result = new_datetime_subclass_fold_ex(GET_YEAR(date), + GET_MONTH(date), + GET_DAY(date), + TIME_GET_HOUR(time), + TIME_GET_MINUTE(time), + TIME_GET_SECOND(time), + TIME_GET_MICROSECOND(time), + tzinfo, + TIME_GET_FOLD(time), + cls); } return result; } @@ -4832,23 +4877,8 @@ datetime_fromisoformat(PyObject* cls, PyObject *dtstr) { return NULL; } - PyObject* dt; - if ( (PyTypeObject*)cls == &PyDateTime_DateTimeType ) { - // Use the fast path constructor - dt = new_datetime(year, month, day, hour, minute, second, microsecond, - tzinfo, 0); - } else { - // Subclass - dt = PyObject_CallFunction(cls, "iiiiiiiO", - year, - month, - day, - hour, - minute, - second, - microsecond, - tzinfo); - } + PyObject *dt = new_datetime_subclass_ex(year, month, day, hour, minute, + second, microsecond, tzinfo, cls); Py_DECREF(tzinfo); return dt;