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:
parent
e58962af4d
commit
08e54270f2
180
Lib/_strptime.py
180
Lib/_strptime.py
|
@ -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.
|
||||||
|
|
|
@ -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)
|
||||||
|
|
Loading…
Reference in New Issue