Issue #6478: _strptime's regexp cache now is reset after changing timezone

with time.tzset().
This commit is contained in:
Serhiy Storchaka 2015-12-03 22:27:31 +02:00
commit 3ab6c981e7
3 changed files with 50 additions and 15 deletions

View File

@ -77,6 +77,8 @@ class LocaleTime(object):
self.__calc_date_time() self.__calc_date_time()
if _getlang() != self.lang: if _getlang() != self.lang:
raise ValueError("locale changed during initialization") raise ValueError("locale changed during initialization")
if time.tzname != self.tzname or time.daylight != self.daylight:
raise ValueError("timezone changed during initialization")
def __pad(self, seq, front): def __pad(self, seq, front):
# Add '' to seq to either the front (is True), else the back. # Add '' to seq to either the front (is True), else the back.
@ -161,15 +163,17 @@ class LocaleTime(object):
def __calc_timezone(self): def __calc_timezone(self):
# Set self.timezone by using time.tzname. # Set self.timezone by using time.tzname.
# Do not worry about possibility of time.tzname[0] == timetzname[1] # Do not worry about possibility of time.tzname[0] == time.tzname[1]
# and time.daylight; handle that in strptime . # and time.daylight; handle that in strptime.
try: try:
time.tzset() time.tzset()
except AttributeError: except AttributeError:
pass pass
no_saving = frozenset({"utc", "gmt", time.tzname[0].lower()}) self.tzname = time.tzname
if time.daylight: self.daylight = time.daylight
has_saving = frozenset({time.tzname[1].lower()}) no_saving = frozenset({"utc", "gmt", self.tzname[0].lower()})
if self.daylight:
has_saving = frozenset({self.tzname[1].lower()})
else: else:
has_saving = frozenset() has_saving = frozenset()
self.timezone = (no_saving, has_saving) self.timezone = (no_saving, has_saving)
@ -326,13 +330,15 @@ def _strptime(data_string, format="%a %b %d %H:%M:%S %Y"):
global _TimeRE_cache, _regex_cache global _TimeRE_cache, _regex_cache
with _cache_lock: with _cache_lock:
locale_time = _TimeRE_cache.locale_time
if _getlang() != _TimeRE_cache.locale_time.lang: if (_getlang() != locale_time.lang or
time.tzname != locale_time.tzname or
time.daylight != locale_time.daylight):
_TimeRE_cache = TimeRE() _TimeRE_cache = TimeRE()
_regex_cache.clear() _regex_cache.clear()
locale_time = _TimeRE_cache.locale_time
if len(_regex_cache) > _CACHE_MAX_SIZE: if len(_regex_cache) > _CACHE_MAX_SIZE:
_regex_cache.clear() _regex_cache.clear()
locale_time = _TimeRE_cache.locale_time
format_regex = _regex_cache.get(format) format_regex = _regex_cache.get(format)
if not format_regex: if not format_regex:
try: try:

View File

@ -4,6 +4,7 @@ import unittest
import time import time
import locale import locale
import re import re
import os
import sys import sys
from test import support from test import support
from datetime import date as datetime_date from datetime import date as datetime_date
@ -344,9 +345,10 @@ class StrptimeTests(unittest.TestCase):
tz_name = time.tzname[0] tz_name = time.tzname[0]
if tz_name.upper() in ("UTC", "GMT"): if tz_name.upper() in ("UTC", "GMT"):
self.skipTest('need non-UTC/GMT timezone') self.skipTest('need non-UTC/GMT timezone')
try:
original_tzname = time.tzname with support.swap_attr(time, 'tzname', (tz_name, tz_name)), \
original_daylight = time.daylight support.swap_attr(time, 'daylight', 1), \
support.swap_attr(time, 'tzset', lambda: None):
time.tzname = (tz_name, tz_name) time.tzname = (tz_name, tz_name)
time.daylight = 1 time.daylight = 1
tz_value = _strptime._strptime_time(tz_name, "%Z")[8] tz_value = _strptime._strptime_time(tz_name, "%Z")[8]
@ -354,9 +356,6 @@ class StrptimeTests(unittest.TestCase):
"%s lead to a timezone value of %s instead of -1 when " "%s lead to a timezone value of %s instead of -1 when "
"time.daylight set to %s and passing in %s" % "time.daylight set to %s and passing in %s" %
(time.tzname, tz_value, time.daylight, tz_name)) (time.tzname, tz_value, time.daylight, tz_name))
finally:
time.tzname = original_tzname
time.daylight = original_daylight
def test_date_time(self): def test_date_time(self):
# Test %c directive # Test %c directive
@ -579,7 +578,7 @@ class CacheTests(unittest.TestCase):
_strptime._strptime_time("10", "%d") _strptime._strptime_time("10", "%d")
self.assertIsNot(locale_time_id, _strptime._TimeRE_cache.locale_time) self.assertIsNot(locale_time_id, _strptime._TimeRE_cache.locale_time)
def test_TimeRE_recreation(self): def test_TimeRE_recreation_locale(self):
# The TimeRE instance should be recreated upon changing the locale. # The TimeRE instance should be recreated upon changing the locale.
locale_info = locale.getlocale(locale.LC_TIME) locale_info = locale.getlocale(locale.LC_TIME)
try: try:
@ -608,6 +607,33 @@ class CacheTests(unittest.TestCase):
finally: finally:
locale.setlocale(locale.LC_TIME, locale_info) locale.setlocale(locale.LC_TIME, locale_info)
@support.run_with_tz('STD-1DST')
def test_TimeRE_recreation_timezone(self):
# The TimeRE instance should be recreated upon changing the timezone.
oldtzname = time.tzname
tm = _strptime._strptime_time(time.tzname[0], '%Z')
self.assertEqual(tm.tm_isdst, 0)
tm = _strptime._strptime_time(time.tzname[1], '%Z')
self.assertEqual(tm.tm_isdst, 1)
# Get id of current cache object.
first_time_re = _strptime._TimeRE_cache
# Change the timezone and force a recreation of the cache.
os.environ['TZ'] = 'EST+05EDT,M3.2.0,M11.1.0'
time.tzset()
tm = _strptime._strptime_time(time.tzname[0], '%Z')
self.assertEqual(tm.tm_isdst, 0)
tm = _strptime._strptime_time(time.tzname[1], '%Z')
self.assertEqual(tm.tm_isdst, 1)
# Get the new cache object's id.
second_time_re = _strptime._TimeRE_cache
# They should not be equal.
self.assertIsNot(first_time_re, second_time_re)
# Make sure old names no longer accepted.
with self.assertRaises(ValueError):
_strptime._strptime_time(oldtzname[0], '%Z')
with self.assertRaises(ValueError):
_strptime._strptime_time(oldtzname[1], '%Z')
if __name__ == '__main__': if __name__ == '__main__':
unittest.main() unittest.main()

View File

@ -109,6 +109,9 @@ Core and Builtins
Library Library
------- -------
- Issue #6478: _strptime's regexp cache now is reset after changing timezone
with time.tzset().
- Issue #14285: When executing a package with the "python -m package" option, - Issue #14285: When executing a package with the "python -m package" option,
and package initialization fails, a proper traceback is now reported. The and package initialization fails, a proper traceback is now reported. The
"runpy" module now lets exceptions from package initialization pass back to "runpy" module now lets exceptions from package initialization pass back to