bpo-24416: Return named tuple from date.isocalendar() (GH-20113)

{date, datetime}.isocalendar() now return a private custom named tuple object
IsoCalendarDate rather than a simple tuple.

In order to leave IsocalendarDate as a private class and to improve what
backwards compatibility is offered for pickling the result of a
datetime.isocalendar() call, add a __reduce__ method to the named tuples that
reduces them to plain tuples. (This is the part of this PR most likely to cause
problems — if it causes major issues, switching to a strucseq or equivalent
would be prudent).

The pure python implementation of IsoCalendarDate uses positional-only
arguments, since it is private and only constructed by position anyway; the
equivalent change in the argument clinic on the C side would require us to move
the forward declaration of the type above the clinic import for whatever
reason, so it seems preferable to hold off on that for now.

bpo-24416: https://bugs.python.org/issue24416

Original PR by Dong-hee Na with only minor alterations by Paul Ganssle.

Co-authored-by: Dong-hee Na <donghee.na92@gmail.com>
This commit is contained in:
Paul Ganssle 2020-05-16 10:02:59 -04:00 committed by GitHub
parent aa92a7cf21
commit 1b97b9b0ad
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
7 changed files with 297 additions and 29 deletions

View file

@ -38,8 +38,9 @@
module datetime
class datetime.datetime "PyDateTime_DateTime *" "&PyDateTime_DateTimeType"
class datetime.date "PyDateTime_Date *" "&PyDateTime_DateType"
class datetime.IsoCalendarDate "PyDateTime_IsoCalendarDate *" "&PyDateTime_IsoCalendarDateType"
[clinic start generated code]*/
/*[clinic end generated code: output=da39a3ee5e6b4b0d input=25138ad6a696b785]*/
/*[clinic end generated code: output=da39a3ee5e6b4b0d input=81bec0fa19837f63]*/
#include "clinic/_datetimemodule.c.h"
@ -131,6 +132,7 @@ class datetime.date "PyDateTime_Date *" "&PyDateTime_DateType"
static PyTypeObject PyDateTime_DateType;
static PyTypeObject PyDateTime_DateTimeType;
static PyTypeObject PyDateTime_DeltaType;
static PyTypeObject PyDateTime_IsoCalendarDateType;
static PyTypeObject PyDateTime_TimeType;
static PyTypeObject PyDateTime_TZInfoType;
static PyTypeObject PyDateTime_TimeZoneType;
@ -3224,6 +3226,136 @@ date_isoweekday(PyDateTime_Date *self, PyObject *Py_UNUSED(ignored))
return PyLong_FromLong(dow + 1);
}
PyDoc_STRVAR(iso_calendar_date__doc__,
"The result of date.isocalendar() or datetime.isocalendar()\n\n\
This object may be accessed either as a tuple of\n\
((year, week, weekday)\n\
or via the object attributes as named in the above tuple.");
typedef struct {
PyTupleObject tuple;
} PyDateTime_IsoCalendarDate;
static PyObject *
iso_calendar_date_repr(PyDateTime_IsoCalendarDate *self)
{
PyObject* year = PyTuple_GetItem((PyObject *)self, 0);
if (year == NULL) {
return NULL;
}
PyObject* week = PyTuple_GetItem((PyObject *)self, 1);
if (week == NULL) {
return NULL;
}
PyObject* weekday = PyTuple_GetItem((PyObject *)self, 2);
if (weekday == NULL) {
return NULL;
}
return PyUnicode_FromFormat("%.200s(year=%S, week=%S, weekday=%S)",
Py_TYPE(self)->tp_name, year, week, weekday);
}
static PyObject *
iso_calendar_date_reduce(PyObject *self, PyObject *Py_UNUSED(ignored))
{
// Construct the tuple that this reduces to
PyObject * reduce_tuple = Py_BuildValue(
"O((OOO))", &PyTuple_Type,
PyTuple_GET_ITEM(self, 0),
PyTuple_GET_ITEM(self, 1),
PyTuple_GET_ITEM(self, 2)
);
return reduce_tuple;
}
static PyObject *
iso_calendar_date_year(PyDateTime_IsoCalendarDate *self, void *unused)
{
PyObject *year = PyTuple_GetItem((PyObject *)self, 0);
if (year == NULL) {
return NULL;
}
Py_INCREF(year);
return year;
}
static PyObject *
iso_calendar_date_week(PyDateTime_IsoCalendarDate *self, void *unused)
{
PyObject *week = PyTuple_GetItem((PyObject *)self, 1);
if (week == NULL) {
return NULL;
}
Py_INCREF(week);
return week;
}
static PyObject *
iso_calendar_date_weekday(PyDateTime_IsoCalendarDate *self, void *unused)
{
PyObject *weekday = PyTuple_GetItem((PyObject *)self, 2);
if (weekday == NULL) {
return NULL;
}
Py_INCREF(weekday);
return weekday;
}
static PyGetSetDef iso_calendar_date_getset[] = {
{"year", (getter)iso_calendar_date_year},
{"week", (getter)iso_calendar_date_week},
{"weekday", (getter)iso_calendar_date_weekday},
{NULL}
};
static PyMethodDef iso_calendar_date_methods[] = {
{"__reduce__", (PyCFunction)iso_calendar_date_reduce, METH_NOARGS,
PyDoc_STR("__reduce__() -> (cls, state)")},
{NULL, NULL},
};
static PyTypeObject PyDateTime_IsoCalendarDateType = {
PyVarObject_HEAD_INIT(NULL, 0)
.tp_name = "datetime.IsoCalendarDate",
.tp_basicsize = sizeof(PyDateTime_IsoCalendarDate),
.tp_repr = (reprfunc) iso_calendar_date_repr,
.tp_flags = Py_TPFLAGS_DEFAULT,
.tp_doc = iso_calendar_date__doc__,
.tp_methods = iso_calendar_date_methods,
.tp_getset = iso_calendar_date_getset,
.tp_base = &PyTuple_Type,
.tp_new = iso_calendar_date_new,
};
/*[clinic input]
@classmethod
datetime.IsoCalendarDate.__new__ as iso_calendar_date_new
year: int
week: int
weekday: int
[clinic start generated code]*/
static PyObject *
iso_calendar_date_new_impl(PyTypeObject *type, int year, int week,
int weekday)
/*[clinic end generated code: output=383d33d8dc7183a2 input=4f2c663c9d19c4ee]*/
{
PyDateTime_IsoCalendarDate *self;
self = (PyDateTime_IsoCalendarDate *) type->tp_alloc(type, 3);
if (self == NULL) {
return NULL;
}
PyTuple_SET_ITEM(self, 0, PyLong_FromLong(year));
PyTuple_SET_ITEM(self, 1, PyLong_FromLong(week));
PyTuple_SET_ITEM(self, 2, PyLong_FromLong(weekday));
return (PyObject *)self;
}
static PyObject *
date_isocalendar(PyDateTime_Date *self, PyObject *Py_UNUSED(ignored))
{
@ -3243,7 +3375,13 @@ date_isocalendar(PyDateTime_Date *self, PyObject *Py_UNUSED(ignored))
++year;
week = 0;
}
return Py_BuildValue("iii", year, week + 1, day + 1);
PyObject* v = iso_calendar_date_new_impl(&PyDateTime_IsoCalendarDateType,
year, week + 1, day + 1);
if (v == NULL) {
return NULL;
}
return v;
}
/* Miscellaneous methods. */
@ -3382,7 +3520,7 @@ static PyMethodDef date_methods[] = {
PyDoc_STR("Return time tuple, compatible with time.localtime().")},
{"isocalendar", (PyCFunction)date_isocalendar, METH_NOARGS,
PyDoc_STR("Return a 3-tuple containing ISO year, week number, and "
PyDoc_STR("Return a named tuple containing ISO year, week number, and "
"weekday.")},
{"isoformat", (PyCFunction)date_isoformat, METH_NOARGS,
@ -6386,13 +6524,14 @@ PyInit__datetime(void)
if (m == NULL)
return NULL;
PyTypeObject *types[] = {
&PyDateTime_DateType,
&PyDateTime_DateTimeType,
&PyDateTime_TimeType,
&PyDateTime_DeltaType,
&PyDateTime_TZInfoType,
&PyDateTime_TimeZoneType
&PyDateTime_TimeZoneType,
};
for (size_t i = 0; i < Py_ARRAY_LENGTH(types); i++) {
@ -6401,6 +6540,11 @@ PyInit__datetime(void)
}
}
if (PyType_Ready(&PyDateTime_IsoCalendarDateType) < 0) {
return NULL;
}
Py_INCREF(&PyDateTime_IsoCalendarDateType);
/* timedelta values */
d = PyDateTime_DeltaType.tp_dict;