diff -r 7c48bb929e6e Doc/library/datetime.rst --- a/Doc/library/datetime.rst Tue Mar 27 11:49:21 2012 +0200 +++ b/Doc/library/datetime.rst Sun Apr 22 16:08:39 2012 -0500 @@ -417,6 +417,15 @@ d``. +.. classmethod:: date.from_iso_week(year, week, day=1) + + Return the date corresponding to the ISO year, week and optionally day. + :exc:`ValueError` is raised unless ``1 <= week <= 53``, ``1 <= day <= 7``, + further :exc:`ValueError` is raised if the choosen week overflows the year. + The default behaviour, without supplying a day, returns the date for monday + of that week, if a ISO day is supplied that days date is returned. + + Class attributes: .. attribute:: date.min diff -r 7c48bb929e6e Lib/datetime.py --- a/Lib/datetime.py Tue Mar 27 11:49:21 2012 +0200 +++ b/Lib/datetime.py Sun Apr 22 16:08:39 2012 -0500 @@ -633,6 +633,7 @@ fromtimestamp() today() fromordinal() + from_iso_week() Operators: @@ -698,6 +699,21 @@ y, m, d = _ord2ymd(n) return cls(y, m, d) + @classmethod + def from_iso_week(cls, year, week, day=1): + """Construct a date from ISO year, week number and weekday.""" + if not 1 <= day <= 7: + raise ValueError("day = " + str(day) + + " invalid, acceptable range 1-7") + if not 1 <= week <= 53: + raise ValueError("week = " + str(week) + + " invalid, acceptable range 1-53") + self = date(year, 1, 4) + self += timedelta((week - 1) * 7 + day - self.isoweekday()) + if self.isocalendar()[0] != year: + raise ValueError("'week' within range but overflows to next year") + return self + # Conversions to string def __repr__(self): diff -r 7c48bb929e6e Lib/test/datetimetester.py --- a/Lib/test/datetimetester.py Tue Mar 27 11:49:21 2012 +0200 +++ b/Lib/test/datetimetester.py Sun Apr 22 16:08:39 2012 -0500 @@ -719,6 +719,8 @@ # Tests here won't pass if also run on datetime objects, so don't # subclass this to test datetimes too. + theclass = date + def test_delta_non_days_ignored(self): dt = date(2000, 1, 2) delta = timedelta(days=1, hours=2, minutes=3, seconds=4, @@ -748,6 +750,47 @@ dt2 = dt - delta self.assertEqual(dt2, dt - days) + def test_from_iso_week(self): + # This is an edge case, 1998 had 53 weeks + year, month, week, day = 1998, 12, 53, 28 + d = self.theclass.from_iso_week(year, week) + self.assertEqual(d.year, year) + self.assertEqual(d.month, month) + self.assertEqual(d.day, day) + + def test_from_iso_week_overflow(self): + # Test that we get the ValueError when the year has 52 weeks + year, week = 1999, 53 + self.assertRaises(ValueError, self.theclass.from_iso_week, year, + week) + + def test_from_iso_week_value_week(self): + # Test that we get the ValueError when we try setting an invalid week + year, week = 1999, 54 + self.assertRaises(ValueError, self.theclass.from_iso_week, year, + week) + year, week = 1999, 0 + self.assertRaises(ValueError, self.theclass.from_iso_week, year, + week) + + def test_from_iso_week_value_day(self): + # Test that we get the ValueError when we try setting an invalid week + year, week, day = 1999, 1, 0 + self.assertRaises(ValueError, self.theclass.from_iso_week, year, + week, day) + year, week, day = 1999, 1, 8 + self.assertRaises(ValueError, self.theclass.from_iso_week, year, + week, day) + + def test_from_iso_week_underflows_year(self): + # Test that week 1 can start in previous year. + year, week = 2013, 1 + past_year, month, day = 2012, 12, 31 + d = self.theclass.from_iso_week(year, week) + self.assertEqual(d.year, past_year) + self.assertEqual(d.month, month) + self.assertEqual(d.day, day) + class SubclassDate(date): sub_var = 1 @@ -980,6 +1023,7 @@ self.assertRaises(OverflowError, self.theclass.fromtimestamp, insane) + def test_today(self): import time diff -r 7c48bb929e6e Modules/_datetimemodule.c --- a/Modules/_datetimemodule.c Tue Mar 27 11:49:21 2012 +0200 +++ b/Modules/_datetimemodule.c Sun Apr 22 16:08:39 2012 -0500 @@ -2529,6 +2529,50 @@ return result; } +/* Returns a New date from year, week and optionally weekday, Raises ValueError + * if the arguments are out of range. ValueError is also raised if the choosen + * valid week overflows into next year. + */ +static PyObject * +date_from_iso_week(PyObject *cls, PyObject *args) +{ + PyObject *result = NULL; + PyDateTime_Delta *delta = NULL; + int year, week, day = 1; + if (PyArg_ParseTupleAndKeywords(args, kw, "iii", keywords, + &year, &week, &day) { + if (week < 1){ + PyErr_SetString(PyExc_ValueError, "week must be " + ">= 1"); + } + if (week > 53){ + PyErr_SetString(PyExc_ValueError, "week must be " + "<= 53"); + } + if (day < 1){ + PyErr_SetString(PyExc_ValueError, "day must be " + ">= 1"); + } + if (day > 7){ + PyErr_SetString(PyExc_ValueError, "day must be " + "<= 7"); + } + else { + result = date_new; + delta = new_delta_ex( + (week - 1) * 7 + day - date_isoweekday(result), + 0,0,1); /* days, seconds, microseconds, 1 */ + result = add_date_timedelta(result, delta); + } + if (GET_YEAR(result) != year) { + PyErr_SetString(PyExc_ValueError, "Year overflows into next " + "year"); + } + return result; + } + +} + /* * Date arithmetic. */