diff --git a/Lib/_strptime.py b/Lib/_strptime.py index 3fb56020e29..0d7dec4dfb2 100644 --- a/Lib/_strptime.py +++ b/Lib/_strptime.py @@ -273,6 +273,27 @@ _TimeRE_cache = TimeRE() _CACHE_MAX_SIZE = 5 # Max number of regexes stored in _regex_cache _regex_cache = {} +def _calc_julian_from_U_or_W(year, week_of_year, day_of_week, week_starts_Mon): + """Calculate the Julian day based on the year, week of the year, and day of + the week, with week_start_day representing whether the week of the year + assumes the week starts on Sunday or Monday (6 or 0).""" + first_weekday = datetime_date(year, 1, 1).weekday() + # If we are dealing with the %U directive (week starts on Sunday), it's + # easier to just shift the view to Sunday being the first day of the + # week. + if not week_starts_Mon: + first_weekday = (first_weekday + 1) % 7 + day_of_week = (day_of_week + 1) % 7 + # Need to watch out for a week 0 (when the first day of the year is not + # the same as that specified by %U or %W). + week_0_length = (7 - first_weekday) % 7 + if week_of_year == 0: + return 1 + day_of_week - first_weekday + else: + days_to_week = week_0_length + (7 * (week_of_year - 1)) + return 1 + days_to_week + day_of_week + + def strptime(data_string, format="%a %b %d %H:%M:%S %Y"): """Return a time struct based on the input string and the format string.""" global _TimeRE_cache, _regex_cache @@ -385,10 +406,10 @@ def strptime(data_string, format="%a %b %d %H:%M:%S %Y"): elif group_key in ('U', 'W'): week_of_year = int(found_dict[group_key]) if group_key == 'U': - # U starts week on Sunday + # U starts week on Sunday. week_of_year_start = 6 else: - # W starts week on Monday + # W starts week on Monday. week_of_year_start = 0 elif group_key == 'Z': # Since -1 is default value only need to worry about setting tz if @@ -406,42 +427,20 @@ def strptime(data_string, format="%a %b %d %H:%M:%S %Y"): tz = value break # If we know the week of the year and what day of that week, we can figure - # out the Julian day of the year - # Calculations below assume 0 is a Monday + # out the Julian day of the year. if julian == -1 and week_of_year != -1 and weekday != -1: - # Calculate how many days in week 0 - first_weekday = datetime_date(year, 1, 1).weekday() - preceeding_days = 7 - first_weekday - if preceeding_days == 7: - preceeding_days = 0 - # Adjust for U directive so that calculations are not dependent on - # directive used to figure out week of year - if weekday == 6 and week_of_year_start == 6: - week_of_year -= 1 - # If a year starts and ends on a Monday but a week is specified to - # start on a Sunday we need to up the week to counter-balance the fact - # that with %W that first Monday starts week 1 while with %U that is - # week 0 and thus shifts everything by a week - if weekday == 0 and first_weekday == 0 and week_of_year_start == 6: - week_of_year += 1 - # If in week 0, then just figure out how many days from Jan 1 to day of - # week specified, else calculate by multiplying week of year by 7, - # adding in days in week 0, and the number of days from Monday to the - # day of the week - if week_of_year == 0: - julian = 1 + weekday - first_weekday - else: - days_to_week = preceeding_days + (7 * (week_of_year - 1)) - julian = 1 + days_to_week + weekday + week_starts_Mon = True if week_of_year_start == 0 else False + julian = _calc_julian_from_U_or_W(year, week_of_year, weekday, + week_starts_Mon) # Cannot pre-calculate datetime_date() since can change in Julian - #calculation and thus could have different value for the day of the week - #calculation + # calculation and thus could have different value for the day of the week + # calculation. if julian == -1: # Need to add 1 to result since first day of the year is 1, not 0. julian = datetime_date(year, month, day).toordinal() - \ datetime_date(year, 1, 1).toordinal() + 1 else: # Assume that if they bothered to include Julian day it will - #be accurate + # be accurate. datetime_result = datetime_date.fromordinal((julian - 1) + datetime_date(year, 1, 1).toordinal()) year = datetime_result.year month = datetime_result.month diff --git a/Lib/test/test_strptime.py b/Lib/test/test_strptime.py index ba65649857e..56e1ab87aa2 100644 --- a/Lib/test/test_strptime.py +++ b/Lib/test/test_strptime.py @@ -463,6 +463,10 @@ class CalculationTests(unittest.TestCase): "of the year") test_helper((1917, 12, 31), "Dec 31 on Monday with year starting and " "ending on Monday") + test_helper((2007, 01, 07), "First Sunday of 2007") + test_helper((2007, 01, 14), "Second Sunday of 2007") + test_helper((2006, 12, 31), "Last Sunday of 2006") + test_helper((2006, 12, 24), "Second to last Sunday of 2006") class CacheTests(unittest.TestCase): diff --git a/Misc/NEWS b/Misc/NEWS index 3fad86217a2..8bbae96d320 100644 --- a/Misc/NEWS +++ b/Misc/NEWS @@ -123,6 +123,8 @@ Core and builtins Library ------- +- Bug #1643943: Fix time.strptime's support for the %U directive. + - Patch #1643874: memory leak in ctypes fixed. - Patch #1627441: close sockets properly in urllib2. @@ -130,12 +132,12 @@ Library - Bug #494589: make ntpath.expandvars behave according to its docstring. - Changed platform module API python_version_tuple() to actually - return a tuple (it used to return a list) + return a tuple (it used to return a list). - Added new platform module APIs python_branch(), python_revision(), - python_implementation() and linux_distribution() + python_implementation() and linux_distribution(). -- Added support for IronPython and Jython to the platform module +- Added support for IronPython and Jython to the platform module. - The sets module has been deprecated. Use the built-in set/frozenset types instead.