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.
This commit is contained in:
Tim Peters 2003-01-18 03:53:49 +00:00
parent e58962af4d
commit 08e54270f2
2 changed files with 127 additions and 138 deletions

View File

@ -399,101 +399,99 @@ class TimeRE(dict):
def strptime(data_string, format="%a %b %d %H:%M:%S %Y"): 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. """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.
"""
locale_time = LocaleTime() locale_time = LocaleTime()
if isinstance(format, RegexpType): compiled_re = TimeRE(locale_time).compile(format)
if format.pattern.find(locale_time.lang) == -1: found = compiled_re.match(data_string)
raise TypeError("re object not created with same language as " if not found:
"LocaleTime instance") raise ValueError("time data did not match format")
else: year = 1900
compiled_re = format month = day = 1
else: hour = minute = second = 0
compiled_re = TimeRE(locale_time).compile(format) tz = -1
if data_string is False: # Defaulted to -1 so as to signal using functions to calc values
return compiled_re weekday = julian = -1
else: found_dict = found.groupdict()
found = compiled_re.match(data_string) for group_key in found_dict.iterkeys():
if not found: if group_key == 'y':
raise ValueError("time data did not match format") year = int(found_dict['y'])
year = month = day = hour = minute = second = weekday = julian = tz =-1 # Open Group specification for strptime() states that a %y
found_dict = found.groupdict() #value in the range of [00, 68] is in the century 2000, while
for group_key in found_dict.iterkeys(): #[69,99] is in the century 1900
if group_key == 'y': if year <= 68:
year = int("%s%s" % year += 2000
(time.strftime("%Y")[:-2], found_dict['y'])) else:
elif group_key == 'Y': year += 1900
year = int(found_dict['Y']) elif group_key == 'Y':
elif group_key == 'm': year = int(found_dict['Y'])
month = int(found_dict['m']) elif group_key == 'm':
elif group_key == 'B': month = int(found_dict['m'])
month = _insensitiveindex(locale_time.f_month, found_dict['B']) elif group_key == 'B':
elif group_key == 'b': month = _insensitiveindex(locale_time.f_month, found_dict['B'])
month = _insensitiveindex(locale_time.a_month, found_dict['b']) elif group_key == 'b':
elif group_key == 'd': month = _insensitiveindex(locale_time.a_month, found_dict['b'])
day = int(found_dict['d']) elif group_key == 'd':
elif group_key is 'H': day = int(found_dict['d'])
hour = int(found_dict['H']) elif group_key is 'H':
elif group_key == 'I': hour = int(found_dict['H'])
hour = int(found_dict['I']) elif group_key == 'I':
ampm = found_dict.get('p', '').lower() hour = int(found_dict['I'])
# If there was no AM/PM indicator, we'll treat this like AM ampm = found_dict.get('p', '').lower()
if ampm in ('', locale_time.am_pm[0].lower()): # If there was no AM/PM indicator, we'll treat this like AM
# We're in AM so the hour is correct unless we're if ampm in ('', locale_time.am_pm[0].lower()):
# looking at 12 midnight. # We're in AM so the hour is correct unless we're
# 12 midnight == 12 AM == hour 0 # looking at 12 midnight.
if hour == 12: # 12 midnight == 12 AM == hour 0
hour = 0 if hour == 12:
elif ampm == locale_time.am_pm[1].lower(): hour = 0
# We're in PM so we need to add 12 to the hour unless elif ampm == locale_time.am_pm[1].lower():
# we're looking at 12 noon. # We're in PM so we need to add 12 to the hour unless
# 12 noon == 12 PM == hour 12 # we're looking at 12 noon.
if hour != 12: # 12 noon == 12 PM == hour 12
hour += 12 if hour != 12:
elif group_key == 'M': hour += 12
minute = int(found_dict['M']) elif group_key == 'M':
elif group_key == 'S': minute = int(found_dict['M'])
second = int(found_dict['S']) elif group_key == 'S':
elif group_key == 'A': second = int(found_dict['S'])
weekday = _insensitiveindex(locale_time.f_weekday, elif group_key == 'A':
found_dict['A']) weekday = _insensitiveindex(locale_time.f_weekday,
elif group_key == 'a': found_dict['A'])
weekday = _insensitiveindex(locale_time.a_weekday, elif group_key == 'a':
found_dict['a']) weekday = _insensitiveindex(locale_time.a_weekday,
elif group_key == 'w': found_dict['a'])
weekday = int(found_dict['w']) elif group_key == 'w':
if weekday == 0: weekday = int(found_dict['w'])
weekday = 6 if weekday == 0:
else: weekday = 6
weekday -= 1 else:
elif group_key == 'j': weekday -= 1
julian = int(found_dict['j']) elif group_key == 'j':
elif group_key == 'Z': julian = int(found_dict['j'])
found_zone = found_dict['Z'].lower() elif group_key == 'Z':
if locale_time.timezone[0] == locale_time.timezone[1]: found_zone = found_dict['Z'].lower()
pass #Deals with bad locale setup where timezone info is if locale_time.timezone[0] == locale_time.timezone[1]:
# the same; first found on FreeBSD 4.4. pass #Deals with bad locale setup where timezone info is
elif locale_time.timezone[0].lower() == found_zone: # the same; first found on FreeBSD 4.4.
tz = 0 elif locale_time.timezone[0].lower() == found_zone:
elif locale_time.timezone[1].lower() == found_zone: tz = 0
tz = 1 elif locale_time.timezone[1].lower() == found_zone:
elif locale_time.timezone[2].lower() == found_zone: tz = 1
tz = 0 elif locale_time.timezone[2].lower() == found_zone:
#XXX <bc>: If calculating fxns are never exposed to the general tz = -1
# populous then just inline calculations. #XXX <bc>: If calculating fxns are never exposed to the general
if julian == -1 and year != -1 and month != -1 and day != -1: #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) 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) 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) weekday = dayofweek(year, month, day)
return time.struct_time( return time.struct_time((year, month, day,
(year,month,day,hour,minute,second,weekday, julian,tz)) hour, minute, second,
weekday, julian, tz))
def _insensitiveindex(lst, findme): def _insensitiveindex(lst, findme):
# Perform a case-insensitive index search. # Perform a case-insensitive index search.

