From 08e54270f2dae5014f04d627739f71ecce5ad19e Mon Sep 17 00:00:00 2001 From: Tim Peters Date: Sat, 18 Jan 2003 03:53:49 +0000 Subject: [PATCH] SF patch 670012: Compatibility changes for _strptime.py. Patch from Brett Cannon: First, the 'y' directive now handles [00, 68] as a suffix for the 21st century while [69, 99] is treated as the suffix for the 20th century (this is for Open Group compatibility). strptime now returns default values that make it a valid date ... the ability to pass in a regex object to use instead of a format string (and the inverse ability to have strptime return a regex object) has been removed. This is in preparation for a future patch that will add some caching internally to get a speed boost. --- Lib/_strptime.py | 180 +++++++++++++++++++------------------- Lib/test/test_strptime.py | 85 ++++++++---------- 2 files changed, 127 insertions(+), 138 deletions(-) diff --git a/Lib/_strptime.py b/Lib/_strptime.py index 90665f31bcd..16944561c91 100644 --- a/Lib/_strptime.py +++ b/Lib/_strptime.py @@ -399,101 +399,99 @@ class TimeRE(dict): def strptime(data_string, format="%a %b %d %H:%M:%S %Y"): - """Return a time struct based on the input data and the format string. - - The format argument may either be a regular expression object compiled by - strptime(), or a format string. If False is passed in for data_string - then the re object calculated for format will be returned. The re object - must be used with the same locale as was used to compile the re object. - """ + """Return a time struct based on the input data and the format string.""" locale_time = LocaleTime() - if isinstance(format, RegexpType): - if format.pattern.find(locale_time.lang) == -1: - raise TypeError("re object not created with same language as " - "LocaleTime instance") - else: - compiled_re = format - else: - compiled_re = TimeRE(locale_time).compile(format) - if data_string is False: - return compiled_re - else: - found = compiled_re.match(data_string) - if not found: - raise ValueError("time data did not match format") - year = month = day = hour = minute = second = weekday = julian = tz =-1 - found_dict = found.groupdict() - for group_key in found_dict.iterkeys(): - if group_key == 'y': - year = int("%s%s" % - (time.strftime("%Y")[:-2], found_dict['y'])) - elif group_key == 'Y': - year = int(found_dict['Y']) - elif group_key == 'm': - month = int(found_dict['m']) - elif group_key == 'B': - month = _insensitiveindex(locale_time.f_month, found_dict['B']) - elif group_key == 'b': - month = _insensitiveindex(locale_time.a_month, found_dict['b']) - elif group_key == 'd': - day = int(found_dict['d']) - elif group_key is 'H': - hour = int(found_dict['H']) - elif group_key == 'I': - hour = int(found_dict['I']) - ampm = found_dict.get('p', '').lower() - # If there was no AM/PM indicator, we'll treat this like AM - if ampm in ('', locale_time.am_pm[0].lower()): - # We're in AM so the hour is correct unless we're - # looking at 12 midnight. - # 12 midnight == 12 AM == hour 0 - if hour == 12: - hour = 0 - elif ampm == locale_time.am_pm[1].lower(): - # We're in PM so we need to add 12 to the hour unless - # we're looking at 12 noon. - # 12 noon == 12 PM == hour 12 - if hour != 12: - hour += 12 - elif group_key == 'M': - minute = int(found_dict['M']) - elif group_key == 'S': - second = int(found_dict['S']) - elif group_key == 'A': - weekday = _insensitiveindex(locale_time.f_weekday, - found_dict['A']) - elif group_key == 'a': - weekday = _insensitiveindex(locale_time.a_weekday, - found_dict['a']) - elif group_key == 'w': - weekday = int(found_dict['w']) - if weekday == 0: - weekday = 6 - else: - weekday -= 1 - elif group_key == 'j': - julian = int(found_dict['j']) - elif group_key == 'Z': - found_zone = found_dict['Z'].lower() - if locale_time.timezone[0] == locale_time.timezone[1]: - pass #Deals with bad locale setup where timezone info is - # the same; first found on FreeBSD 4.4. - elif locale_time.timezone[0].lower() == found_zone: - tz = 0 - elif locale_time.timezone[1].lower() == found_zone: - tz = 1 - elif locale_time.timezone[2].lower() == found_zone: - tz = 0 - #XXX : If calculating fxns are never exposed to the general - # populous then just inline calculations. - if julian == -1 and year != -1 and month != -1 and day != -1: + compiled_re = TimeRE(locale_time).compile(format) + found = compiled_re.match(data_string) + if not found: + raise ValueError("time data did not match format") + year = 1900 + month = day = 1 + hour = minute = second = 0 + tz = -1 + # Defaulted to -1 so as to signal using functions to calc values + weekday = julian = -1 + found_dict = found.groupdict() + for group_key in found_dict.iterkeys(): + if group_key == 'y': + year = int(found_dict['y']) + # Open Group specification for strptime() states that a %y + #value in the range of [00, 68] is in the century 2000, while + #[69,99] is in the century 1900 + if year <= 68: + year += 2000 + else: + year += 1900 + elif group_key == 'Y': + year = int(found_dict['Y']) + elif group_key == 'm': + month = int(found_dict['m']) + elif group_key == 'B': + month = _insensitiveindex(locale_time.f_month, found_dict['B']) + elif group_key == 'b': + month = _insensitiveindex(locale_time.a_month, found_dict['b']) + elif group_key == 'd': + day = int(found_dict['d']) + elif group_key is 'H': + hour = int(found_dict['H']) + elif group_key == 'I': + hour = int(found_dict['I']) + ampm = found_dict.get('p', '').lower() + # If there was no AM/PM indicator, we'll treat this like AM + if ampm in ('', locale_time.am_pm[0].lower()): + # We're in AM so the hour is correct unless we're + # looking at 12 midnight. + # 12 midnight == 12 AM == hour 0 + if hour == 12: + hour = 0 + elif ampm == locale_time.am_pm[1].lower(): + # We're in PM so we need to add 12 to the hour unless + # we're looking at 12 noon. + # 12 noon == 12 PM == hour 12 + if hour != 12: + hour += 12 + elif group_key == 'M': + minute = int(found_dict['M']) + elif group_key == 'S': + second = int(found_dict['S']) + elif group_key == 'A': + weekday = _insensitiveindex(locale_time.f_weekday, + found_dict['A']) + elif group_key == 'a': + weekday = _insensitiveindex(locale_time.a_weekday, + found_dict['a']) + elif group_key == 'w': + weekday = int(found_dict['w']) + if weekday == 0: + weekday = 6 + else: + weekday -= 1 + elif group_key == 'j': + julian = int(found_dict['j']) + elif group_key == 'Z': + found_zone = found_dict['Z'].lower() + if locale_time.timezone[0] == locale_time.timezone[1]: + pass #Deals with bad locale setup where timezone info is + # the same; first found on FreeBSD 4.4. + elif locale_time.timezone[0].lower() == found_zone: + tz = 0 + elif locale_time.timezone[1].lower() == found_zone: + tz = 1 + elif locale_time.timezone[2].lower() == found_zone: + tz = -1 + #XXX : If calculating fxns are never exposed to the general + #populous then just inline calculations. Also might be able to use + #``datetime`` and the methods it provides. + if julian == -1: julian = julianday(year, month, day) - if (month == -1 or day == -1) and julian != -1 and year != -1: + else: # Assuming that if they bothered to include Julian day it will + #be accurate year, month, day = gregorian(julian, year) - if weekday == -1 and year != -1 and month != -1 and day != -1: + if weekday == -1: weekday = dayofweek(year, month, day) - return time.struct_time( - (year,month,day,hour,minute,second,weekday, julian,tz)) + return time.struct_time((year, month, day, + hour, minute, second, + weekday, julian, tz)) def _insensitiveindex(lst, findme): # Perform a case-insensitive index search. diff --git a/Lib/test/test_strptime.py b/Lib/test/test_strptime.py index 83c03b484ff..2510a90076c 100644 --- a/Lib/test/test_strptime.py +++ b/Lib/test/test_strptime.py @@ -124,6 +124,14 @@ class LocaleTime_Tests(unittest.TestCase): self.assertRaises(TypeError, _strptime.LocaleTime, timezone=range(1)) self.assertRaises(TypeError, _strptime.LocaleTime, timezone=range(3)) + def test_unknowntimezone(self): + # Handle timezone set to ('','') properly. + # Fixes bug #661354 + locale_time = _strptime.LocaleTime(timezone=('','')) + self.failUnless("%Z" not in locale_time.LC_date, + "when timezone == ('',''), string.replace('','%Z') is " + "occuring") + class TimeRETests(unittest.TestCase): """Tests for TimeRE.""" @@ -180,12 +188,19 @@ class TimeRETests(unittest.TestCase): found.group('b'))) for directive in ('a','A','b','B','c','d','H','I','j','m','M','p','S', 'U','w','W','x','X','y','Y','Z','%'): - compiled = self.time_re.compile("%%%s"% directive) - found = compiled.match(time.strftime("%%%s" % directive)) + compiled = self.time_re.compile("%" + directive) + found = compiled.match(time.strftime("%" + directive)) self.failUnless(found, "Matching failed on '%s' using '%s' regex" % - (time.strftime("%%%s" % directive), + (time.strftime("%" + directive), compiled.pattern)) + def test_blankpattern(self): + # Make sure when tuple or something has no values no regex is generated. + # Fixes bug #661354 + test_locale = _strptime.LocaleTime(timezone=('','')) + self.failUnless(_strptime.TimeRE(test_locale).pattern("%Z") == '', + "with timezone == ('',''), TimeRE().pattern('%Z') != ''") + class StrptimeTests(unittest.TestCase): """Tests for _strptime.strptime.""" @@ -198,21 +213,10 @@ class StrptimeTests(unittest.TestCase): self.assertRaises(ValueError, _strptime.strptime, data_string="%d", format="%A") - def test_returning_RE(self): - # Make sure that an re can be returned - strp_output = _strptime.strptime(False, "%Y") - self.failUnless(isinstance(strp_output, type(re.compile(''))), - "re object not returned correctly") - self.failUnless(_strptime.strptime("1999", strp_output), - "Use of re object failed") - bad_locale_time = _strptime.LocaleTime(lang="gibberish") - self.assertRaises(TypeError, _strptime.strptime, data_string='1999', - format=strp_output, locale_time=bad_locale_time) - def helper(self, directive, position): """Helper fxn in testing.""" - strf_output = time.strftime("%%%s" % directive, self.time_tuple) - strp_output = _strptime.strptime(strf_output, "%%%s" % directive) + strf_output = time.strftime("%" + directive, self.time_tuple) + strp_output = _strptime.strptime(strf_output, "%" + directive) self.failUnless(strp_output[position] == self.time_tuple[position], "testing of '%s' directive failed; '%s' -> %s != %s" % (directive, strf_output, strp_output[position], @@ -222,6 +226,14 @@ class StrptimeTests(unittest.TestCase): # Test that the year is handled properly for directive in ('y', 'Y'): self.helper(directive, 0) + # Must also make sure %y values are correct for bounds set by Open Group + for century, bounds in ((1900, ('69', '99')), (2000, ('00', '68'))): + for bound in bounds: + strp_output = _strptime.strptime(bound, '%y') + expected_result = century + int(bound) + self.failUnless(strp_output[0] == expected_result, + "'y' test failed; passed in '%s' " + "and returned '%s'" % (bound, strp_output[0])) def test_month(self): # Test for month directives @@ -262,7 +274,7 @@ class StrptimeTests(unittest.TestCase): # Test timezone directives. # When gmtime() is used with %Z, entire result of strftime() is empty. # Check for equal timezone names deals with bad locale info when this - # occurs; first found in FreeBSD 4.4 -current + # occurs; first found in FreeBSD 4.4. time_tuple = time.localtime() strf_output = time.strftime("%Z") #UTC does not have a timezone strp_output = _strptime.strptime(strf_output, "%Z") @@ -274,7 +286,7 @@ class StrptimeTests(unittest.TestCase): else: self.failUnless(strp_output[8] == -1, "LocaleTime().timezone has duplicate values but " - "timzone value not set to -1") + "timzone value not set to 0") def test_date_time(self): # Test %c directive @@ -309,6 +321,14 @@ class StrptimeTests(unittest.TestCase): self.failUnless(_strptime.strptime(strf_output.capitalize(), "%B"), "strptime does not handle capword names properly") + def test_defaults(self): + # Default return value should be (1900, 1, 1, 0, 0, 0, 0, 1, 0) + defaults = (1900, 1, 1, 0, 0, 0, 0, 1, -1) + strp_output = _strptime.strptime('1', '%m') + self.failUnless(strp_output == defaults, + "Default values for strptime() are incorrect;" + " %s != %s" % (strp_output, defaults)) + class FxnTests(unittest.TestCase): """Test functions that fill in info by validating result and are triggered properly.""" @@ -325,14 +345,6 @@ class FxnTests(unittest.TestCase): "julianday failed; %s != %s" % (result, self.time_tuple[7])) - def test_julianday_trigger(self): - # Make sure julianday is called - strf_output = time.strftime("%Y-%m-%d", self.time_tuple) - strp_output = _strptime.strptime(strf_output, "%Y-%m-%d") - self.failUnless(strp_output[7] == self.time_tuple[7], - "strptime did not trigger julianday(); %s != %s" % - (strp_output[7], self.time_tuple[7])) - def test_gregorian_result(self): # Test gregorian result = _strptime.gregorian(self.time_tuple[7], self.time_tuple[0]) @@ -340,17 +352,6 @@ class FxnTests(unittest.TestCase): self.failUnless(result == comparison, "gregorian() failed; %s != %s" % (result, comparison)) - def test_gregorian_trigger(self): - # Test that gregorian() is triggered - strf_output = time.strftime("%j %Y", self.time_tuple) - strp_output = _strptime.strptime(strf_output, "%j %Y") - self.failUnless(strp_output[1] == self.time_tuple[1] and - strp_output[2] == self.time_tuple[2], - "gregorian() not triggered; month -- %s != %s, " - "day -- %s != %s" % - (strp_output[1], self.time_tuple[1], strp_output[2], - self.time_tuple[2])) - def test_dayofweek_result(self): # Test dayofweek result = _strptime.dayofweek(self.time_tuple[0], self.time_tuple[1], @@ -359,15 +360,6 @@ class FxnTests(unittest.TestCase): self.failUnless(result == comparison, "dayofweek() failed; %s != %s" % (result, comparison)) - def test_dayofweek_trigger(self): - # Make sure dayofweek() gets triggered - strf_output = time.strftime("%Y-%m-%d", self.time_tuple) - strp_output = _strptime.strptime(strf_output, "%Y-%m-%d") - self.failUnless(strp_output[6] == self.time_tuple[6], - "triggering of dayofweek() failed; %s != %s" % - (strp_output[6], self.time_tuple[6])) - - class Strptime12AMPMTests(unittest.TestCase): """Test a _strptime regression in '%I %p' at 12 noon (12 PM)""" @@ -384,7 +376,6 @@ class JulianTests(unittest.TestCase): def test_all_julian_days(self): eq = self.assertEqual - # XXX: should 0 be accepted? for i in range(1, 367): # use 2004, since it is a leap year, we have 366 days eq(_strptime.strptime('%d 2004' % i, '%j %Y')[7], i)