Commit 08e54270 authored by Tim Peters's avatar Tim Peters

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.
parent e58962af
...@@ -399,34 +399,29 @@ class TimeRE(dict): ...@@ -399,34 +399,29 @@ 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):
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) compiled_re = TimeRE(locale_time).compile(format)
if data_string is False:
return compiled_re
else:
found = compiled_re.match(data_string) found = compiled_re.match(data_string)
if not found: if not found:
raise ValueError("time data did not match format") raise ValueError("time data did not match format")
year = month = day = hour = minute = second = weekday = julian = tz =-1 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() found_dict = found.groupdict()
for group_key in found_dict.iterkeys(): for group_key in found_dict.iterkeys():
if group_key == 'y': if group_key == 'y':
year = int("%s%s" % year = int(found_dict['y'])
(time.strftime("%Y")[:-2], 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': elif group_key == 'Y':
year = int(found_dict['Y']) year = int(found_dict['Y'])
elif group_key == 'm': elif group_key == 'm':
...@@ -483,17 +478,20 @@ def strptime(data_string, format="%a %b %d %H:%M:%S %Y"): ...@@ -483,17 +478,20 @@ def strptime(data_string, format="%a %b %d %H:%M:%S %Y"):
elif locale_time.timezone[1].lower() == found_zone: elif locale_time.timezone[1].lower() == found_zone:
tz = 1 tz = 1
elif locale_time.timezone[2].lower() == found_zone: elif locale_time.timezone[2].lower() == found_zone:
tz = 0 tz = -1
#XXX <bc>: If calculating fxns are never exposed to the general #XXX <bc>: If calculating fxns are never exposed to the general
# populous then just inline calculations. #populous then just inline calculations. Also might be able to use
if julian == -1 and year != -1 and month != -1 and day != -1: #``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.
......
...@@ -124,6 +124,14 @@ class LocaleTime_Tests(unittest.TestCase): ...@@ -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): ...@@ -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): ...@@ -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): ...@@ -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): ...@@ -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): ...@@ -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): ...@@ -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): ...@@ -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): ...@@ -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): ...@@ -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): ...@@ -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)
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment