@@ -3003,6 +3003,67 @@ date_fromisoformat(PyObject *cls, PyObject *dtstr)
30033003 return NULL ;
30043004}
30053005
3006+
3007+ static PyObject *
3008+ date_fromisocalendar (PyObject * cls , PyObject * args , PyObject * kw )
3009+ {
3010+ static char * keywords [] = {
3011+ "year" , "week" , "day" , NULL
3012+ };
3013+
3014+ int year , week , day ;
3015+ if (PyArg_ParseTupleAndKeywords (args , kw , "iii:fromisocalendar" ,
3016+ keywords ,
3017+ & year , & week , & day ) == 0 ) {
3018+ if (PyErr_ExceptionMatches (PyExc_OverflowError )) {
3019+ PyErr_Format (PyExc_ValueError ,
3020+ "ISO calendar component out of range" );
3021+
3022+ }
3023+ return NULL ;
3024+ }
3025+
3026+ // Year is bounded to 0 < year < 10000 because 9999-12-31 is (9999, 52, 5)
3027+ if (year < MINYEAR || year > MAXYEAR ) {
3028+ PyErr_Format (PyExc_ValueError , "Year is out of range: %d" , year );
3029+ return NULL ;
3030+ }
3031+
3032+ if (week <= 0 || week >= 53 ) {
3033+ int out_of_range = 1 ;
3034+ if (week == 53 ) {
3035+ // ISO years have 53 weeks in it on years starting with a Thursday
3036+ // and on leap years starting on Wednesday
3037+ int first_weekday = weekday (year , 1 , 1 );
3038+ if (first_weekday == 3 || (first_weekday == 2 && is_leap (year ))) {
3039+ out_of_range = 0 ;
3040+ }
3041+ }
3042+
3043+ if (out_of_range ) {
3044+ PyErr_Format (PyExc_ValueError , "Invalid week: %d" , week );
3045+ return NULL ;
3046+ }
3047+ }
3048+
3049+ if (day <= 0 || day >= 8 ) {
3050+ PyErr_Format (PyExc_ValueError , "Invalid day: %d (range is [1, 7])" ,
3051+ day );
3052+ return NULL ;
3053+ }
3054+
3055+ // Convert (Y, W, D) to (Y, M, D) in-place
3056+ int day_1 = iso_week1_monday (year );
3057+
3058+ int month = week ;
3059+ int day_offset = (month - 1 )* 7 + day - 1 ;
3060+
3061+ ord_to_ymd (day_1 + day_offset , & year , & month , & day );
3062+
3063+ return new_date_subclass_ex (year , month , day , cls );
3064+ }
3065+
3066+
30063067/*
30073068 * Date arithmetic.
30083069 */
@@ -3296,6 +3357,12 @@ static PyMethodDef date_methods[] = {
32963357 METH_CLASS ,
32973358 PyDoc_STR ("str -> Construct a date from the output of date.isoformat()" )},
32983359
3360+ {"fromisocalendar" , (PyCFunction )(void (* )(void ))date_fromisocalendar ,
3361+ METH_VARARGS | METH_KEYWORDS | METH_CLASS ,
3362+ PyDoc_STR ("int, int, int -> Construct a date from the ISO year, week "
3363+ "number and weekday.\n\n"
3364+ "This is the inverse of the date.isocalendar() function" )},
3365+
32993366 {"today" , (PyCFunction )date_today , METH_NOARGS | METH_CLASS ,
33003367 PyDoc_STR ("Current date or datetime: same as "
33013368 "self.__class__.fromtimestamp(time.time())." )},
0 commit comments