Skip to content

Commit

Permalink
Revert support for negative timestamps on Windows (#745)
Browse files Browse the repository at this point in the history
  • Loading branch information
systemcatch committed Jan 1, 2020
1 parent 70fb905 commit 1cd7f94
Show file tree
Hide file tree
Showing 5 changed files with 24 additions and 133 deletions.
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)

0 comments on commit 1cd7f94

Please sign in to comment.