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.
diff --git a/Lib/_strptime.py b/Lib/_strptime.py
index 90665f3..1694456 100644
--- a/Lib/_strptime.py
+++ b/Lib/_strptime.py
@@ -399,101 +399,99 @@
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 <bc>: 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 <bc>: 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 83c03b4..2510a90 100644
--- a/Lib/test/test_strptime.py
+++ b/Lib/test/test_strptime.py
@@ -124,6 +124,14 @@
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 @@
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 @@
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 @@
# 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 @@
# 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 @@
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 @@
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 @@
"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 @@
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 @@
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 @@
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)