Skip to content

Commit c253aa6

Browse files
author
peterfischer
committed
fix: datetime before epoch on windows in cython implementation
Cython implementation still used datetime.from_timestamp method, which does not work on windows. Update the cython implementation to use utc time and delta and add a regression test to highlight the issue.
1 parent 772c830 commit c253aa6

2 files changed

Lines changed: 48 additions & 19 deletions

File tree

msgpack/unpack.h

Lines changed: 40 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -342,32 +342,53 @@ static int unpack_callback_ext(unpack_user* u, const char* base, const char* pos
342342
py = PyObject_CallFunction(u->timestamp_t, "(Lk)", ts.tv_sec, ts.tv_nsec);
343343
}
344344
else { // float or datetime
345-
PyObject *a = PyFloat_FromDouble((double)ts.tv_nsec);
346-
if (a == NULL) return -1;
345+
if (u->timestamp == 3) { // datetime
346+
// Calculate datetime using epoch + delta
347+
// due to limitations PyDateTime_FromTimestamp on Windows with negative timestamps
348+
PyObject *t0 = PyLong_FromLongLong(0L);
349+
if (t0 == NULL) {
350+
return -1;
351+
}
347352

348-
PyObject *b = PyNumber_TrueDivide(a, u->giga);
349-
Py_DECREF(a);
350-
if (b == NULL) return -1;
353+
PyObject *t = PyTuple_Pack(2, t0, u->utc);
354+
Py_DECREF(t0);
355+
if (t == NULL) {
356+
return -1;
357+
}
351358

352-
PyObject *c = PyLong_FromLongLong(ts.tv_sec);
353-
if (c == NULL) {
354-
Py_DECREF(b);
355-
return -1;
356-
}
359+
PyObject *t_epoch = PyDateTime_FromTimestamp(t);
360+
Py_DECREF(t);
361+
if (t_epoch == NULL) {
362+
return -1;
363+
}
357364

358-
a = PyNumber_Add(b, c);
359-
Py_DECREF(b);
360-
Py_DECREF(c);
365+
PyObject* d = PyDelta_FromDSU(0, ts.tv_sec, ts.tv_nsec / 1000);
366+
if (d == NULL) {
367+
Py_DECREF(t_epoch);
368+
return -1;
369+
}
361370

362-
if (u->timestamp == 3) { // datetime
363-
PyObject *t = PyTuple_Pack(2, a, u->utc);
371+
py = PyObject_CallMethod(t_epoch, "__add__", "(O)", d);
372+
373+
Py_DECREF(t_epoch);
374+
Py_DECREF(d);
375+
} else { // float
376+
PyObject *a = PyFloat_FromDouble((double)ts.tv_nsec);
377+
if (a == NULL) return -1;
378+
379+
PyObject *b = PyNumber_TrueDivide(a, u->giga);
364380
Py_DECREF(a);
365-
if (t == NULL) {
381+
if (b == NULL) return -1;
382+
383+
PyObject *c = PyLong_FromLongLong(ts.tv_sec);
384+
if (c == NULL) {
385+
Py_DECREF(b);
366386
return -1;
367387
}
368-
py = PyDateTime_FromTimestamp(t);
369-
Py_DECREF(t);
370-
} else { // float
388+
389+
a = PyNumber_Add(b, c);
390+
Py_DECREF(b);
391+
Py_DECREF(c);
371392
py = a;
372393
}
373394
}

test/test_timestamp.py

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -99,6 +99,14 @@ def test_unpack_datetime():
9999
assert unpacked == datetime.datetime(1970, 1, 1, 0, 0, 42, 0, tzinfo=_utc)
100100

101101

102+
@pytest.mark.skipif(sys.version_info[0] == 2, reason="datetime support is PY3+ only")
103+
def test_pack_unpack_before_epoch():
104+
t_in = datetime.datetime(1960, 1, 1, tzinfo=_utc)
105+
packed = msgpack.packb(t_in, datetime=True)
106+
unpacked = msgpack.unpackb(packed, timestamp=3)
107+
assert unpacked == t_in
108+
109+
102110
@pytest.mark.skipif(sys.version_info[0] == 2, reason="datetime support is PY3+ only")
103111
def test_pack_datetime():
104112
t = Timestamp(42, 14000)

0 commit comments

Comments
 (0)