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:
@@ -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);
|
||||
|
||||
|
||||
Reference in New Issue
Block a user