Skip to content

Commit 6704876

Browse files
committed
Support negative values in fromtimestamp on Windows using 0 + timedelta.
1 parent 4eacf38 commit 6704876

File tree

4 files changed

+55
-20
lines changed

4 files changed

+55
-20
lines changed

Lib/_pydatetime.py

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1037,6 +1037,11 @@ def fromtimestamp(cls, t):
10371037
"Construct a date from a POSIX timestamp (like time.time())."
10381038
if t is None:
10391039
raise TypeError("'NoneType' object cannot be interpreted as an integer")
1040+
if t < 0 and sys.platform.startswith("win"):
1041+
# Windows converters throw an OSError for negative values.
1042+
y, m, d, hh, mm, ss, weekday, jday, dst = _time.localtime(0)
1043+
result = cls(y, m, d)
1044+
return result + timedelta(seconds=t)
10401045
y, m, d, hh, mm, ss, weekday, jday, dst = _time.localtime(t)
10411046
return cls(y, m, d)
10421047

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

18661871
converter = _time.gmtime if utc else _time.localtime
1872+
if t < 0 and sys.platform.startswith("win"):
1873+
# Windows converters throw an OSError for negative values.
1874+
y, m, d, hh, mm, ss, weekday, jday, dst = converter(0)
1875+
result = cls(y, m, d, hh, mm, ss, us, tz)
1876+
return result + timedelta(seconds=t, microseconds=us)
18671877
y, m, d, hh, mm, ss, weekday, jday, dst = converter(t)
18681878
ss = min(ss, 59) # clamp out leap seconds if the platform has them
18691879
result = cls(y, m, d, hh, mm, ss, us, tz)

Lib/test/datetimetester.py

Lines changed: 13 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -2683,24 +2683,19 @@ def utcfromtimestamp(*args, **kwargs):
26832683
self.assertEqual(zero.second, 0)
26842684
self.assertEqual(zero.microsecond, 0)
26852685
one = fts(1e-6)
2686-
try:
2687-
minus_one = fts(-1e-6)
2688-
except OSError:
2689-
# localtime(-1) and gmtime(-1) is not supported on Windows
2690-
pass
2691-
else:
2692-
self.assertEqual(minus_one.second, 59)
2693-
self.assertEqual(minus_one.microsecond, 999999)
2694-
2695-
t = fts(-1e-8)
2696-
self.assertEqual(t, zero)
2697-
t = fts(-9e-7)
2698-
self.assertEqual(t, minus_one)
2699-
t = fts(-1e-7)
2700-
self.assertEqual(t, zero)
2701-
t = fts(-1/2**7)
2702-
self.assertEqual(t.second, 59)
2703-
self.assertEqual(t.microsecond, 992188)
2686+
minus_one = fts(-1e-6)
2687+
self.assertEqual(minus_one.second, 59)
2688+
self.assertEqual(minus_one.microsecond, 999999)
2689+
2690+
t = fts(-1e-8)
2691+
self.assertEqual(t, zero)
2692+
t = fts(-9e-7)
2693+
self.assertEqual(t, minus_one)
2694+
t = fts(-1e-7)
2695+
self.assertEqual(t, zero)
2696+
t = fts(-1/2**7)
2697+
self.assertEqual(t.second, 59)
2698+
self.assertEqual(t.microsecond, 992188)
27042699

27052700
t = fts(1e-7)
27062701
self.assertEqual(t, zero)
@@ -2835,13 +2830,11 @@ def test_insane_utcfromtimestamp(self):
28352830
self.assertRaises(OverflowError, self.theclass.utcfromtimestamp,
28362831
insane)
28372832

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

2844-
@unittest.skipIf(sys.platform == "win32", "Windows doesn't accept negative timestamps")
28452838
def test_negative_float_utcfromtimestamp(self):
28462839
with self.assertWarns(DeprecationWarning):
28472840
d = self.theclass.utcfromtimestamp(-1.05)
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
Support negative values in :meth:`datetime.date.fromtimestamp` and
2+
:meth:`datetime.datetime.fromtimestamp` on Windows, by substituting 0 and
3+
using :class:`datetime.timedelta` to go back in time. Patch by John Keith
4+
Hohm.

Modules/_datetimemodule.c

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3250,6 +3250,20 @@ date_fromtimestamp(PyObject *cls, PyObject *obj)
32503250
if (_PyTime_ObjectToTime_t(obj, &t, _PyTime_ROUND_FLOOR) == -1)
32513251
return NULL;
32523252

3253+
#ifdef MS_WINDOWS
3254+
if (t < 0) {
3255+
if (_PyTime_localtime(0, &tm) != 0)
3256+
return NULL;
3257+
3258+
PyObject *date = new_date_subclass_ex(tm.tm_year + 1900,
3259+
tm.tm_mon + 1,
3260+
tm.tm_mday,
3261+
cls);
3262+
PyObject *delta = new_delta(0, t, 0, 1);
3263+
return add_datetime_timedelta(date, delta, 1);
3264+
}
3265+
#endif
3266+
32533267
if (_PyTime_localtime(t, &tm) != 0)
32543268
return NULL;
32553269

@@ -5529,6 +5543,20 @@ datetime_from_timestamp(PyObject *cls, TM_FUNC f, PyObject *timestamp,
55295543
time_t timet;
55305544
long us;
55315545

5546+
#ifdef MS_WINDOWS
5547+
if (PyFloat_Check(timestamp)) {
5548+
if (PyFloat_AsDouble(timestamp) < 0) {
5549+
if (_PyTime_ObjectToTimeval(timestamp,
5550+
&timet, &us, _PyTime_ROUND_HALF_EVEN) == -1)
5551+
return NULL;
5552+
5553+
PyObject *dt = datetime_from_timet_and_us(cls, f, 0, 0, tzinfo);
5554+
PyObject *delta = new_delta(0, timet, us, 1);
5555+
return add_datetime_timedelta(date, delta, 1);
5556+
}
5557+
}
5558+
#endif
5559+
55325560
if (_PyTime_ObjectToTimeval(timestamp,
55335561
&timet, &us, _PyTime_ROUND_HALF_EVEN) == -1)
55345562
return NULL;

0 commit comments

Comments
 (0)