Skip to content
Open
Show file tree
Hide file tree
Changes from 12 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 12 additions & 2 deletions Lib/_pydatetime.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@

import time as _time
import math as _math
import sys
import os
from operator import index as _index

def _cmp(x, y):
Expand Down Expand Up @@ -1037,6 +1037,11 @@ def fromtimestamp(cls, t):
"Construct a date from a POSIX timestamp (like time.time())."
if t is None:
raise TypeError("'NoneType' object cannot be interpreted as an integer")
if t < 0 and os.name == 'nt':
# Windows converters throw an OSError for negative values.
y, m, d, hh, mm, ss, weekday, jday, dst = _time.localtime(0)
result = cls(y, m, d)
return result + timedelta(seconds=t)
y, m, d, hh, mm, ss, weekday, jday, dst = _time.localtime(t)
return cls(y, m, d)

Expand Down Expand Up @@ -1864,6 +1869,11 @@ def _fromtimestamp(cls, t, utc, tz):
us += 1000000

converter = _time.gmtime if utc else _time.localtime
if t < 0 and os.name == 'nt':
# Windows converters throw an OSError for negative values.
y, m, d, hh, mm, ss, weekday, jday, dst = converter(0)
result = cls(y, m, d, hh, mm, ss, 0, tz)
return result + timedelta(seconds=t, microseconds=us)
y, m, d, hh, mm, ss, weekday, jday, dst = converter(t)
ss = min(ss, 59) # clamp out leap seconds if the platform has them
result = cls(y, m, d, hh, mm, ss, us, tz)
Expand All @@ -1877,7 +1887,7 @@ def _fromtimestamp(cls, t, utc, tz):
# thus we can't perform fold detection for values of time less
# than the max time fold. See comments in _datetimemodule's
# version of this method for more details.
if t < max_fold_seconds and sys.platform.startswith("win"):
if t < max_fold_seconds and os.name == 'nt':
return result

y, m, d, hh, mm, ss = converter(t - max_fold_seconds)[:6]
Expand Down
35 changes: 15 additions & 20 deletions Lib/test/datetimetester.py
Original file line number Diff line number Diff line change
Expand Up @@ -2683,24 +2683,19 @@ def utcfromtimestamp(*args, **kwargs):
self.assertEqual(zero.second, 0)
self.assertEqual(zero.microsecond, 0)
one = fts(1e-6)
try:
minus_one = fts(-1e-6)
except OSError:
# localtime(-1) and gmtime(-1) is not supported on Windows
pass
else:
self.assertEqual(minus_one.second, 59)
self.assertEqual(minus_one.microsecond, 999999)

t = fts(-1e-8)
self.assertEqual(t, zero)
t = fts(-9e-7)
self.assertEqual(t, minus_one)
t = fts(-1e-7)
self.assertEqual(t, zero)
t = fts(-1/2**7)
self.assertEqual(t.second, 59)
self.assertEqual(t.microsecond, 992188)
minus_one = fts(-1e-6)
self.assertEqual(minus_one.second, 59)
self.assertEqual(minus_one.microsecond, 999999)

t = fts(-1e-8)
self.assertEqual(t, zero)
t = fts(-9e-7)
self.assertEqual(t, minus_one)
t = fts(-1e-7)
self.assertEqual(t, zero)
t = fts(-1/2**7)
self.assertEqual(t.second, 59)
self.assertEqual(t.microsecond, 992188)

t = fts(1e-7)
self.assertEqual(t, zero)
Expand Down Expand Up @@ -2735,6 +2730,7 @@ def test_timestamp_limits(self):
# If that assumption changes, this value can change as well
self.assertEqual(max_ts, 253402300799.0)

@unittest.skipIf(sys.platform == "win32", "Windows can't generate negative timestamps")
def test_fromtimestamp_limits(self):
try:
self.theclass.fromtimestamp(-2**32 - 1)
Expand Down Expand Up @@ -2774,6 +2770,7 @@ def test_fromtimestamp_limits(self):
# OverflowError, especially on 32-bit platforms.
self.theclass.fromtimestamp(ts)

@unittest.skipIf(sys.platform == "win32", "Windows can't generate negative timestamps")
def test_utcfromtimestamp_limits(self):
with self.assertWarns(DeprecationWarning):
try:
Expand Down Expand Up @@ -2835,13 +2832,11 @@ def test_insane_utcfromtimestamp(self):
self.assertRaises(OverflowError, self.theclass.utcfromtimestamp,
insane)

