gh-102450: Add ISO-8601 alternative for midnight to fromisoformat() calls. (#105856)

* Add NEWS.d entry

* Allow ISO-8601 24:00 alternative to midnight on datetime.time.fromisoformat()

* Allow ISO-8601 24:00 alternative to midnight on datetime.datetime.fromisoformat()

* Add NEWS.d entry

* Improve error message when hour is 24 and minute/second/microsecond is not 0

* Add tests for 24:00 fromisoformat

* Remove duplicate call to days_in_month() by storing in variable

* Add Python implementation

* Fix Lint

* Fix differing error msg in datetime.fromisoformat implementations when 24hrs has non-zero time component(s)

* Fix using time components inside tzinfo in Python implementation

* Don't parse tzinfo in C implementation when invalid iso midnight

* Remove duplicated variable in datetime test assertion line

* Add self to acknowledgements

* Remove duplicate NEWS entry

* Linting

* Add missing test case for when wrapping the year makes it invalid (too large)
This commit is contained in:
TizzySaurus
2024-09-25 22:32:51 +01:00
committed by GitHub
parent 68e384c217
commit b0c6cf5f17
5 changed files with 80 additions and 4 deletions

View File

@@ -4997,6 +4997,14 @@ time_fromisoformat(PyObject *cls, PyObject *tstr) {
goto invalid_string_error;
}
if (hour == 24) {
if (minute == 0 && second == 0 && microsecond == 0) {
hour = 0;
} else {
goto invalid_iso_midnight;
}
}
PyObject *tzinfo = tzinfo_from_isoformat_results(rv, tzoffset,
tzimicrosecond);
@@ -5015,6 +5023,10 @@ time_fromisoformat(PyObject *cls, PyObject *tstr) {
Py_DECREF(tzinfo);
return t;
invalid_iso_midnight:
PyErr_SetString(PyExc_ValueError, "minute, second, and microsecond must be 0 when hour is 24");
return NULL;
invalid_string_error:
PyErr_Format(PyExc_ValueError, "Invalid isoformat string: %R", tstr);
return NULL;
@@ -5861,6 +5873,26 @@ datetime_fromisoformat(PyObject *cls, PyObject *dtstr)
goto error;
}
if ((hour == 24) && (month <= 12)) {
int d_in_month = days_in_month(year, month);
if (day <= d_in_month) {
if (minute == 0 && second == 0 && microsecond == 0) {
// Calculate midnight of the next day
hour = 0;
day += 1;
if (day > d_in_month) {
day = 1;
month += 1;
if (month > 12) {
month = 1;
year += 1;
}
}
} else {
goto invalid_iso_midnight;
}
}
}
PyObject *dt = new_datetime_subclass_ex(year, month, day, hour, minute,
second, microsecond, tzinfo, cls);
@@ -5868,6 +5900,10 @@ datetime_fromisoformat(PyObject *cls, PyObject *dtstr)
Py_DECREF(dtstr_clean);
return dt;
invalid_iso_midnight:
PyErr_SetString(PyExc_ValueError, "minute, second, and microsecond must be 0 when hour is 24");
return NULL;
invalid_string_error:
PyErr_Format(PyExc_ValueError, "Invalid isoformat string: %R", dtstr);