Skip to content
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

gh-64376: Use Argument Clinic for datetime.date classmethods #20208

Closed
wants to merge 12 commits into from
3 changes: 0 additions & 3 deletions Lib/test/datetimetester.py
Original file line number Diff line number Diff line change
Expand Up @@ -1909,9 +1909,6 @@ def test_fromisocalendar_value_errors(self):
(10000, 1, 1),
(0, 1, 1),
(9999999, 1, 1),
(2<<32, 1, 1),
(2019, 2<<32, 1),
(2019, 1, 2<<32),
Comment on lines -1912 to -1914
Copy link
Contributor

Choose a reason for hiding this comment

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

Why are these removed?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I mentioned this in a PR comment:

Re: datetime.date.fromisocalendar
Note that this now can throw OverflowError instead of ValueError when
using the C version of datetime. There is precedent for this, e.g.,
datetime.date.fromordinal will throw an OverflowError with the C module
but ValueError with the Python.
Also, prior to this PR, the C version fromisocalendar would throw a ValueError while the Python version throws TypeError for issues with e.g, missing arguments.

Copy link
Contributor

Choose a reason for hiding this comment

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

Ah, thanks, I didn't make the connection! I'll take another look at that.

]

for isocal in isocals:
Expand Down
135 changes: 72 additions & 63 deletions Modules/_datetimemodule.c
Original file line number Diff line number Diff line change
Expand Up @@ -2876,13 +2876,18 @@ date_fromtimestamp(PyObject *cls, PyObject *obj)
cls);
}

/* Return new date from current time.
* We say this is equivalent to fromtimestamp(time.time()), and the
* only way to be sure of that is to *call* time.time(). That's not
* generally the same as calling C's time.
*/
/*[clinic input]
@classmethod
datetime.date.today

Current date or datetime.

Same as self.__class__.fromtimestamp(time.time())
[clinic start generated code]*/

static PyObject *
date_today(PyObject *cls, PyObject *dummy)
datetime_date_today_impl(PyTypeObject *type)
/*[clinic end generated code: output=d5474697df6b251c input=0dff9bfe3e2274b2]*/
{
PyObject *time;
PyObject *result;
Expand All @@ -2897,8 +2902,12 @@ date_today(PyObject *cls, PyObject *dummy)
* datetime.fromtimestamp. That's why we need all the accuracy
* time.time() delivers; if someone were gonzo about optimization,
* date.today() could get away with plain C time().
* Besides, we claim that this method is equivalent to
* fromtimestamp(time.time()), and the only way to be of that is to *call*
* time.time(). That's not generally the same as calling C's time.
*/
result = _PyObject_CallMethodIdOneArg(cls, &PyId_fromtimestamp, time);
result = _PyObject_CallMethodIdOneArg((PyObject *) type,
&PyId_fromtimestamp, time);
Py_DECREF(time);
return result;
}
Expand Down Expand Up @@ -2940,34 +2949,50 @@ datetime_date_fromtimestamp_capi(PyObject *cls, PyObject *args)
return result;
}

/* Return new date from proleptic Gregorian ordinal. Raises ValueError if
* the ordinal is out of range.
*/

/*[clinic input]
@classmethod
datetime.date.fromordinal

ordinal: int
/

int -> date corresponding to a proleptic Gregorian ordinal.

Raises ValueError if the ordinal is out of range.
[clinic start generated code]*/

