Commit c7217d7c authored by Serhiy Storchaka's avatar Serhiy Storchaka

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

with time.tzset().
parent 7aa69086
...@@ -77,6 +77,8 @@ class LocaleTime(object): ...@@ -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): ...@@ -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)
...@@ -308,12 +312,15 @@ def _strptime(data_string, format="%a %b %d %H:%M:%S %Y"): ...@@ -308,12 +312,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:
if _getlang() != _TimeRE_cache.locale_time.lang: locale_time = _TimeRE_cache.locale_time
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:
......
...@@ -4,6 +4,7 @@ import unittest ...@@ -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
...@@ -324,9 +325,10 @@ class StrptimeTests(unittest.TestCase): ...@@ -324,9 +325,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]
...@@ -334,9 +336,6 @@ class StrptimeTests(unittest.TestCase): ...@@ -334,9 +336,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
...@@ -548,7 +547,7 @@ class CacheTests(unittest.TestCase): ...@@ -548,7 +547,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:
...@@ -577,6 +576,33 @@ class CacheTests(unittest.TestCase): ...@@ -577,6 +576,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')
def test_main(): def test_main():
support.run_unittest( support.run_unittest(
......
...@@ -115,6 +115,9 @@ Core and Builtins ...@@ -115,6 +115,9 @@ Core and Builtins
Library Library
------- -------
- Issue #6478: _strptime's regexp cache now is reset after changing timezone
with time.tzset().
- Issue #25177: Fixed problem with the mean of very small and very large - Issue #25177: Fixed problem with the mean of very small and very large
numbers. As a side effect, statistics.mean and statistics.variance should numbers. As a side effect, statistics.mean and statistics.variance should
be significantly faster. be significantly faster.
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment