8000 Fix datetime before epoch on windows in cython implementation (#436) · nomissbowling/msgpack-python@8fb709f · GitHub
[go: up one dir, main page]

Skip to content

Commit 8fb709f

Browse files
authored
Fix datetime before epoch on windows in cython implementation (msgpack#436)
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 8fb709f

File tree

3 files changed

+30
-13
lines changed

3 files changed

+30
-13
lines changed

Makefile

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ clean:
2525
rm -rf build
2626
rm -f msgpack/_cmsgpack.cpp
2727
rm -f msgpack/_cmsgpack.*.so
28+
rm -f msgpack/_cmsgpack.*.pyd
2829
rm -rf msgpack/__pycache__
2930
rm -rf test/__pycache__
3031

msgpack/unpack.h

Lines changed: 21 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -341,7 +341,26 @@ static int unpack_callback_ext(unpack_user* u, const char* base, const char* pos
341341
else if (u->timestamp == 0) { // Timestamp
342342
py = PyObject_CallFunction(u->timestamp_t, "(Lk)", ts.tv_sec, ts.tv_nsec);
343343
}
344-
else { // float or datetime
344+
else if (u->timestamp == 3) { // datetime
345+
// Calculate datetime using epoch + delta
346+
// due to limitations PyDateTime_FromTimestamp on Windows with negative timestamps
347+
PyObject *epoch = PyDateTimeAPI->DateTime_FromDateAndTime(1970, 1, 1, 0, 0, 0, 0, u->utc, PyDateTimeAPI->DateTimeType);
348+
if (epoch == NULL) {
349+
return -1;
350+
}
351+
352+
PyObject* d = PyDelta_FromDSU(0, ts.tv_sec, ts.tv_nsec / 1000);
353+
if (d == NULL) {
354+
Py_DECREF(epoch);
355+
return -1;
356+
}
357+
358+
py = PyNumber_Add(epoch, d);
359+
360+
Py_DECREF(epoch);
361+
Py_DECREF(d);
362+
}
363+
else { // float
345364
PyObject *a = PyFloat_FromDouble((double)ts.tv_nsec);
346365
if (a == NULL) return -1;
347366

@@ -358,18 +377,7 @@ static int unpack_callback_ext(unpack_user* u, const char* base, const char* pos
358377
a = PyNumber_Add(b, c);
359378
Py_DECREF(b);
360379
Py_DECREF(c);
361-
362-
if (u->timestamp == 3) { // datetime
363-
PyObject *t = PyTuple_Pack(2, a, u->utc);
364-
Py_DECREF(a);
365-
if (t == NULL) {
366-
return -1;
367-
}
368-
py = PyDateTime_FromTimestamp(t);
369-
Py_DECREF(t);
370-
} else { // float
371-
py = a;
372-
}
380+
py = a;
373381
}
374382
} else {
375383
py = PyObject_CallFunction(u->ext_hook, "(iy#)", (int)typecode, pos, (Py_ssize_t)length-1);

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)
0