Fix last_modified_date_to_timestamp on non-UTC systems
Before, we were calling datetime.datetime.strftime('%s.%f') to convert a datetime to epoch seconds + microseconds. However, the '%s' format isn't actually part of Python's library. Rather, Python passes that on to the system C library, which is typically glibc. Now, glibc takes the '%s' format and helpfully* applies the current timezone as an offset. This gives bogus results on machines where UTC is not the system timezone. (Yes, some people really do that.) For example: >>> import os >>> from swift.common import utils >>> os.environ['TZ'] = 'PST8PDT,M3.2.0,M11.1.0' >>> float(utils.last_modified_date_to_timestamp('1970-01-01T00:00:00.000000')) 28800.0 >>> That timestamp should obviously be 0. This patch replaces the strftime() call with datetime arithmetic, which is entirely in Python so the system timezone doesn't mess it up. * unhelpfully Change-Id: I56855acd79a5d8f2c98a771fa9fd2729e4f490b1
This commit is contained in:
parent
8d02147d04
commit
33980c792d
@ -696,16 +696,25 @@ def normalize_timestamp(timestamp):
|
|||||||
return Timestamp(timestamp).normal
|
return Timestamp(timestamp).normal
|
||||||
|
|
||||||
|
|
||||||
|
EPOCH = datetime.datetime(1970, 1, 1)
|
||||||
|
|
||||||
|
|
||||||
def last_modified_date_to_timestamp(last_modified_date_str):
|
def last_modified_date_to_timestamp(last_modified_date_str):
|
||||||
"""
|
"""
|
||||||
Convert a last modified date (like you'd get from a container listing,
|
Convert a last modified date (like you'd get from a container listing,
|
||||||
e.g. 2014-02-28T23:22:36.698390) to a float.
|
e.g. 2014-02-28T23:22:36.698390) to a float.
|
||||||
"""
|
"""
|
||||||
return Timestamp(
|
start = datetime.datetime.strptime(last_modified_date_str,
|
||||||
datetime.datetime.strptime(
|
'%Y-%m-%dT%H:%M:%S.%f')
|
||||||
last_modified_date_str, '%Y-%m-%dT%H:%M:%S.%f'
|
delta = start - EPOCH
|
||||||
).strftime('%s.%f')
|
# TODO(sam): after we no longer support py2.6, this expression can
|
||||||
)
|
# simplify to Timestamp(delta.total_seconds()).
|
||||||
|
#
|
||||||
|
# This calculation is based on Python 2.7's Modules/datetimemodule.c,
|
||||||
|
# function delta_to_microseconds(), but written in Python.
|
||||||
|
return Timestamp(delta.days * 86400 +
|
||||||
|
delta.seconds +
|
||||||
|
delta.microseconds / 1000000.0)
|
||||||
|
|
||||||
|
|
||||||
def normalize_delete_at_timestamp(timestamp):
|
def normalize_delete_at_timestamp(timestamp):
|
||||||
|
@ -859,6 +859,24 @@ class TestUtils(unittest.TestCase):
|
|||||||
real = utils.last_modified_date_to_timestamp(last_modified)
|
real = utils.last_modified_date_to_timestamp(last_modified)
|
||||||
self.assertEqual(real, ts, "failed for %s" % last_modified)
|
self.assertEqual(real, ts, "failed for %s" % last_modified)
|
||||||
|
|
||||||
|
def test_last_modified_date_to_timestamp_when_system_not_UTC(self):
|
||||||
|
try:
|
||||||
|
old_tz = os.environ.get('TZ')
|
||||||
|
# Western Argentina Summer Time. Found in glibc manual; this
|
||||||
|
# timezone always has a non-zero offset from UTC, so this test is
|
||||||
|
# always meaningful.
|
||||||
|
os.environ['TZ'] = 'WART4WARST,J1/0,J365/25'
|
||||||
|
|
||||||
|
self.assertEqual(utils.last_modified_date_to_timestamp(
|
||||||
|
'1970-01-01T00:00:00.000000'),
|
||||||
|
0.0)
|
||||||
|
|
||||||
|
finally:
|
||||||
|
if old_tz is not None:
|
||||||
|
os.environ['TZ'] = old_tz
|
||||||
|
else:
|
||||||
|
os.environ.pop('TZ')
|
||||||
|
|
||||||
def test_backwards(self):
|
def test_backwards(self):
|
||||||
# Test swift.common.utils.backward
|
# Test swift.common.utils.backward
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user