diff --git a/Lib/_strptime.py b/Lib/_strptime.py --- a/Lib/_strptime.py +++ b/Lib/_strptime.py @@ -155,29 +155,33 @@ class LocaleTime(object): else: U_W = '%U' date_time[offset] = current_format.replace('11', U_W) self.LC_date_time = date_time[0] self.LC_date = date_time[1] self.LC_time = date_time[2] def __calc_timezone(self): + self.timezone = self._get_timezone() + + @staticmethod + def _get_timezone(): # Set self.timezone by using time.tzname. # Do not worry about possibility of time.tzname[0] == timetzname[1] - # and time.daylight; handle that in strptime . + # and time.daylight; handle that in strptime. try: time.tzset() except AttributeError: pass no_saving = frozenset(["utc", "gmt", time.tzname[0].lower()]) if time.daylight: has_saving = frozenset([time.tzname[1].lower()]) else: has_saving = frozenset() - self.timezone = (no_saving, has_saving) + return (no_saving, has_saving) class TimeRE(dict): """Handle conversion from format directives to regexes.""" def __init__(self, locale_time=None): """Create keys/values. @@ -207,25 +211,29 @@ class TimeRE(dict): # 4 digits? 'Y': r"(?P\d\d\d\d)", 'z': r"(?P[+-]\d\d[0-5]\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'), + 'Z': self._calculate_timezone(), '%': '%'}) base.__setitem__('W', base.__getitem__('U').replace('U', 'W')) 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 _calculate_timezone(self, locale_time=None): + if locale_time is None: + locale_time = self.locale_time.timezone + return self.__seqToRE((tz for tz_names in locale_time + for tz in tz_names), 'Z') + def __seqToRE(self, to_convert, directive): """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). @@ -303,22 +311,25 @@ def _strptime(data_string, format="%a %b for index, arg in enumerate([data_string, format]): if not isinstance(arg, str): msg = "strptime() argument {} must be str, not {}" raise TypeError(msg.format(index, type(arg))) global _TimeRE_cache, _regex_cache with _cache_lock: - if _getlang() != _TimeRE_cache.locale_time.lang: + locale_time = _TimeRE_cache.locale_time + locale_tz = locale_time._get_timezone() + if locale_time.timezone != locale_tz: + _TimeRE_cache['Z'] = _TimeRE_cache._calculate_timezone(locale_tz) + if _getlang() != locale_time.lang: _TimeRE_cache = TimeRE() _regex_cache.clear() if len(_regex_cache) > _CACHE_MAX_SIZE: _regex_cache.clear() - locale_time = _TimeRE_cache.locale_time format_regex = _regex_cache.get(format) if not format_regex: try: format_regex = _TimeRE_cache.compile(format) # KeyError raised when a bad format is found; can be specified as # \\, in which case it was a stray % but with a space after it except KeyError as err: bad_directive = err.args[0] diff --git a/Lib/test/test_strptime.py b/Lib/test/test_strptime.py --- a/Lib/test/test_strptime.py +++ b/Lib/test/test_strptime.py @@ -2,16 +2,17 @@ import unittest import time import locale import re import sys from test import support from datetime import date as datetime_date +from unittest import mock import _strptime class getlang_Tests(unittest.TestCase): """Test _getlang""" def test_basic(self): self.assertEqual(_strptime._getlang(), locale.getlocale(locale.LC_TIME)) @@ -303,35 +304,29 @@ class StrptimeTests(unittest.TestCase): self.assertTrue(strp_output[8] == time_tuple[8], "timezone check failed; '%s' -> %s != %s" % (strf_output, strp_output[8], time_tuple[8])) else: self.assertTrue(strp_output[8] == -1, "LocaleTime().timezone has duplicate values and " "time.daylight but timezone value not set to -1") + @mock.patch('_strptime._TimeRE_cache.locale_time._get_timezone', + lambda: (frozenset(["utc", "gmt", time.tzname[0].lower()]), + frozenset([time.tzname[0].lower()]))) + @mock.patch('time.tzname', (time.tzname[0], time.tzname[0])) def test_bad_timezone(self): # Explicitly test possibility of bad timezone; # when time.tzname[0] == time.tzname[1] and time.daylight tz_name = time.tzname[0] - if tz_name.upper() in ("UTC", "GMT"): - return - try: - original_tzname = time.tzname - original_daylight = time.daylight - time.tzname = (tz_name, tz_name) - time.daylight = 1 - tz_value = _strptime._strptime_time(tz_name, "%Z")[8] - self.assertEqual(tz_value, -1, - "%s lead to a timezone value of %s instead of -1 when " - "time.daylight set to %s and passing in %s" % - (time.tzname, tz_value, time.daylight, tz_name)) - finally: - time.tzname = original_tzname - time.daylight = original_daylight + tz_value = _strptime._strptime_time(tz_name, "%Z") + self.assertEqual(tz_value[8], -1, + "%s lead to a timezone value of %s instead of -1 when " + "time.daylight set to %s and passing in %s" % + (time.tzname, tz_value, time.daylight, tz_name)) def test_date_time(self): # Test %c directive for position in range(6): self.helper('c', position) def test_date(self): # Test %x directive @@ -544,16 +539,45 @@ class CacheTests(unittest.TestCase): # to the resetting to the original locale. except locale.Error: pass # Make sure we don't trample on the locale setting once we leave the # test. finally: locale.setlocale(locale.LC_TIME, locale_info) + @unittest.skipUnless(hasattr(time, "tzset"), + "time module has no attribute tzset") + def test_TimeRE_recreation_timezone(self): + # See issue 6478 for more information + from os import environ + + org_TZ = environ.get('TZ') + try: + environ['TZ'] = 'Pacific/Fiji' + time.tzset() + time.strptime('24-02-1988', '%d-%m-%Y') + first_time_re = _strptime._TimeRE_cache['Z'] + + environ['TZ'] = 'America/New_York' + time.tzset() + time.strptime('07-02-2013', '%d-%m-%Y') + + second_time_re = _strptime._TimeRE_cache['Z'] + + self.assertNotEqual(first_time_re, second_time_re) + finally: + # Repair TZ environment variable in case any other tests + # rely on it. + if org_TZ is not None: + environ['TZ'] = org_TZ + elif 'TZ' in environ: + del environ['TZ'] + time.tzset() + def test_main(): support.run_unittest( getlang_Tests, LocaleTime_Tests, TimeRETests, StrptimeTests, Strptime12AMPMTests,