@unittest.skipIf(sys.platform == "win32", "Windows doesn't accept negative timestamps")
def test_negative_float_fromtimestamp(self):
# The result is tz-dependent; at least test that this doesn't
# fail (like it did before bug 1646728 was fixed).
self.theclass.fromtimestamp(-1.05)

@unittest.skipIf(sys.platform == "win32", "Windows doesn't accept negative timestamps")
def test_negative_float_utcfromtimestamp(self):
with self.assertWarns(DeprecationWarning):
d = self.theclass.utcfromtimestamp(-1.05)
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
Support negative values (dates before the UNIX epoch of 1970-01-01) in
:meth:`datetime.date.fromtimestamp` and
:meth:`datetime.datetime.fromtimestamp` on Windows, by substituting 0
and using :class:`datetime.timedelta` to go back in time. Patch by
John Keith Hohm.
56 changes: 53 additions & 3 deletions Modules/_datetimemodule.c
Original file line number Diff line number Diff line change
Expand Up @@ -3241,6 +3241,13 @@ date_new(PyTypeObject *type, PyObject *args, PyObject *kw)
return self;
}

static PyObject *add_datetime_timedelta(PyDateTime_DateTime *date,
PyDateTime_Delta *delta,
int factor);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you remove the duplicated definition line 3762?

Can you also move these two definitions at line 154? In the "Forward declarations" block.

static PyObject *
add_date_timedelta(PyDateTime_Date *date, PyDateTime_Delta *delta, int negate);


static PyObject *
date_fromtimestamp(PyObject *cls, PyObject *obj)
{
Expand All @@ -3250,6 +3257,32 @@ date_fromtimestamp(PyObject *cls, PyObject *obj)
if (_PyTime_ObjectToTime_t(obj, &t, _PyTime_ROUND_FLOOR) == -1)
return NULL;

#ifdef MS_WINDOWS
if (t < 0) {
if (_PyTime_localtime(0, &tm) != 0)
return NULL;
Comment on lines +3262 to +3263
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
if (_PyTime_localtime(0, &tm) != 0)
return NULL;
if (_PyTime_localtime(0, &tm) != 0) {
return NULL;
}


int normalize = 1, negate = 0;
PyObject *date = new_date_subclass_ex(tm.tm_year + 1900,
tm.tm_mon + 1,
tm.tm_mday,
cls);
if (date == NULL) {
return NULL;
}
PyObject *result = NULL;
PyObject *delta = new_delta(0, (int)t, 0, normalize);
if (delta == NULL) {
Py_DECREF(date);
return NULL;
}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You can move negate and result definitions here. There is no need to initialize result to NULL.

result = add_date_timedelta(PyDate_CAST(date), PyDelta_CAST(delta), negate);
Py_DECREF(delta);
Py_DECREF(date);
return result;
}
#endif

if (_PyTime_localtime(t, &tm) != 0)
return NULL;

Expand Down Expand Up @@ -4070,9 +4103,6 @@ tzinfo_dst(PyObject *Py_UNUSED(self), PyObject *Py_UNUSED(dt))
}


static PyObject *add_datetime_timedelta(PyDateTime_DateTime *date,
PyDateTime_Delta *delta,
int factor);
static PyObject *datetime_utcoffset(PyObject *self, PyObject *);
static PyObject *datetime_dst(PyObject *self, PyObject *);

Expand Down Expand Up @@ -5533,6 +5563,26 @@ datetime_from_timestamp(PyObject *cls, TM_FUNC f, PyObject *timestamp,
&timet, &us, _PyTime_ROUND_HALF_EVEN) == -1)
return NULL;

#ifdef MS_WINDOWS
if (timet < 0) {
int normalize = 1, factor = 1;
PyObject *dt = datetime_from_timet_and_us(cls, f, 0, 0, tzinfo);
if (dt == NULL) {
return NULL;
}
PyObject *result = NULL;
PyObject *delta = new_delta(0, (int)timet, us, normalize);
if (delta == NULL) {
Py_DECREF(dt);
return NULL;
}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You can move result definition here. There is no need to initialize it to NULL.

result = add_datetime_timedelta(PyDateTime_CAST(dt), PyDelta_CAST(delta), factor);
Py_DECREF(delta);
Py_DECREF(dt);
return result;
}
#endif

return datetime_from_timet_and_us(cls, f, timet, (int)us, tzinfo);
}

Expand Down
Loading