8000 gh-109798: Normalize `_datetime` and `datetime` error messages by donBarbos · Pull Request #127345 · python/cpython · GitHub
[go: up one dir, main page]

Skip to content

gh-109798: Normalize _datetime and datetime error messages #127345

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 31 commits into from
Feb 12, 2025
Merged
Show file tree
Hide file tree
Changes from 5 commits
Commits
Show all changes
31 commits
Select commit Hold shift + click to select a range
764eb1b
Update error messages to be the same in datetime
donBarbos Nov 27, 2024
2bf419f
Add NEWS.d/next
donBarbos Nov 27, 2024
4363e9e
fixed syntax errors
donBarbos Nov 27, 2024
9da0dfc
Move Py_DECREF after PyErr_Format
donBarbos Nov 29, 2024
179423d
Add more info in message error in _pydatetime impl
donBarbos Nov 29, 2024
f691251
Update Modules/_datetimemodule.c
donBarbos Nov 29, 2024
d8973cf
Update Modules/_datetimemodule.c
donBarbos Nov 29, 2024
5eea62f
Update Modules/_datetimemodule.c
donBarbos Nov 29, 2024
79543cc
Update Modules/_datetimemodule.c for optimisation
donBarbos Nov 29, 2024
d174497
Revert last update Modules/_datetimemodule.c
donBarbos Nov 29, 2024
498c4ba
Update Modules/_datetimemodule.c
donBarbos Nov 29, 2024
216d0fe
Update Misc/NEWS.d message
donBarbos Nov 29, 2024
c409fec
Update Misc/NEWS.d message
donBarbos Nov 29, 2024
3f454f6
Update Misc/NEWS.d messa 8000 ge
donBarbos Nov 29, 2024
a2b8f7a
Update Misc/NEWS.d message
donBarbos Nov 29, 2024
209c338
Update Lib/_pydatetime.py
donBarbos Nov 30, 2024
0777aa5
Update Misc/NEWS.d/next/Library/2024-11-27-23-29-05.gh-issue-109798.O…
donBarbos Nov 30, 2024
4d31d33
Update Lib/_pydatetime.py
donBarbos Nov 30, 2024
f05ebba
Update Lib/_pydatetime.py
donBarbos Nov 30, 2024
f840105
Update Lib/_pydatetime.py
donBarbos Nov 30, 2024
61c95a5
Update Lib/_pydatetime.py
donBarbos Nov 30, 2024
2ab77b3
Update Lib/_pydatetime.py
donBarbos Nov 30, 2024
b1e272a
Update Lib/_pydatetime.py
donBarbos Nov 30, 2024
7a35bd4
Update Lib/_pydatetime.py
donBarbos Nov 30, 2024
cfd18cb
Add tests
donBarbos Nov 30, 2024
2827514
Update _pydatetime.py
donBarbos Dec 1, 2024
cd3bdc1
Update _pydatetime.py
donBarbos Dec 1, 2024
9915dfe
Change but got to not
donBarbos Dec 1, 2024
1da5a3a
Correct line break
donBarbos Dec 1, 2024
610f067
Update 2024-11-27-23-29-05.gh-issue-109798.OPj1CT.rst
pganssle Feb 12, 2025
410e0ce
Merge branch 'main' into issue-109798
pganssle Feb 12, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
42 changes: 23 additions & 19 deletions Lib/_pydatetime.py
Original file line number Diff line number Diff line change
Expand Up @@ -60,14 +60,14 @@ def _days_in_month(year, month):

def _days_before_month(year, month):
"year, month -> number of days in year preceding first day of month."
assert 1 <= month <= 12, 'month must be in 1..12'
assert 1 <= month <= 12, f"month must be in 1..12, but got {month}"
return _DAYS_BEFORE_MONTH[month] + (month > 2 and _is_leap(year))

def _ymd2ord(year, month, day):
"year, month, day -> ordinal, considering 01-Jan-0001 as day 1."
assert 1 <= month <= 12, 'month must be in 1..12'
assert 1 <= month <= 12, f"month must be in 1..12, but got {month}"
dim = _days_in_month(year, month)
assert 1 <= day <= dim, ('day must be in 1..%d' % dim)
assert 1 <= day <= dim, f"day must be in 1..{dim}, but got {day}"
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think it's worth changing these to ifs and ValueErrors; assertions are disabled when Python is ran with -O. (Though, it's theoretically possible that will break code for people relying on AssertionError. I'm not sure how big of an issue that is.)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

the practice of calling assert is also found in other modules and I think this issue should be raised separately (not in this PR)

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think it's fine to do it in this PR, because IMO, unifying the error types fall under the category of "normalizing." I'm not super crazy about it though--any opinion @erlend-aasland?

