Thanks to visit codestin.com
Credit goes to github.com

Skip to content

gh-80620: Support negative values in fromtimestamp on Windows using 0 + timedelta #134461

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

Open
wants to merge 14 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all 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
11 changes: 9 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,9 @@ 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.
return cls.fromtimestamp(0) + timedelta(seconds=t)
y, m, d, hh, mm, ss, weekday, jday, dst = _time.localtime(t)
return cls(y, m, d)

Expand Down Expand Up @@ -1854,6 +1857,10 @@ def _fromtimestamp(cls, t, utc, tz):

A timezone info object may be passed in as well.
"""
if t < 0 and os.name == 'nt':
# Windows converters throw an OSError for negative values.
return cls._fromtimestamp(0, utc, tz) + timedelta(seconds=t)

frac, t = _math.modf(t)
us = round(frac * 1e6)
if us >= 1000000:
Expand All @@ -1877,7 +1884,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.
53 changes: 50 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,29 @@ 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 negate = 0;
PyObject *date = date_fromtimestamp(cls, _PyLong_GetZero());
if (date == NULL) {
return NULL;
}
PyObject *result = NULL;
PyObject *delta = PyObject_CallFunction((PyObject*)&DELTA_TYPE(NO_STATE), "iO", 0, obj);
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 +4100,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 +5560,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 factor = 1;
PyObject *dt = datetime_from_timet_and_us(cls, f, 0, 0, tzinfo);
if (dt == NULL) {
return NULL;
}
PyObject *result = NULL;
PyObject *delta = PyObject_CallFunction((PyObject*)&DELTA_TYPE(NO_STATE), "iO", 0, timestamp);
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