Skip to content
Open
Show file tree
Hide file tree
Changes from 7 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
10 changes: 10 additions & 0 deletions Lib/_pydatetime.py
Original file line number Diff line number Diff line change
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 sys.platform.startswith("win"):
# 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 sys.platform.startswith("win"):
# 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 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,4 @@
Support negative values 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.
40 changes: 37 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,24 @@ 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);
PyObject *delta = new_delta(0, (int)t, 0, normalize);
PyObject *result = add_date_timedelta(PyDate_CAST(date), PyDelta_CAST(delta), negate);
Py_XDECREF(delta);
Py_XDECREF(date);
return result;
}
#endif

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

Expand Down Expand Up @@ -4070,9 +4095,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 +5555,18 @@ 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);
PyObject *delta = new_delta(0, (int)timet, us, normalize);
PyObject *result = add_datetime_timedelta(PyDateTime_CAST(dt), PyDelta_CAST(delta), factor);
Py_XDECREF(delta);
Py_XDECREF(dt);
return result;
}
#endif

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

Expand Down
Loading