return (_days_before_year(year) +
_days_before_month(year, month) +
day)
Expand Down Expand Up @@ -512,7 +512,7 @@ def _parse_isoformat_time(tstr):
def _isoweek_to_gregorian(year, week, day):
# Year is bounded this way because 9999-12-31 is (9999, 52, 5)
if not MINYEAR <= year <= MAXYEAR:
raise ValueError(f"Year is out of range: {year}")
raise ValueError(f"year must be in {MINYEAR}..{MAXYEAR}, but got {year}")

if not 0 < week < 53:
out_of_range = True
Expand Down 8000 Expand Up @@ -545,7 +545,7 @@ def _isoweek_to_gregorian(year, week, day):
def _check_tzname(name):
if name is not None and not isinstance(name, str):
raise TypeError("tzinfo.tzname() must return None or string, "
"not '%s'" % type(name))
"not '%s'" % type(name).__name__)

# name is the offset-producing method, "utcoffset" or "dst".
# offset is what it returned.
Expand All @@ -559,23 +559,24 @@ def _check_utc_offset(name, offset):
return
if not isinstance(offset, timedelta):
raise TypeError("tzinfo.%s() must return None "
"or timedelta, not '%s'" % (name, type(offset)))
"or timedelta, not '%s'"
% (name, type(offset).__name__))
if not -timedelta(1) < offset < timedelta(1):
raise ValueError("%s()=%s, must be strictly between "
"-timedelta(hours=24) and timedelta(hours=24)" %
(name, offset))
raise ValueError("offset must be a timedelta "
"strictly between -timedelta(hours=24) and "
f"timedelta(hours=24), not {offset.__repr__()}")

def _check_date_fields(year, month, day):
year = _index(year)
month = _index(month)
day = _index(day)
if not MINYEAR <= year <= MAXYEAR:
raise ValueError('year must be in %d..%d' % (MINYEAR, MAXYEAR), year)
raise ValueError(f"year must be in {MINYEAR}..{MAXYEAR}, but got {year}")
if not 1 <= month <= 12:
raise ValueError('month must be in 1..12', month)
raise ValueError(f"month must be in 1..12, but got {month}")
dim = _days_in_month(year, month)
if not 1 <= day <= dim:
raise ValueError('day must be in 1..%d' % dim, day)
raise ValueError(f"day must be in 1..{dim}, but got {day}")
return year, month, day

def _check_time_fields(hour, minute, second, microsecond, fold):
Expand All @@ -584,20 +585,23 @@ def _check_time_fields(hour, minute, second, microsecond, fold):
second = _index(second)
microsecond = _index(microsecond)
if not 0 <= hour <= 23:
raise ValueError('hour must be in 0..23', hour)
raise ValueError(f"hour must be in 0..23, but got {hour}")
if not 0 <= minute <= 59:
raise ValueError('minute must be in 0..59', minute)
raise ValueError(f"minute must be in 0..59, but got {minute}")
if not 0 <= second <= 59:
raise ValueError('second must be in 0..59', second)
raise ValueError(f"second must be in 0..59, but got {second}")
if not 0 <= microsecond <= 999999:
raise ValueError('microsecond must be in 0..999999', microsecond)
raise ValueError(f"microsecond must be in 0..999999, but got {microsecond}")
if fold not in (0, 1):
raise ValueError('fold must be either 0 or 1', fold)
raise ValueError(f"fold must be either 0 or 1, but got {fold}")
return hour, minute, second, microsecond, fold

def _check_tzinfo_arg(tz):
if tz is not None and not isinstance(tz, tzinfo):
raise TypeError("tzinfo argument must be None or of a tzinfo subclass")
raise TypeError(
"tzinfo argument must be None or of a tzinfo subclass, "
f"not type '{type(tz).__name__}'"
)