static PyObject *
date_fromordinal(PyObject *cls, PyObject *args)
datetime_date_fromordinal_impl(PyTypeObject *type, int ordinal)
/*[clinic end generated code: output=ea5cc69d86614a6b input=1d7158c082e677fd]*/
{
PyObject *result = NULL;
int ordinal;

if (PyArg_ParseTuple(args, "i:fromordinal", &ordinal)) {
int year;
int month;
int day;
int year;
int month;
int day;

if (ordinal < 1)
PyErr_SetString(PyExc_ValueError, "ordinal must be "
">= 1");
else {
ord_to_ymd(ordinal, &year, &month, &day);
result = new_date_subclass_ex(year, month, day, cls);
}
if (ordinal < 1)
PyErr_SetString(PyExc_ValueError, "ordinal must be >= 1");
else {
ord_to_ymd(ordinal, &year, &month, &day);
result = new_date_subclass_ex(year, month, day, (PyObject *) type);
}
return result;
}

/* Return the new date from a string as generated by date.isoformat() */
/*[clinic input]
@classmethod
datetime.date.fromisoformat

date_string as dtstr: object
/

str -> Construct a date from the output of date.isoformat()
[clinic start generated code]*/

static PyObject *
date_fromisoformat(PyObject *cls, PyObject *dtstr)
datetime_date_fromisoformat(PyTypeObject *type, PyObject *dtstr)
/*[clinic end generated code: output=0c55fa0175e3e94b input=6bebe824e582fd1e]*/
{
assert(dtstr != NULL);

Expand Down Expand Up @@ -2998,33 +3023,32 @@ date_fromisoformat(PyObject *cls, PyObject *dtstr)
goto invalid_string_error;
}

return new_date_subclass_ex(year, month, day, cls);
return new_date_subclass_ex(year, month, day, (PyObject *) type);

invalid_string_error:
PyErr_Format(PyExc_ValueError, "Invalid isoformat string: %R", dtstr);
return NULL;
}


static PyObject *
date_fromisocalendar(PyObject *cls, PyObject *args, PyObject *kw)
{
static char *keywords[] = {
"year", "week", "day", NULL
};
/*[clinic input]
@classmethod
datetime.date.fromisocalendar

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");
year: int
week: int
day: int

}
Comment on lines -3020 to -3024
Copy link
Contributor

Choose a reason for hiding this comment

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

Now I see that before this PR we have code in fromisocalendar() to raise ValueError rather than OverflowError. Raising a ValueError seems like better behavior to me. Also, changing this would be considered backwards-incompatible, would require NEWS and What's New entries, and couldn't be backported.

I think we should find a way to retain this behavior, even if that means not using Argument Clinic for this method right now.

return NULL;
}
int, int, int -> Construct a date from the ISO year, week number and weekday.

This is the inverse of the date.isocalendar() function.
[clinic start generated code]*/

static PyObject *
datetime_date_fromisocalendar_impl(PyTypeObject *type, int year, int week,
int day)
/*[clinic end generated code: output=7b26e15115d24df6 input=e3daae6e2b75f1be]*/
{
// 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);
Expand Down Expand Up @@ -3062,7 +3086,7 @@ date_fromisocalendar(PyObject *cls, PyObject *args, PyObject *kw)

ord_to_ymd(day_1 + day_offset, &year, &month, &day);

return new_date_subclass_ex(year, month, day, cls);
return new_date_subclass_ex(year, month, day, (PyObject *) type);
}


Expand Down Expand Up @@ -3485,25 +3509,10 @@ static PyMethodDef date_methods[] = {

/* Class methods: */
DATETIME_DATE_FROMTIMESTAMP_METHODDEF

{"fromordinal", (PyCFunction)date_fromordinal, METH_VARARGS |
METH_CLASS,
PyDoc_STR("int -> date corresponding to a proleptic Gregorian "
"ordinal.")},

{"fromisoformat", (PyCFunction)date_fromisoformat, METH_O |
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()).")},
DATETIME_DATE_FROMORDINAL_METHODDEF
DATETIME_DATE_FROMISOFORMAT_METHODDEF
DATETIME_DATE_FROMISOCALENDAR_METHODDEF
DATETIME_DATE_TODAY_METHODDEF

/* Instance methods: */

Expand Down
109 changes: 108 additions & 1 deletion Modules/clinic/_datetimemodule.c.h

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.