Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Prev Previous commit
Next Next commit
Switch datetime constructors to fastpath, fix bpo-32404
  • Loading branch information
pganssle committed Jan 13, 2018
commit 2e47897470eab878649f0675df0d4588f7589e65
58 changes: 51 additions & 7 deletions Lib/test/datetimetester.py
Original file line number Diff line number Diff line change
Expand Up @@ -1562,27 +1562,23 @@ def __new__(cls, *args, **kwargs):
return result

args = (2003, 4, 14)
ts = 1050292800.0 # Equivalent timestamp
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:
if (issubclass(self.theclass, datetime) and
constr_name == 'fromtimestamp'):
# Known failure for datetime.fromtimestamp
# See bpo-32404
continue

for base_obj in (DateSubclass, base_d):
# Test both the classmethod and method
with self.subTest(base_obj_type=type(base_obj),
Expand Down Expand Up @@ -2468,6 +2464,54 @@ def newmeth(self, start):
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 = [
Expand Down
79 changes: 49 additions & 30 deletions Modules/_datetimemodule.c
Original file line number Diff line number Diff line change
Expand Up @@ -848,7 +848,7 @@ new_date_ex(int year, int month, int day, PyTypeObject *type)
static PyObject *
new_date_subclass_ex(int year, int month, int day, PyObject *cls) {
PyObject *result;
if ( (PyTypeObject *)cls == & PyDateTime_DateType ) {
if ((PyTypeObject *)cls == &PyDateTime_DateType) {
result = new_date_ex(year, month, day, (PyTypeObject *)cls);
} else {
result = PyObject_CallFunction(cls, "iii", year, month, day);
Expand Down Expand Up @@ -907,6 +907,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,
Expand Down Expand Up @@ -4601,9 +4635,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.
Expand Down Expand Up @@ -4769,15 +4802,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);
result = new_datetime_subclass_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,
cls);

if (result)
DATE_SET_FOLD(result, TIME_GET_FOLD(time));
}
Expand Down Expand Up @@ -4837,23 +4871,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;
Expand Down