View File

@ -124,6 +124,14 @@ class LocaleTime_Tests(unittest.TestCase):
self.assertRaises(TypeError, _strptime.LocaleTime, timezone=range(1)) self.assertRaises(TypeError, _strptime.LocaleTime, timezone=range(1))
self.assertRaises(TypeError, _strptime.LocaleTime, timezone=range(3)) 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): class TimeRETests(unittest.TestCase):
"""Tests for TimeRE.""" """Tests for TimeRE."""
@ -180,12 +188,19 @@ class TimeRETests(unittest.TestCase):
found.group('b'))) found.group('b')))
for directive in ('a','A','b','B','c','d','H','I','j','m','M','p','S', for directive in ('a','A','b','B','c','d','H','I','j','m','M','p','S',
'U','w','W','x','X','y','Y','Z','%'): 'U','w','W','x','X','y','Y','Z','%'):
compiled = self.time_re.compile("%%%s"% directive) compiled = self.time_re.compile("%" + directive)
found = compiled.match(time.strftime("%%%s" % directive)) found = compiled.match(time.strftime("%" + directive))
self.failUnless(found, "Matching failed on '%s' using '%s' regex" % self.failUnless(found, "Matching failed on '%s' using '%s' regex" %
(time.strftime("%%%s" % directive), (time.strftime("%" + directive),
compiled.pattern)) 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): class StrptimeTests(unittest.TestCase):
"""Tests for _strptime.strptime.""" """Tests for _strptime.strptime."""
@ -198,21 +213,10 @@ class StrptimeTests(unittest.TestCase):
self.assertRaises(ValueError, _strptime.strptime, data_string="%d", self.assertRaises(ValueError, _strptime.strptime, data_string="%d",
format="%A") 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): def helper(self, directive, position):
"""Helper fxn in testing.""" """Helper fxn in testing."""
strf_output = time.strftime("%%%s" % directive, self.time_tuple) strf_output = time.strftime("%" + directive, self.time_tuple)
strp_output = _strptime.strptime(strf_output, "%%%s" % directive) strp_output = _strptime.strptime(strf_output, "%" + directive)
self.failUnless(strp_output[position] == self.time_tuple[position], self.failUnless(strp_output[position] == self.time_tuple[position],
"testing of '%s' directive failed; '%s' -> %s != %s" % "testing of '%s' directive failed; '%s' -> %s != %s" %
(directive, strf_output, strp_output[position], (directive, strf_output, strp_output[position],
@ -222,6 +226,14 @@ class StrptimeTests(unittest.TestCase):
# Test that the year is handled properly # Test that the year is handled properly
for directive in ('y', 'Y'): for directive in ('y', 'Y'):
self.helper(directive, 0) 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): def test_month(self):
# Test for month directives # Test for month directives
@ -262,7 +274,7 @@ class StrptimeTests(unittest.TestCase):
# Test timezone directives. # Test timezone directives.
# When gmtime() is used with %Z, entire result of strftime() is empty. # When gmtime() is used with %Z, entire result of strftime() is empty.
# Check for equal timezone names deals with bad locale info when this # 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() time_tuple = time.localtime()
strf_output = time.strftime("%Z") #UTC does not have a timezone strf_output = time.strftime("%Z") #UTC does not have a timezone
strp_output = _strptime.strptime(strf_output, "%Z") strp_output = _strptime.strptime(strf_output, "%Z")
@ -274,7 +286,7 @@ class StrptimeTests(unittest.TestCase):
else: else:
self.failUnless(strp_output[8] == -1, self.failUnless(strp_output[8] == -1,
"LocaleTime().timezone has duplicate values but " "LocaleTime().timezone has duplicate values but "
"timzone value not set to -1") "timzone value not set to 0")
def test_date_time(self): def test_date_time(self):
# Test %c directive # Test %c directive
@ -309,6 +321,14 @@ class StrptimeTests(unittest.TestCase):
self.failUnless(_strptime.strptime(strf_output.capitalize(), "%B"), self.failUnless(_strptime.strptime(strf_output.capitalize(), "%B"),
"strptime does not handle capword names properly") "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): class FxnTests(unittest.TestCase):
"""Test functions that fill in info by validating result and are triggered """Test functions that fill in info by validating result and are triggered
properly.""" properly."""
@ -325,14 +345,6 @@ class FxnTests(unittest.TestCase):
"julianday failed; %s != %s" % "julianday failed; %s != %s" %
(result, self.time_tuple[7])) (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): def test_gregorian_result(self):
# Test gregorian # Test gregorian
result = _strptime.gregorian(self.time_tuple[7], self.time_tuple[0]) result = _strptime.gregorian(self.time_tuple[7], self.time_tuple[0])
@ -340,17 +352,6 @@ class FxnTests(unittest.TestCase):
self.failUnless(result == comparison, self.failUnless(result == comparison,
"gregorian() failed; %s != %s" % (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): def test_dayofweek_result(self):
# Test dayofweek # Test dayofweek
result = _strptime.dayofweek(self.time_tuple[0], self.time_tuple[1], result = _strptime.dayofweek(self.time_tuple[0], self.time_tuple[1],
@ -359,15 +360,6 @@ class FxnTests(unittest.TestCase):
self.failUnless(result == comparison, self.failUnless(result == comparison,
"dayofweek() failed; %s != %s" % (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): class Strptime12AMPMTests(unittest.TestCase):
"""Test a _strptime regression in '%I %p' at 12 noon (12 PM)""" """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): def test_all_julian_days(self):
eq = self.assertEqual eq = self.assertEqual
# XXX: should 0 be accepted?
for i in range(1, 367): for i in range(1, 367):
# use 2004, since it is a leap year, we have 366 days # use 2004, since it is a leap year, we have 366 days
eq(_strptime.strptime('%d 2004' % i, '%j %Y')[7], i) eq(_strptime.strptime('%d 2004' % i, '%j %Y')[7], i)