diff options
author | Paul Ganssle <pganssle@users.noreply.github.com> | 2019-04-29 13:22:03 (GMT) |
---|---|---|
committer | Victor Stinner <vstinner@redhat.com> | 2019-04-29 13:22:03 (GMT) |
commit | 88c093705615c50c47fdd9ab976803f73de7e308 (patch) | |
tree | 4a76496a2c930d1e27e020555f4b91e2b43f777b /Modules | |
parent | a86e06433a010f873dfd7957e0f87a39539876ee (diff) | |
download | cpython-88c093705615c50c47fdd9ab976803f73de7e308.zip cpython-88c093705615c50c47fdd9ab976803f73de7e308.tar.gz cpython-88c093705615c50c47fdd9ab976803f73de7e308.tar.bz2 |
bpo-36004: Add date.fromisocalendar (GH-11888)
This commit implements the first version of date.fromisocalendar, the
inverse function for date.isocalendar.
Diffstat (limited to 'Modules')
-rw-r--r-- | Modules/_datetimemodule.c | 67 |
1 files changed, 67 insertions, 0 deletions
diff --git a/Modules/_datetimemodule.c b/Modules/_datetimemodule.c index b3954c9..83e43a2 100644 --- a/Modules/_datetimemodule.c +++ b/Modules/_datetimemodule.c @@ -3003,6 +3003,67 @@ invalid_string_error: return NULL; } + +static PyObject * +date_fromisocalendar(PyObject *cls, PyObject *args, PyObject *kw) +{ + static char *keywords[] = { + "year", "week", "day", NULL + }; + + int year, week, day; + if (PyArg_ParseTupleAndKeywords(args, kw, "iii:fromisocalendar", + keywords, + &year, &week, &day) == 0) { + if (PyErr_ExceptionMatches(PyExc_OverflowError)) { + PyErr_Format(PyExc_ValueError, + "ISO calendar component out of range"); + + } + return NULL; + } + + // Year is bounded to 0 < year < 10000 because 9999-12-31 is (9999, 52, 5) + if (year < MINYEAR || year > MAXYEAR) { + PyErr_Format(PyExc_ValueError, "Year is out of range: %d", year); + return NULL; + } + + if (week <= 0 || week >= 53) { + int out_of_range = 1; + if (week == 53) { + // ISO years have 53 weeks in it on years starting with a Thursday + // and on leap years starting on Wednesday + int first_weekday = weekday(year, 1, 1); + if (first_weekday == 3 || (first_weekday == 2 && is_leap(year))) { + out_of_range = 0; + } + } + + if (out_of_range) { + PyErr_Format(PyExc_ValueError, "Invalid week: %d", week); + return NULL; + } + } + + if (day <= 0 || day >= 8) { + PyErr_Format(PyExc_ValueError, "Invalid day: %d (range is [1, 7])", + day); + return NULL; + } + + // Convert (Y, W, D) to (Y, M, D) in-place + int day_1 = iso_week1_monday(year); + + int month = week; + int day_offset = (month - 1)*7 + day - 1; + + ord_to_ymd(day_1 + day_offset, &year, &month, &day); + + return new_date_subclass_ex(year, month, day, cls); +} + + /* * Date arithmetic. */ @@ -3296,6 +3357,12 @@ static PyMethodDef date_methods[] = { METH_CLASS, PyDoc_STR("str -> Construct a date from the output of date.isoformat()")}, + {"fromisocalendar", (PyCFunction)(void(*)(void))date_fromisocalendar, + METH_VARARGS | METH_KEYWORDS | METH_CLASS, + PyDoc_STR("int, int, int -> Construct a date from the ISO year, week " + "number and weekday.\n\n" + "This is the inverse of the date.isocalendar() function")}, + {"today", (PyCFunction)date_today, METH_NOARGS | METH_CLASS, PyDoc_STR("Current date or datetime: same as " "self.__class__.fromtimestamp(time.time()).")}, |