blob: 3e9420eca3593d7c62f1682066e722db909f4977 [file] [edit]
import calendar
import datetime
from email.utils import formatdate, parsedate
import time
import pytest
from webob import datetime_utils
def test_UTC():
"""Test missing function in _UTC"""
x = datetime_utils.UTC
assert x.tzname(datetime.datetime.now()) == "UTC"
assert x.dst(datetime.datetime.now()) == datetime.timedelta(0)
assert x.utcoffset(datetime.datetime.now()) == datetime.timedelta(0)
assert repr(x) == "UTC"
# Testing datetime_utils.parse_date.
# We need to verify the following scenarios:
# * a nil submitted value
# * a submitted value that cannot be parse into a date
# * a valid RFC2822 date with and without timezone
class Uncooperative:
def __str__(self):
raise NotImplementedError
@pytest.mark.parametrize("invalid_date", [None, "Hi there", 1, "\xc3", Uncooperative()])
def test_parse_date_invalid(invalid_date):
assert datetime_utils.parse_date(invalid_date) is None
@pytest.mark.parametrize(
"valid_date, parsed_datetime",
[
(
"Mon, 20 Nov 1995 19:12:08 -0500",
datetime.datetime(1995, 11, 21, 0, 12, 8, tzinfo=datetime_utils.UTC),
),
(
"Mon, 20 Nov 1995 19:12:08",
datetime.datetime(1995, 11, 20, 19, 12, 8, tzinfo=datetime_utils.UTC),
),
],
)
def test_parse_date_valid(valid_date, parsed_datetime):
assert datetime_utils.parse_date(valid_date) == parsed_datetime
def test_serialize_date():
"""Testing datetime_utils.serialize_date
We need to verify the following scenarios:
* on py3, passing an binary date, return the same date but str
* on py2, passing an unicode date, return the same date but str
* passing a timedelta, return now plus the delta
* passing an invalid object, should raise ValueError
"""
from webob.util import text_
ret = datetime_utils.serialize_date("Mon, 20 Nov 1995 19:12:08 GMT")
assert isinstance(ret, str)
assert ret == "Mon, 20 Nov 1995 19:12:08 GMT"
ret = datetime_utils.serialize_date(text_("Mon, 20 Nov 1995 19:12:08 GMT"))
assert isinstance(ret, str)
assert ret == "Mon, 20 Nov 1995 19:12:08 GMT"
dt = formatdate(
calendar.timegm((datetime_utils.utcnow() + datetime.timedelta(1)).timetuple()),
usegmt=True,
)
assert dt == datetime_utils.serialize_date(datetime.timedelta(1))
with pytest.raises(ValueError):
datetime_utils.serialize_date(None)
@pytest.mark.skipif(
not hasattr(time, "tzset"),
reason="time.tzset() (setting the process timezone) is not available",
)
def test_serialize_date_timedelta_is_utc_based(local_timezone):
"""A timedelta must be added to *UTC* now, not naive local time.
``serialize_date`` labels its output as GMT (``usegmt=True``) and builds the
epoch with ``calendar.timegm`` (which treats the time tuple as UTC). If the
"now" the delta is added to is naive local time, the resulting header ends
up off by the local UTC offset. See
https://github.com/Pylons/webob/issues/430.
Running under a non-UTC timezone, the serialized zero-delta header must
still match the current UTC time (within a small tolerance for the second
boundary), regardless of the local offset.
"""
# "America/New_York" is a non-UTC, DST-having offset
with local_timezone("America/New_York"):
before = datetime_utils.utcnow()
header = datetime_utils.serialize_date(datetime.timedelta(seconds=0))
after = datetime_utils.utcnow()
header_ts = calendar.timegm(parsedate(header))
assert header_ts is not None, header
lower = calendar.timegm(before.timetuple()) - 1
upper = calendar.timegm(after.timetuple()) + 1
assert lower <= header_ts <= upper, (
"Expires header %r (%d) is not within [%d, %d] of UTC now; "
"it appears to use naive local time" % (header, header_ts, lower, upper)
)
def test_parse_date_delta():
"""Testing datetime_utils.parse_date_delta
We need to verify the following scenarios:
* passing a nil value, should return nil
* passing a value that fails the conversion to int, should call
parse_date
"""
assert datetime_utils.parse_date_delta(None) is None, (
"Passing none value," "should return None"
)
ret = datetime_utils.parse_date_delta("Mon, 20 Nov 1995 19:12:08 -0500")
assert ret == datetime.datetime(1995, 11, 21, 0, 12, 8, tzinfo=datetime_utils.UTC)
WHEN = datetime.datetime(2011, 3, 16, 10, 10, 37, tzinfo=datetime_utils.UTC)
with _NowRestorer(WHEN):
ret = datetime_utils.parse_date_delta(1)
assert ret == WHEN + datetime.timedelta(0, 1)
@pytest.mark.skipif(
not hasattr(time, "tzset"),
reason="time.tzset() (setting the process timezone) is not available",
)
def test_parse_date_delta_is_utc_based(local_timezone):
"""
Delta seconds are resolved against UTC now, not naive local time. Reading
back a delta-seconds header (such as ``Response.retry_after``) otherwise
returns an instant off by the local UTC offset. See
https://github.com/Pylons/webob/issues/430
"""
# "America/New_York" is a non-UTC, DST-having offset
with local_timezone("America/New_York"):
# Note: a falsy value returns None, so the delta must be non-zero.
delta = datetime.timedelta(seconds=1)
before = datetime_utils.utcnow() + delta
ret = datetime_utils.parse_date_delta(1)
after = datetime_utils.utcnow() + delta
assert before <= ret <= after, (
"parse_date_delta(1) returned %r, outside [%r, %r]; it appears to use "
"naive local time" % (ret, before, after)
)
def test_serialize_date_delta():
"""Testing datetime_utils.serialize_date_delta
We need to verify the following scenarios:
* if we pass something that's not an int or float, it should delegate
the task to serialize_date
"""
assert datetime_utils.serialize_date_delta(1) == "1"
assert datetime_utils.serialize_date_delta(1.5) == "1"
ret = datetime_utils.serialize_date_delta("Mon, 20 Nov 1995 19:12:08 GMT")
assert type(ret) is (str)
assert ret == "Mon, 20 Nov 1995 19:12:08 GMT"
def test_timedelta_to_seconds():
val = datetime.timedelta(86400)
result = datetime_utils.timedelta_to_seconds(val)
assert result == 7464960000
def test_utcnow():
"""utcnow() is a drop-in for the deprecated datetime.utcnow()."""
before = datetime.datetime.now(datetime.timezone.utc).replace(tzinfo=None)
result = datetime_utils.utcnow()
after = datetime.datetime.now(datetime.timezone.utc).replace(tzinfo=None)
assert result.tzinfo is None
assert before <= result <= after
class _NowRestorer:
def __init__(self, new_now):
self._new_now = new_now
self._old_now = None
def __enter__(self):
import webob.datetime_utils
self._old_now = webob.datetime_utils._now
webob.datetime_utils._now = lambda: self._new_now
def __exit__(self, exc_type, exc_value, traceback):
import webob.datetime_utils
webob.datetime_utils._now = self._old_now