@@ -3003,6 +3003,67 @@ date_fromisoformat(PyObject *cls, PyObject *dtstr)
3003
3003
return NULL ;
3004
3004
}
3005
3005
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
+
3006
3067
/*
3007
3068
* Date arithmetic.
3008
3069
*/
@@ -3296,6 +3357,12 @@ static PyMethodDef date_methods[] = {
3296
3357
METH_CLASS ,
3297
3358
PyDoc_STR ("str -> Construct a date from the output of date.isoformat()" )},
3298
3359
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
+
3299
3366
{"today" , (PyCFunction )date_today , METH_NOARGS | METH_CLASS ,
3300
3367
PyDoc_STR ("Current date or datetime: same as "
3301
3368
"self.__class__.fromtimestamp(time.time())." )},
0 commit comments