Skip to content

Commit 752aad8

Browse files
pgansslecorona10
authored andcommitted
Change pickling behavior of isocalendar() objects
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. Also add a test to this effect.
1 parent 63d1ed9 commit 752aad8

3 files changed

Lines changed: 66 additions & 9 deletions

File tree

Lib/datetime.py

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1232,9 +1232,12 @@ def week(self):
12321232
def weekday(self):
12331233
return self[2]
12341234

1235+
def __reduce__(self):
1236+
return (tuple, (tuple(self),))
1237+
12351238
def __repr__(self):
12361239
return (f'{self.__class__.__name__}'
1237-
f'(year={self[0]}, week={self[1]}, weekday={self[2]})')
1240+
f'(year={self[0]}, week={self[1]}, weekday={self[2]})')
12381241

12391242

12401243
_tzinfo_class = tzinfo

Lib/test/datetimetester.py

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
33
See http://www.zope.org/Members/fdrake/DateTimeWiki/TestCases
44
"""
5+
import io
56
import itertools
67
import bisect
78
import copy
@@ -1380,6 +1381,20 @@ def test_isocalendar(self):
13801381
self.assertEqual((t.year, t.week, t.weekday), exp_iso)
13811382
self.assertEqual(d.isocalendar(), exp_iso)
13821383

1384+
def test_isocalendar_pickling(self):
1385+
"""Test that the result of datetime.isocalendar() can be pickled.
1386+
1387+
The result of a round trip should be a plain tuple.
1388+
"""
1389+
d = self.theclass(2019, 1, 1)
1390+
f = io.BytesIO()
1391+
pickle.dump(d.isocalendar(), f)
1392+
f.seek(0)
1393+
1394+
res = pickle.load(f)
1395+
1396+
self.assertEqual(res, (2019, 1, 2))
1397+
13831398
def test_iso_long_years(self):
13841399
# Calculate long ISO years and compare to table from
13851400
# http://www.phys.uu.nl/~vgent/calendar/isocalendar.htm

Modules/_datetimemodule.c

Lines changed: 47 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -3245,9 +3245,53 @@ static PyStructSequence_Desc struct_iso_calendar_date_desc = {
32453245
3
32463246
};
32473247

3248-
static int initialized;
3248+
static int isocalendardate_initialized;
32493249
static PyTypeObject StructIsoCalendarDateType;
32503250

3251+
// Required for pickling as a tuple
3252+
static PyObject *
3253+
isocalendardate_reduce(PyObject *self, PyObject *Py_UNUSED(ignored))
3254+
{
3255+
// Construct the tuple that this reduces to
3256+
PyObject * reduce_tuple = Py_BuildValue(
3257+
"O((OOO))", &PyTuple_Type,
3258+
PyStructSequence_GET_ITEM(self, 0),
3259+
PyStructSequence_GET_ITEM(self, 1),
3260+
PyStructSequence_GET_ITEM(self, 2)
3261+
);
3262+
3263+
return reduce_tuple;
3264+
}
3265+
3266+
/* Method to add our custom reduce type to the IsoCalendarDate type */
3267+
static int
3268+
_initialize_isocalendardate() {
3269+
if (isocalendardate_initialized) {
3270+
return 0;
3271+
}
3272+
3273+
if (PyStructSequence_InitType2(&StructIsoCalendarDateType,
3274+
&struct_iso_calendar_date_desc) < 0) {
3275+
return -1;
3276+
}
3277+
3278+
// Add our custom __reduce__
3279+
PyMethodDef *method = StructIsoCalendarDateType.tp_methods;
3280+
int reduce_missing = 1;
3281+
while (method != NULL) {
3282+
if (strcmp("__reduce__", method->ml_name) == 0) {
3283+
reduce_missing = 0;
3284+
method->ml_meth = (PyCFunction)isocalendardate_reduce;
3285+
method->ml_doc = "Reduce IsoCalendarDate to a simple tuple";
3286+
3287+
break;
3288+
}
3289+
}
3290+
3291+
assert(!reduce_missing);
3292+
return 0;
3293+
}
3294+
32513295
static PyObject *
32523296
date_isocalendar(PyDateTime_Date *self, PyObject *Py_UNUSED(ignored))
32533297
{
@@ -6554,15 +6598,10 @@ PyInit__datetime(void)
65546598
PyModule_AddIntMacro(m, MAXYEAR);
65556599

65566600
/* IsoCalendarDate */
6557-
if (!initialized) {
6558-
if (PyStructSequence_InitType2(&StructIsoCalendarDateType,
6559-
&struct_iso_calendar_date_desc) < 0) {
6560-
return NULL;
6561-
}
6562-
initialized = 1;
6601+
if (_initialize_isocalendardate()) {
6602+
return NULL;
65636603
}
65646604
Py_INCREF((PyObject *) &StructIsoCalendarDateType);
6565-
PyModule_AddObject(m, "IsoCalendarDate", (PyObject *) &StructIsoCalendarDateType);
65666605

65676606
Py_INCREF(&PyDateTime_DateType);
65686607
PyModule_AddObject(m, "date", (PyObject *) &PyDateTime_DateType);

0 commit comments

Comments
 (0)