def _divide_and_round(a, b):
"""divide a by b and round result to the nearest integer
Expand Down Expand Up @@ -2419,7 +2423,7 @@ def __new__(cls, offset, name=_Omitted):
if not cls._minoffset <= offset <= cls._maxoffset:
raise ValueError("offset must be a timedelta "
"strictly between -timedelta(hours=24) and "
"timedelta(hours=24).")
f"timedelta(hours=24), not {offset.__repr__()}")
return cls._create(offset, name)

def __init_subclass__(cls):
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
Update error messages to be the same in :mod:`datetime`. Patch by Semyon
Moroz.
54 changes: 32 additions & 22 deletions Modules/_datetimemodule.c
Original file line number Diff line number Diff line change
Expand Up @@ -637,17 +637,22 @@ check_date_args(int year, int month, int day)
{

if (year < MINYEAR || year > MAXYEAR) {
PyErr_Format(PyExc_ValueError, "year %i is out of range", year);
PyErr_Format(PyExc_ValueError,
"year must be in %d..%d, but got %d",
MINYEAR, MAXYEAR, year);
return -1;
}
if (month < 1 || month > 12) {
PyErr_SetString(PyExc_ValueError,
"month must be in 1..12");
PyErr_Format(PyExc_ValueError,
"month must be in 1..12, but got %d",
month);
return -1;
}
if (day < 1 || day > days_in_month(year, month)) {
PyErr_SetString(PyExc_ValueError,
"day is out of range for month");
int dim = days_in_month(year, month);
if (day < 1 || day > dim) {
PyErr_Format(PyExc_ValueError,
"day must be in 1..%d, but got %d",
dim, day);
return -1;
}
return 0;
Expand All @@ -660,28 +665,28 @@ static int
check_time_args(int h, int m, int s, int us, int fold)
{
if (h < 0 || h > 23) {
PyErr_SetString(PyExc_ValueError,
"hour must be in 0..23");
PyErr_Format(PyExc_ValueError,
"hour must be in 0..23, but got %i", h);
return -1;
}
if (m < 0 || m > 59) {
PyErr_SetString(PyExc_ValueError,
"minute must be in 0..59");
PyErr_Format(PyExc_ValueError,
"minute must be in 0..59, but got %i", m);
return -1;
}
if (s < 0 || s > 59) {
PyErr_SetString(PyExc_ValueError,
"second must be in 0..59");
PyErr_Format(PyExc_ValueError,
"second must be in 0..59, but got %i", s);
return -1;
}
if (us < 0 || us > 999999) {
PyErr_SetString(PyExc_ValueError,
"microsecond must be in 0..999999");
PyErr_Format(PyExc_ValueError,
"microsecond must be in 0..999999, but got %i", us);
return -1;
}
if (fold != 0 && fold != 1) {
PyErr_SetString(PyExc_ValueError,
"fold must be either 0 or 1");
PyErr_Format(PyExc_ValueError,
"fold must be either 0 or 1, but got %i", fold);
return -1;
}
return 0;
Expand Down Expand Up @@ -1436,7 +1441,7 @@ new_timezone(PyObject *offset, PyObject *name)
PyErr_Format(PyExc_ValueError, "offset must be a timedelta"
" strictly between -timedelta(hours=24) and"
" timedelta(hours=24),"
" not %R.", offset);
" not %R", offset);
return NULL;
}

Expand Down Expand Up @@ -1505,10 +1510,11 @@ call_tzinfo_method(PyObject *tzinfo, const char *name, PyObject *tzinfoarg)
GET_TD_SECONDS(offset) == 0 &&
GET_TD_MICROSECONDS(offset) < 1) ||
GET_TD_DAYS(offset) < -1 || GET_TD_DAYS(offset) >= 1) {
Py_DECREF(offset);
PyErr_Format(PyExc_ValueError, "offset must be a timedelta"
" strictly between -timedelta(hours=24) and"
" timedelta(hours=24).");
" timedelta(hours=24),"
" not %R", offset);
Py_DECREF(offset);
return NULL;
}
}
Expand Down Expand Up @@ -3387,7 +3393,9 @@ date_fromisocalendar(PyObject *cls, PyObject *args, PyObject *kw)
int rv = iso_to_ymd(year, week, day, &year, &month, &day);

if (rv == -4) {
PyErr_Format(PyExc_ValueError, "Year is out of range: %d", year);
PyErr_Format(PyExc_ValueError,
"year must be in %d..%d, but got %d",
MINYEAR, MAXYEAR, year);
return NULL;
}

Expand All @@ -3397,7 +3405,7 @@ date_fromisocalendar(PyObject *cls, PyObject *args, PyObject *kw)
}

if (rv == -3) {
PyErr_Format(PyExc_ValueError, "Invalid day: %d (range is [1, 7])",
PyErr_Format(PyExc_ValueError, "Invalid weekday: %d (range is [1, 7])",
day);
return NULL;
}
Expand Down Expand Up @@ -5357,7 +5365,9 @@ utc_to_seconds(int year, int month, int day,

/* ymd_to_ord() doesn't support year <= 0 */
if (year < MINYEAR || year > MAXYEAR) {
PyErr_Format(PyExc_ValueError, "year %i is out of range", year);
PyErr_Format(PyExc_ValueError,
"year must be in %d..%d, but got %d",
MINYEAR, MAXYEAR, year);
return -1;
}

Expand Down
Loading
0