Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Revert support for negative timestamps on Windows #745

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
4 changes: 2 additions & 2 deletions arrow/arrow.py
Expand Up @@ -162,7 +162,7 @@ def fromtimestamp(cls, timestamp, tzinfo=None):
"The provided timestamp '{}' is invalid.".format(timestamp)
)

dt = util.safe_fromtimestamp(float(timestamp), tzinfo)
dt = datetime.fromtimestamp(float(timestamp), tzinfo)

return cls(
dt.year,
Expand All @@ -188,7 +188,7 @@ def utcfromtimestamp(cls, timestamp):
"The provided timestamp '{}' is invalid.".format(timestamp)
)

dt = util.safe_utcfromtimestamp(float(timestamp))
dt = datetime.utcfromtimestamp(float(timestamp))

return cls(
dt.year,
Expand Down
6 changes: 3 additions & 3 deletions arrow/parser.py
Expand Up @@ -6,7 +6,7 @@

from dateutil import tz

from arrow import locales, util
from arrow import locales
from arrow.constants import MAX_TIMESTAMP, MAX_TIMESTAMP_MS, MAX_TIMESTAMP_US

try:
Expand Down Expand Up @@ -383,7 +383,7 @@ def _build_datetime(parts):
timestamp = parts.get("timestamp")

if timestamp is not None:
return util.safe_fromtimestamp(timestamp, tz=tz.tzutc())
return datetime.fromtimestamp(timestamp, tz=tz.tzutc())

expanded_timestamp = parts.get("expanded_timestamp")

Expand All @@ -401,7 +401,7 @@ def _build_datetime(parts):
)
)

return util.safe_fromtimestamp(expanded_timestamp, tz=tz.tzutc())
return datetime.fromtimestamp(expanded_timestamp, tz=tz.tzutc())

day_of_year = parts.get("day_of_year")

Expand Down
53 changes: 1 addition & 52 deletions arrow/util.py
Expand Up @@ -2,11 +2,6 @@
from __future__ import absolute_import

import datetime
import math
import time
from os import name as os_name

import dateutil


def total_seconds(td): # pragma: no cover
Expand All @@ -28,44 +23,6 @@ def is_timestamp(value):
return False


def windows_datetime_from_timestamp(timestamp, tz=None):
"""Computes datetime from timestamp. Supports negative timestamps on Windows platform."""
sec_frac, sec = math.modf(timestamp)
dt = datetime.datetime(1970, 1, 1, tzinfo=dateutil.tz.tzutc()) + datetime.timedelta(
seconds=sec, microseconds=sec_frac * 1000000
)
if tz is None:
tz = dateutil.tz.tzlocal()

if tz == dateutil.tz.tzlocal():
# because datetime.astimezone does not work on Windows for tzlocal() and dates before the 1970-01-01
# take timestamp from appropriate time of the year, because of daylight saving time changes
ts = time.mktime(dt.replace(year=1970).timetuple())
dt += datetime.datetime.fromtimestamp(ts) - datetime.datetime.utcfromtimestamp(
ts
)
dt = dt.replace(tzinfo=dateutil.tz.tzlocal())
else:
dt = dt.astimezone(tz)
return dt


def safe_utcfromtimestamp(timestamp):
""" datetime.utcfromtimestamp alternative which supports negative timestamps on Windows platform."""
if os_name == "nt" and timestamp < 0:
return windows_datetime_from_timestamp(timestamp, dateutil.tz.tzutc())
else:
return datetime.datetime.utcfromtimestamp(timestamp)


def safe_fromtimestamp(timestamp, tz=None):
""" datetime.fromtimestamp alternative which supports negative timestamps on Windows platform."""
if os_name == "nt" and timestamp < 0:
return windows_datetime_from_timestamp(timestamp, tz)
else:
return datetime.datetime.fromtimestamp(timestamp, tz)


# Credit to https://stackoverflow.com/a/1700069
def iso_to_gregorian(iso_year, iso_week, iso_day):
"""Converts an ISO week date tuple into a datetime object."""
Expand Down Expand Up @@ -100,12 +57,4 @@ def isstr(s):
return isinstance(s, str)


__all__ = [
"total_seconds",
"is_timestamp",
"isstr",
"iso_to_gregorian",
"windows_datetime_from_timestamp",
"safe_utcfromtimestamp",
"safe_fromtimestamp",
]
__all__ = ["total_seconds", "is_timestamp", "isstr", "iso_to_gregorian"]
31 changes: 18 additions & 13 deletions tests/parser_tests.py
Expand Up @@ -2,13 +2,14 @@
from __future__ import unicode_literals

import calendar
import os
import time
from datetime import datetime

from chai import Chai
from dateutil import tz

from arrow import parser, util
from arrow import parser
from arrow.constants import MAX_TIMESTAMP_US
from arrow.parser import DateTimeParser, ParserError, ParserMatchError

Expand Down Expand Up @@ -227,19 +228,23 @@ def test_parse_timestamp(self):
self.parser.parse("{:f}123456".format(float_timestamp), "X"), self.expected
)

