summaryrefslogtreecommitdiffstats
path: root/Modules
diff options
context:
space:
mode:
authorPaul Ganssle <pganssle@users.noreply.github.com>2019-04-29 13:22:03 (GMT)
committerVictor Stinner <vstinner@redhat.com>2019-04-29 13:22:03 (GMT)
commit88c093705615c50c47fdd9ab976803f73de7e308 (patch)
tree4a76496a2c930d1e27e020555f4b91e2b43f777b /Modules
parenta86e06433a010f873dfd7957e0f87a39539876ee (diff)
downloadcpython-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.c67
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()).")},