diff options
-rw-r--r-- | Lib/_strptime.py | 411 | ||||
-rw-r--r-- | Lib/test/test_strptime.py | 114 |
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 ) |