# regression test for issue #662
negative_int_timestamp = -int_timestamp
self.expected = util.safe_fromtimestamp(negative_int_timestamp, tz=tz_utc)
self.assertEqual(
self.parser.parse("{:d}".format(negative_int_timestamp), "X"), self.expected
)
# NOTE: negative timestamps cannot be handled by datetime on Window
# Must use timedelta to handle them. ref: https://stackoverflow.com/questions/36179914
if os.name != "nt":
# regression test for issue #662
negative_int_timestamp = -int_timestamp
self.expected = datetime.fromtimestamp(negative_int_timestamp, tz=tz_utc)
self.assertEqual(
self.parser.parse("{:d}".format(negative_int_timestamp), "X"),
self.expected,
)

negative_float_timestamp = -float_timestamp
self.expected = util.safe_fromtimestamp(negative_float_timestamp, tz=tz_utc)
self.assertEqual(
self.parser.parse("{:f}".format(negative_float_timestamp), "X"),
self.expected,
)
negative_float_timestamp = -float_timestamp
self.expected = datetime.fromtimestamp(negative_float_timestamp, tz=tz_utc)
self.assertEqual(
self.parser.parse("{:f}".format(negative_float_timestamp), "X"),
self.expected,
)

# NOTE: timestamps cannot be parsed from natural language strings (by removing the ^...$) because it will
# break cases like "15 Jul 2000" and a format list (see issue #447)
Expand Down
63 changes: 0 additions & 63 deletions tests/util_tests.py
@@ -1,10 +1,7 @@
# -*- coding: utf-8 -*-
import time
from datetime import datetime

from chai import Chai
from dateutil import tz
from mock import patch

from arrow import util

Expand Down Expand Up @@ -36,63 +33,3 @@ def test_iso_gregorian(self):

with self.assertRaises(ValueError):
util.iso_to_gregorian(2013, 8, 0)

def test_windows_datetime_from_timestamp(self):
timestamp = 1572204340.6460679
result = util.windows_datetime_from_timestamp(timestamp)
expected = datetime.fromtimestamp(timestamp).replace(tzinfo=tz.tzlocal())
self.assertEqual(result, expected)

def test_windows_datetime_from_timestamp_utc(self):
timestamp = 1572204340.6460679
result = util.windows_datetime_from_timestamp(timestamp, tz.tzutc())
expected = datetime.utcfromtimestamp(timestamp).replace(tzinfo=tz.tzutc())
self.assertEqual(result, expected)

def test_safe_utcfromtimestamp(self):
timestamp = 1572204340.6460679
result = util.safe_utcfromtimestamp(timestamp).replace(tzinfo=tz.tzutc())
expected = datetime.utcfromtimestamp(timestamp).replace(tzinfo=tz.tzutc())
self.assertEqual(result, expected)

def test_safe_fromtimestamp_default_tz(self):
timestamp = 1572204340.6460679
result = util.safe_fromtimestamp(timestamp).replace(tzinfo=tz.tzlocal())
expected = datetime.fromtimestamp(timestamp).replace(tzinfo=tz.tzlocal())
self.assertEqual(result, expected)

def test_safe_fromtimestamp_paris_tz(self):
timestamp = 1572204340.6460679
result = util.safe_fromtimestamp(timestamp, tz.gettz("Europe/Paris"))
expected = datetime.fromtimestamp(timestamp, tz.gettz("Europe/Paris"))
self.assertEqual(result, expected)

def test_safe_utcfromtimestamp_negative(self):
timestamp = -1572204340.6460679
result = util.safe_utcfromtimestamp(timestamp).replace(tzinfo=tz.tzutc())
expected = datetime(1920, 3, 7, 4, 34, 19, 353932, tzinfo=tz.tzutc())
self.assertEqual(result, expected)

def test_safe_fromtimestamp_negative(self):
timestamp = -1572204340.6460679
result = util.safe_fromtimestamp(timestamp, tz.gettz("Europe/Paris"))
expected = datetime(
1920, 3, 7, 5, 34, 19, 353932, tzinfo=tz.gettz("Europe/Paris")
)
self.assertEqual(result, expected)

@patch.object(util, "os_name", "nt")
def test_safe_utcfromtimestamp_negative_nt(self):
timestamp = -1572204340.6460679
result = util.safe_utcfromtimestamp(timestamp).replace(tzinfo=tz.tzutc())
expected = datetime(1920, 3, 7, 4, 34, 19, 353932, tzinfo=tz.tzutc())
self.assertEqual(result, expected)

@patch.object(util, "os_name", "nt")
def test_safe_fromtimestamp_negative_nt(self):
timestamp = -1572204340.6460679
result = util.safe_fromtimestamp(timestamp, tz.gettz("Europe/Paris"))
expected = datetime(
1920, 3, 7, 5, 34, 19, 353932, tzinfo=tz.gettz("Europe/Paris")
)
self.assertEqual(result, expected)