summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--Lib/_strptime.py411
-rw-r--r--Lib/test/test_strptime.py114
2 files changed, 190 insertions, 335 deletions
diff --git a/Lib/_strptime.py b/Lib/_strptime.py
index ed8ed35..353d42b 100644
--- a/Lib/_strptime.py
+++ b/Lib/_strptime.py
@@ -1,17 +1,14 @@
"""Strptime-related classes and functions.
CLASSES:
- LocaleTime -- Discovers and/or stores locale-specific time information
+ LocaleTime -- Discovers and stores locale-specific time information
TimeRE -- Creates regexes for pattern matching a string of text containing
- time information as is returned by time.strftime()
+ time information
FUNCTIONS:
_getlang -- Figure out what language is being used for the locale
strptime -- Calculates the time struct represented by the passed-in string
-Requires Python 2.2.1 or higher (mainly because of the use of property()).
-Can be used in Python 2.2 if the following line is added:
- True = 1; False = 0
"""
import time
import locale
@@ -19,6 +16,11 @@ import calendar
from re import compile as re_compile
from re import IGNORECASE
from datetime import date as datetime_date
+from sets import ImmutableSet as sets_ImmutableSet
+try:
+ from thread import allocate_lock as _thread_allocate_lock
+except:
+ from dummy_thread import allocate_lock as _thread_allocate_lock
__author__ = "Brett Cannon"
__email__ = "brett@python.org"
@@ -32,12 +34,7 @@ def _getlang():
class LocaleTime(object):
"""Stores and handles locale-specific information related to time.
- This is not thread-safe! Attributes are lazily calculated and no
- precaution is taken to check to see if the locale information has changed
- since the creation of the instance in use.
-
- ATTRIBUTES (all read-only after instance creation! Instance variables that
- store the values have mangled names):
+ ATTRIBUTES:
f_weekday -- full weekday names (7-item list)
a_weekday -- abbreviated weekday names (7-item list)
f_month -- full weekday names (14-item list; dummy value in [0], which
@@ -49,63 +46,37 @@ class LocaleTime(object):
LC_date -- format string for date representation (string)
LC_time -- format string for time representation (string)
timezone -- daylight- and non-daylight-savings timezone representation
- (3-item list; code tacks on blank item at end for
- possible lack of timezone such as UTC)
- lang -- Language used by instance (string)
+ (2-item list of sets)
+ lang -- Language used by instance (2-item tuple)
"""
- def __init__(self, f_weekday=None, a_weekday=None, f_month=None,
- a_month=None, am_pm=None, LC_date_time=None, LC_time=None,
- LC_date=None, timezone=None, lang=None):
- """Optionally set attributes with passed-in values."""
- if f_weekday is None:
- self.__f_weekday = None
- elif len(f_weekday) == 7:
- self.__f_weekday = list(f_weekday)
- else:
- raise TypeError("full weekday names must be a 7-item sequence")
- if a_weekday is None:
- self.__a_weekday = None
- elif len(a_weekday) == 7:
- self.__a_weekday = list(a_weekday)
- else:
- raise TypeError(
- "abbreviated weekday names must be a 7-item sequence")
- if f_month is None:
- self.__f_month = None
- elif len(f_month) == 12:
- self.__f_month = self.__pad(f_month, True)
- else:
- raise TypeError("full month names must be a 12-item sequence")
- if a_month is None:
- self.__a_month = None
- elif len(a_month) == 12:
- self.__a_month = self.__pad(a_month, True)
- else:
- raise TypeError(
- "abbreviated month names must be a 12-item sequence")
- if am_pm is None:
- self.__am_pm = None
- elif len(am_pm) == 2:
- self.__am_pm = am_pm
- else:
- raise TypeError("AM/PM representation must be a 2-item sequence")
- self.__LC_date_time = LC_date_time
- self.__LC_time = LC_time
- self.__LC_date = LC_date
- self.__timezone = timezone
- if timezone:
- if len(timezone) != 2:
- raise TypeError("timezone names must contain 2 items")
- else:
- self.__timezone = self.__pad(timezone, False)
- if lang:
- self.__lang = lang
- else:
- self.__lang = _getlang()
+ def __init__(self):
+ """Set all attributes.
+
+ Order of methods called matters for dependency reasons.
+
+ The locale language is set at the offset and then checked again before
+ exiting. This is to make sure that the attributes were not set with a
+ mix of information from more than one locale. This would most likely
+ happen when using threads where one thread calls a locale-dependent
+ function while another thread changes the locale while the function in
+ the other thread is still running. Proper coding would call for
+ locks to prevent changing the locale while locale-dependent code is
+ running. The check here is done in case someone does not think about
+ doing this.
+
+ """
+ self.lang = _getlang()
+ self.__calc_weekday()
+ self.__calc_month()
+ self.__calc_am_pm()
+ self.__calc_timezone()
+ self.__calc_date_time()
+ if _getlang() != self.lang:
+ raise ValueError("locale changed during initialization")
def __pad(self, seq, front):
- # Add '' to seq to either front (is True), else the back.
+ # Add '' to seq to either the front (is True), else the back.
seq = list(seq)
if front:
seq.insert(0, '')
@@ -113,112 +84,23 @@ class LocaleTime(object):
seq.append('')
return seq
- def __set_nothing(self, stuff):
- # Raise TypeError when trying to set an attribute.
- raise TypeError("attribute does not support assignment")
-
- def __get_f_weekday(self):
- # Fetch self.f_weekday.
- if not self.__f_weekday:
- self.__calc_weekday()
- return self.__f_weekday
-
- def __get_a_weekday(self):
- # Fetch self.a_weekday.
- if not self.__a_weekday:
- self.__calc_weekday()
- return self.__a_weekday
-
- f_weekday = property(__get_f_weekday, __set_nothing,
- doc="Full weekday names")
- a_weekday = property(__get_a_weekday, __set_nothing,
- doc="Abbreviated weekday names")
-
- def __get_f_month(self):
- # Fetch self.f_month.
- if not self.__f_month:
- self.__calc_month()
- return self.__f_month
-
- def __get_a_month(self):
- # Fetch self.a_month.
- if not self.__a_month:
- self.__calc_month()
- return self.__a_month
-
- f_month = property(__get_f_month, __set_nothing,
- doc="Full month names (dummy value at index 0)")
- a_month = property(__get_a_month, __set_nothing,
- doc="Abbreviated month names (dummy value at index 0)")
-
- def __get_am_pm(self):
- # Fetch self.am_pm.
- if not self.__am_pm:
- self.__calc_am_pm()
- return self.__am_pm
-
- am_pm = property(__get_am_pm, __set_nothing, doc="AM/PM representation")
-
- def __get_timezone(self):
- # Fetch self.timezone.
- if not self.__timezone:
- self.__calc_timezone()
- return self.__timezone
-
- timezone = property(__get_timezone, __set_nothing,
- doc="Timezone representation (dummy value at index 2)")
-
- def __get_LC_date_time(self):
- # Fetch self.LC_date_time.
- if not self.__LC_date_time:
- self.__calc_date_time()
- return self.__LC_date_time
-
- def __get_LC_date(self):
- # Fetch self.LC_date.
- if not self.__LC_date:
- self.__calc_date_time()
- return self.__LC_date
-
- def __get_LC_time(self):
- # Fetch self.LC_time.
- if not self.__LC_time:
- self.__calc_date_time()
- return self.__LC_time
-
- LC_date_time = property(
- __get_LC_date_time, __set_nothing,
- doc=
- "Format string for locale's date/time representation ('%c' format)")
- LC_date = property(__get_LC_date, __set_nothing,
- doc="Format string for locale's date representation ('%x' format)")
- LC_time = property(__get_LC_time, __set_nothing,
- doc="Format string for locale's time representation ('%X' format)")
-
- lang = property(lambda self: self.__lang, __set_nothing,
- doc="Language used for instance")
-
def __calc_weekday(self):
- # Set self.__a_weekday and self.__f_weekday using the calendar
+ # Set self.a_weekday and self.f_weekday using the calendar
# module.
- a_weekday = [calendar.day_abbr[i] for i in range(7)]
- f_weekday = [calendar.day_name[i] for i in range(7)]
- if not self.__a_weekday:
- self.__a_weekday = a_weekday
- if not self.__f_weekday:
- self.__f_weekday = f_weekday
+ a_weekday = [calendar.day_abbr[i].lower() for i in range(7)]
+ f_weekday = [calendar.day_name[i].lower() for i in range(7)]
+ self.a_weekday = a_weekday
+ self.f_weekday = f_weekday
def __calc_month(self):
- # Set self.__f_month and self.__a_month using the calendar module.
- a_month = [calendar.month_abbr[i] for i in range(13)]
- f_month = [calendar.month_name[i] for i in range(13)]
- if not self.__a_month:
- self.__a_month = a_month
- if not self.__f_month:
- self.__f_month = f_month
+ # Set self.f_month and self.a_month using the calendar module.
+ a_month = [calendar.month_abbr[i].lower() for i in range(13)]
+ f_month = [calendar.month_name[i].lower() for i in range(13)]
+ self.a_month = a_month
+ self.f_month = f_month
def __calc_am_pm(self):
- # Set self.__am_pm by using time.strftime().
+ # Set self.am_pm by using time.strftime().
# The magic date (1999,3,17,hour,44,55,2,76,0) is not really that
# magical; just happened to have used it everywhere else where a
@@ -226,11 +108,11 @@ class LocaleTime(object):
am_pm = []
for hour in (01,22):
time_tuple = time.struct_time((1999,3,17,hour,44,55,2,76,0))
- am_pm.append(time.strftime("%p", time_tuple))
- self.__am_pm = am_pm
+ am_pm.append(time.strftime("%p", time_tuple).lower())
+ self.am_pm = am_pm
def __calc_date_time(self):
- # Set self.__date_time, self.__date, & self.__time by using
+ # Set self.date_time, self.date, & self.time by using
# time.strftime().
# Use (1999,3,17,22,44,55,2,76,0) for magic date because the amount of
@@ -239,21 +121,22 @@ class LocaleTime(object):
# possible ambiguity for what something represents.
time_tuple = time.struct_time((1999,3,17,22,44,55,2,76,0))
date_time = [None, None, None]
- date_time[0] = time.strftime("%c", time_tuple)
- date_time[1] = time.strftime("%x", time_tuple)
- date_time[2] = time.strftime("%X", time_tuple)
- for offset,directive in ((0,'%c'), (1,'%x'), (2,'%X')):
- current_format = date_time[offset]
- for old, new in (
- ('%', '%%'), (self.f_weekday[2], '%A'),
+ date_time[0] = time.strftime("%c", time_tuple).lower()
+ date_time[1] = time.strftime("%x", time_tuple).lower()
+ date_time[2] = time.strftime("%X", time_tuple).lower()
+ replacement_pairs = [('%', '%%'), (self.f_weekday[2], '%A'),
(self.f_month[3], '%B'), (self.a_weekday[2], '%a'),
(self.a_month[3], '%b'), (self.am_pm[1], '%p'),
- (self.timezone[0], '%Z'), (self.timezone[1], '%Z'),
('1999', '%Y'), ('99', '%y'), ('22', '%H'),
('44', '%M'), ('55', '%S'), ('76', '%j'),
('17', '%d'), ('03', '%m'), ('3', '%m'),
# '3' needed for when no leading zero.
- ('2', '%w'), ('10', '%I')):
+ ('2', '%w'), ('10', '%I')]
+ replacement_pairs.extend([(tz, "%Z") for tz_values in self.timezone
+ for tz in tz_values])
+ for offset,directive in ((0,'%c'), (1,'%x'), (2,'%X')):
+ current_format = date_time[offset]
+ for old, new in replacement_pairs:
# Must deal with possible lack of locale info
# manifesting itself as the empty string (e.g., Swedish's
# lack of AM/PM info) or a platform returning a tuple of empty
@@ -266,38 +149,40 @@ class LocaleTime(object):
else:
U_W = '%W'
date_time[offset] = current_format.replace('11', U_W)
- if not self.__LC_date_time:
- self.__LC_date_time = date_time[0]
- if not self.__LC_date:
- self.__LC_date = date_time[1]
- if not self.__LC_time:
- self.__LC_time = date_time[2]
+ self.LC_date_time = date_time[0]
+ self.LC_date = date_time[1]
+ self.LC_time = date_time[2]
def __calc_timezone(self):
- # Set self.__timezone by using time.tzname.
- #
- # Empty string used for matching when timezone is not used/needed.
+ # Set self.timezone by using time.tzname.
try:
time.tzset()
except AttributeError:
pass
- time_zones = ["UTC", "GMT"]
+ no_saving = sets_ImmutableSet(["utc", "gmt", time.tzname[0].lower()])
if time.daylight:
- time_zones.extend(time.tzname)
+ has_saving = sets_ImmutableSet([time.tzname[1].lower()])
else:
- time_zones.append(time.tzname[0])
- self.__timezone = self.__pad(time_zones, 0)
+ has_saving = sets_ImmutableSet()
+ self.timezone = (no_saving, has_saving)
class TimeRE(dict):
"""Handle conversion from format directives to regexes."""
def __init__(self, locale_time=None):
- """Init inst with non-locale regexes and store LocaleTime object."""
- #XXX: Does 'Y' need to worry about having less or more than 4 digits?
+ """Create keys/values.
+
+ Order of execution is important for dependency reasons.
+
+ """
+ if locale_time:
+ self.locale_time = locale_time
+ else:
+ self.locale_time = LocaleTime()
base = super(TimeRE, self)
base.__init__({
- # The " \d" option is to make %c from ANSI C work
+ # The " \d" part of the regex is to make %c from ANSI C work
'd': r"(?P<d>3[0-1]|[1-2]\d|0[1-9]|[1-9]| [1-9])",
'H': r"(?P<H>2[0-3]|[0-1]\d|\d)",
'I': r"(?P<I>1[0-2]|0[1-9]|[1-9])",
@@ -309,70 +194,47 @@ class TimeRE(dict):
'w': r"(?P<w>[0-6])",
# W is set below by using 'U'
'y': r"(?P<y>\d\d)",
- 'Y': r"(?P<Y>\d\d\d\d)"})
+ #XXX: Does 'Y' need to worry about having less or more than
+ # 4 digits?
+ 'Y': r"(?P<Y>\d\d\d\d)",
+ 'A': self.__seqToRE(self.locale_time.f_weekday, 'A'),
+ 'a': self.__seqToRE(self.locale_time.a_weekday, 'a'),
+ 'B': self.__seqToRE(self.locale_time.f_month[1:], 'B'),
+ 'b': self.__seqToRE(self.locale_time.a_month[1:], 'b'),
+ 'p': self.__seqToRE(self.locale_time.am_pm, 'p'),
+ 'Z': self.__seqToRE([tz for tz_names in self.locale_time.timezone
+ for tz in tz_names],
+ 'Z'),
+ '%': '%'})
base.__setitem__('W', base.__getitem__('U'))
- if locale_time:
- self.locale_time = locale_time
- else:
- self.locale_time = LocaleTime()
-
- def __getitem__(self, fetch):
- """Try to fetch regex; if it does not exist, construct it."""
- try:
- return super(TimeRE, self).__getitem__(fetch)
- except KeyError:
- constructors = {
- 'A': lambda: self.__seqToRE(self.locale_time.f_weekday, fetch),
- 'a': lambda: self.__seqToRE(self.locale_time.a_weekday, fetch),
- 'B': lambda: self.__seqToRE(self.locale_time.f_month[1:],
- fetch),
- 'b': lambda: self.__seqToRE(self.locale_time.a_month[1:],
- fetch),
- 'c': lambda: self.pattern(self.locale_time.LC_date_time),
- 'p': lambda: self.__seqToRE(self.locale_time.am_pm, fetch),
- 'x': lambda: self.pattern(self.locale_time.LC_date),
- 'X': lambda: self.pattern(self.locale_time.LC_time),
- 'Z': lambda: self.__seqToRE(self.locale_time.timezone, fetch),
- '%': lambda: '%',
- }
- if fetch in constructors:
- self[fetch] = constructors[fetch]()
- return self[fetch]
- else:
- raise
+ base.__setitem__('c', self.pattern(self.locale_time.LC_date_time))
+ base.__setitem__('x', self.pattern(self.locale_time.LC_date))
+ base.__setitem__('X', self.pattern(self.locale_time.LC_time))
def __seqToRE(self, to_convert, directive):
- """Convert a list to a regex string for matching a directive."""
- def sorter(a, b):
- """Sort based on length.
-
- Done in case for some strange reason that names in the locale only
- differ by a suffix and thus want the name with the suffix to match
- first.
- """
- try:
- a_length = len(a)
- except TypeError:
- a_length = 0
- try:
- b_length = len(b)
- except TypeError:
- b_length = 0
- return cmp(b_length, a_length)
-
- to_convert = to_convert[:] # Don't want to change value in-place.
+ """Convert a list to a regex string for matching a directive.
+
+ Want possible matching values to be from longest to shortest. This
+ prevents the possibility of a match occuring for a value that also
+ a substring of a larger value that should have matched (e.g., 'abc'
+ matching when 'abcdef' should have been the match).
+
+ """
for value in to_convert:
if value != '':
break
else:
return ''
- to_convert.sort(sorter)
+ to_sort = [(len(item), item) for item in to_convert]
+ to_sort.sort()
+ to_sort.reverse()
+ to_convert = [item for length, item in to_sort]
regex = '|'.join(to_convert)
regex = '(?P<%s>%s' % (directive, regex)
return '%s)' % regex
def pattern(self, format):
- """Return re pattern for the format string.
+ """Return regex pattern for the format string.
Need to make sure that any characters that might be interpreted as
regex syntax is escaped.
@@ -397,12 +259,30 @@ class TimeRE(dict):
"""Return a compiled re object for the format string."""
return re_compile(self.pattern(format), IGNORECASE)
+_cache_lock = _thread_allocate_lock()
+# DO NOT modify _TimeRE_cache or _regex_cache without acquiring the cache lock
+# first!
+_TimeRE_cache = TimeRE()
+_CACHE_MAX_SIZE = 5
+_regex_cache = {}
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."""
- time_re = TimeRE()
- locale_time = time_re.locale_time
- format_regex = time_re.compile(format)
+ global _TimeRE_cache
+ _cache_lock.acquire()
+ try:
+ time_re = _TimeRE_cache
+ locale_time = time_re.locale_time
+ if _getlang() != locale_time.lang:
+ _TimeRE_cache = TimeRE()
+ if len(_regex_cache) > _CACHE_MAX_SIZE:
+ _regex_cache.clear()
+ format_regex = _regex_cache.get(format)
+ if not format_regex:
+ format_regex = time_re.compile(format)
+ _regex_cache[format] = format_regex
+ finally:
+ _cache_lock.release()
found = format_regex.match(data_string)
if not found:
raise ValueError("time data did not match format: data=%s fmt=%s" %
@@ -432,9 +312,9 @@ def strptime(data_string, format="%a %b %d %H:%M:%S %Y"):
elif group_key == 'm':
month = int(found_dict['m'])
elif group_key == 'B':
- month = _insensitiveindex(locale_time.f_month, found_dict['B'])
+ month = locale_time.f_month.index(found_dict['B'].lower())
elif group_key == 'b':
- month = _insensitiveindex(locale_time.a_month, found_dict['b'])
+ month = locale_time.a_month.index(found_dict['b'].lower())
elif group_key == 'd':
day = int(found_dict['d'])
elif group_key == 'H':
@@ -443,13 +323,13 @@ def strptime(data_string, format="%a %b %d %H:%M:%S %Y"):
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()):
+ if ampm in ('', locale_time.am_pm[0]):
# 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():
+ elif ampm == locale_time.am_pm[1]:
# 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
@@ -460,11 +340,9 @@ def strptime(data_string, format="%a %b %d %H:%M:%S %Y"):
elif group_key == 'S':
second = int(found_dict['S'])
elif group_key == 'A':
- weekday = _insensitiveindex(locale_time.f_weekday,
- found_dict['A'])
+ weekday = locale_time.f_weekday.index(found_dict['A'].lower())
elif group_key == 'a':
- weekday = _insensitiveindex(locale_time.a_weekday,
- found_dict['a'])
+ weekday = locale_time.a_weekday.index(found_dict['a'].lower())
elif group_key == 'w':
weekday = int(found_dict['w'])
if weekday == 0:
@@ -481,14 +359,10 @@ def strptime(data_string, format="%a %b %d %H:%M:%S %Y"):
time.daylight:
pass #Deals with bad locale setup where timezone info is
# the same; first found on FreeBSD 4.4.
- elif found_zone in ("utc", "gmt"):
- tz = 0
- elif locale_time.timezone[2].lower() == found_zone:
- tz = 0
- elif time.daylight and \
- locale_time.timezone[3].lower() == found_zone:
- tz = 1
-
+ else:
+ for value, tz_values in enumerate(locale_time.timezone):
+ if found_zone in tz_values:
+ tz = value
# Cannot pre-calculate datetime_date() since can change in Julian
#calculation and thus could have different value for the day of the week
#calculation
@@ -507,16 +381,3 @@ def strptime(data_string, format="%a %b %d %H:%M:%S %Y"):
return time.struct_time((year, month, day,
hour, minute, second,
weekday, julian, tz))
-
-def _insensitiveindex(lst, findme):
- # Perform a case-insensitive index search.
-
- #XXX <bc>: If LocaleTime is not exposed, then consider removing this and
- # just lowercase when LocaleTime sets its vars and lowercasing
- # search values.
- findme = findme.lower()
- for key,item in enumerate(lst):
- if item.lower() == findme:
- return key
- else:
- raise ValueError("value not in list")
diff --git a/Lib/test/test_strptime.py b/Lib/test/test_strptime.py
index 9ac75e6..fef5d90 100644
--- a/Lib/test/test_strptime.py
+++ b/Lib/test/test_strptime.py
@@ -4,6 +4,7 @@ import unittest
import time
import locale
import re
+import sets
from test import test_support
import _strptime
@@ -14,7 +15,12 @@ class getlang_Tests(unittest.TestCase):
self.failUnlessEqual(_strptime._getlang(), locale.getlocale(locale.LC_TIME))
class LocaleTime_Tests(unittest.TestCase):
- """Tests for _strptime.LocaleTime."""
+ """Tests for _strptime.LocaleTime.
+
+ All values are lower-cased when stored in LocaleTime, so make sure to
+ compare values after running ``lower`` on them.
+
+ """
def setUp(self):
"""Create time tuple based on current time."""
@@ -27,7 +33,7 @@ class LocaleTime_Tests(unittest.TestCase):
tuple_position of time_tuple. Uses error_msg as error message.
"""
- strftime_output = time.strftime(directive, self.time_tuple)
+ strftime_output = time.strftime(directive, self.time_tuple).lower()
comparison = testing[self.time_tuple[tuple_position]]
self.failUnless(strftime_output in testing, "%s: not found in tuple" %
error_msg)
@@ -53,7 +59,7 @@ class LocaleTime_Tests(unittest.TestCase):
def test_am_pm(self):
# Make sure AM/PM representation done properly
- strftime_output = time.strftime("%p", self.time_tuple)
+ strftime_output = time.strftime("%p", self.time_tuple).lower()
self.failUnless(strftime_output in self.LT_ins.am_pm,
"AM/PM representation not in tuple")
if self.time_tuple[3] < 12: position = 0
@@ -63,9 +69,10 @@ class LocaleTime_Tests(unittest.TestCase):
def test_timezone(self):
# Make sure timezone is correct
- timezone = time.strftime("%Z", self.time_tuple)
+ timezone = time.strftime("%Z", self.time_tuple).lower()
if timezone:
- self.failUnless(timezone in self.LT_ins.timezone,
+ self.failUnless(timezone in self.LT_ins.timezone[0] or \
+ timezone in self.LT_ins.timezone[1],
"timezone %s not found in %s" %
(timezone, self.LT_ins.timezone))
@@ -89,7 +96,8 @@ class LocaleTime_Tests(unittest.TestCase):
self.failUnless(strftime_output == time.strftime(self.LT_ins.LC_time,
magic_date),
"LC_time incorrect")
- LT = _strptime.LocaleTime(am_pm=('',''))
+ LT = _strptime.LocaleTime()
+ LT.am_pm = ('', '')
self.failUnless(LT.LC_time, "LocaleTime's LC directives cannot handle "
"empty strings")
@@ -98,44 +106,6 @@ class LocaleTime_Tests(unittest.TestCase):
# Assuming locale has not changed between now and when self.LT_ins was created
self.failUnlessEqual(self.LT_ins.lang, _strptime._getlang())
- def test_by_hand_input(self):
- # Test passed-in initialization value checks
- self.failUnless(_strptime.LocaleTime(f_weekday=range(7)),
- "Argument size check for f_weekday failed")
- self.assertRaises(TypeError, _strptime.LocaleTime, f_weekday=range(8))
- self.assertRaises(TypeError, _strptime.LocaleTime, f_weekday=range(6))
- self.failUnless(_strptime.LocaleTime(a_weekday=range(7)),
- "Argument size check for a_weekday failed")
- self.assertRaises(TypeError, _strptime.LocaleTime, a_weekday=range(8))
- self.assertRaises(TypeError, _strptime.LocaleTime, a_weekday=range(6))
- self.failUnless(_strptime.LocaleTime(f_month=range(12)),
- "Argument size check for f_month failed")
- self.assertRaises(TypeError, _strptime.LocaleTime, f_month=range(11))
- self.assertRaises(TypeError, _strptime.LocaleTime, f_month=range(13))
- self.failUnless(len(_strptime.LocaleTime(f_month=range(12)).f_month) == 13,
- "dummy value for f_month not added")
- self.failUnless(_strptime.LocaleTime(a_month=range(12)),
- "Argument size check for a_month failed")
- self.assertRaises(TypeError, _strptime.LocaleTime, a_month=range(11))
- self.assertRaises(TypeError, _strptime.LocaleTime, a_month=range(13))
- self.failUnless(len(_strptime.LocaleTime(a_month=range(12)).a_month) == 13,
- "dummy value for a_month not added")
- self.failUnless(_strptime.LocaleTime(am_pm=range(2)),
- "Argument size check for am_pm failed")
- self.assertRaises(TypeError, _strptime.LocaleTime, am_pm=range(1))
- self.assertRaises(TypeError, _strptime.LocaleTime, am_pm=range(3))
- self.failUnless(_strptime.LocaleTime(timezone=range(2)),
- "Argument size check for timezone failed")
- 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."""
@@ -145,21 +115,6 @@ class TimeRETests(unittest.TestCase):
self.time_re = _strptime.TimeRE()
self.locale_time = _strptime.LocaleTime()
- def test_getitem(self):
- # Make sure that __getitem__ works properly
- self.failUnless(self.time_re['m'],
- "Fetching 'm' directive (built-in) failed")
- self.failUnless(self.time_re['b'],
- "Fetching 'b' directive (built w/ __tupleToRE) failed")
- for name in self.locale_time.a_month:
- self.failUnless(self.time_re['b'].find(name) != -1,
- "Not all abbreviated month names in regex")
- self.failUnless(self.time_re['c'],
- "Fetching 'c' directive (built w/ format) failed")
- self.failUnless(self.time_re['c'].find('%') == -1,
- "Conversion of 'c' directive failed; '%' found")
- self.assertRaises(KeyError, self.time_re.__getitem__, '1')
-
def test_pattern(self):
# Test TimeRE.pattern
pattern_string = self.time_re.pattern(r"%a %A %d")
@@ -210,7 +165,8 @@ class TimeRETests(unittest.TestCase):
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=('',''))
+ test_locale = _strptime.LocaleTime()
+ test_locale.timezone = (sets.ImmutableSet(), sets.ImmutableSet())
self.failUnless(_strptime.TimeRE(test_locale).pattern("%Z") == '',
"with timezone == ('',''), TimeRE().pattern('%Z') != ''")
@@ -413,6 +369,43 @@ class CalculationTests(unittest.TestCase):
self.failUnless(result.tm_wday == self.time_tuple.tm_wday,
"Calculation of day of the week failed;"
"%s != %s" % (result.tm_wday, self.time_tuple.tm_wday))
+
+
+class CacheTests(unittest.TestCase):
+ """Test that caching works properly."""
+
+ def test_time_re_recreation(self):
+ # Make sure cache is recreated when current locale does not match what
+ # cached object was created with.
+ _strptime.strptime("10", "%d")
+ _strptime._TimeRE_cache.locale_time.lang = "Ni"
+ original_time_re = id(_strptime._TimeRE_cache)
+ _strptime.strptime("10", "%d")
+ self.failIfEqual(original_time_re, id(_strptime._TimeRE_cache))
+
+ def test_regex_cleanup(self):
+ # Make sure cached regexes are discarded when cache becomes "full".
+ try:
+ del _strptime._regex_cache['%d']
+ except KeyError:
+ pass
+ bogus_key = 0
+ while len(_strptime._regex_cache) <= _strptime._CACHE_MAX_SIZE:
+ _strptime._regex_cache[bogus_key] = None
+ bogus_key += 1
+ _strptime.strptime("10", "%d")
+ self.failUnlessEqual(len(_strptime._regex_cache), 1)
+
+ def test_new_localetime(self):
+ # A new LocaleTime instance should be created when a new TimeRE object
+ # is created.
+ locale_time_id = id(_strptime._TimeRE_cache.locale_time)
+ _strptime._TimeRE_cache.locale_time.lang = "Ni"
+ _strptime.strptime("10", "%d")
+ self.failIfEqual(locale_time_id,
+ id(_strptime._TimeRE_cache.locale_time))
+
+
def test_main():
test_support.run_unittest(
getlang_Tests,
@@ -422,6 +415,7 @@ def test_main():
Strptime12AMPMTests,
JulianTests,
CalculationTests,
+ CacheTests
)