mirror of
https://github.com/pybind/pybind11.git
synced 2024-11-22 13:15:12 +00:00
Fix bug roundtripping datetime.time objects after midnight in eastern hemisphere timezones (#2417) (#2438)
* Fix bug roundtripping datetime.time objects after midnight in eastern hemisphere timezones (#2417) * tests: check more timezones * Fix review remarks: remove useless comment and skip setting TZ environment variable on Windows
This commit is contained in:
parent
1abc4a9de5
commit
6a192781fc
@ -150,21 +150,28 @@ public:
|
|||||||
// Lazy initialise the PyDateTime import
|
// Lazy initialise the PyDateTime import
|
||||||
if (!PyDateTimeAPI) { PyDateTime_IMPORT; }
|
if (!PyDateTimeAPI) { PyDateTime_IMPORT; }
|
||||||
|
|
||||||
std::time_t tt = system_clock::to_time_t(time_point_cast<system_clock::duration>(src));
|
// Get out microseconds, and make sure they are positive, to avoid bug in eastern hemisphere time zones
|
||||||
|
// (cfr. https://github.com/pybind/pybind11/issues/2417)
|
||||||
|
using us_t = duration<int, std::micro>;
|
||||||
|
auto us = duration_cast<us_t>(src.time_since_epoch() % seconds(1));
|
||||||
|
if (us.count() < 0)
|
||||||
|
us += seconds(1);
|
||||||
|
|
||||||
|
// Subtract microseconds BEFORE `system_clock::to_time_t`, because:
|
||||||
|
// > If std::time_t has lower precision, it is implementation-defined whether the value is rounded or truncated.
|
||||||
|
// (https://en.cppreference.com/w/cpp/chrono/system_clock/to_time_t)
|
||||||
|
std::time_t tt = system_clock::to_time_t(time_point_cast<system_clock::duration>(src - us));
|
||||||
// this function uses static memory so it's best to copy it out asap just in case
|
// this function uses static memory so it's best to copy it out asap just in case
|
||||||
// otherwise other code that is using localtime may break this (not just python code)
|
// otherwise other code that is using localtime may break this (not just python code)
|
||||||
std::tm localtime = *std::localtime(&tt);
|
std::tm localtime = *std::localtime(&tt);
|
||||||
|
|
||||||
// Declare these special duration types so the conversions happen with the correct primitive types (int)
|
|
||||||
using us_t = duration<int, std::micro>;
|
|
||||||
|
|
||||||
return PyDateTime_FromDateAndTime(localtime.tm_year + 1900,
|
return PyDateTime_FromDateAndTime(localtime.tm_year + 1900,
|
||||||
localtime.tm_mon + 1,
|
localtime.tm_mon + 1,
|
||||||
localtime.tm_mday,
|
localtime.tm_mday,
|
||||||
localtime.tm_hour,
|
localtime.tm_hour,
|
||||||
localtime.tm_min,
|
localtime.tm_min,
|
||||||
localtime.tm_sec,
|
localtime.tm_sec,
|
||||||
(duration_cast<us_t>(src.time_since_epoch() % seconds(1))).count());
|
us.count());
|
||||||
}
|
}
|
||||||
PYBIND11_TYPE_CASTER(type, _("datetime.datetime"));
|
PYBIND11_TYPE_CASTER(type, _("datetime.datetime"));
|
||||||
};
|
};
|
||||||
|
@ -10,6 +10,7 @@
|
|||||||
|
|
||||||
#include "pybind11_tests.h"
|
#include "pybind11_tests.h"
|
||||||
#include <pybind11/chrono.h>
|
#include <pybind11/chrono.h>
|
||||||
|
#include <chrono>
|
||||||
|
|
||||||
TEST_SUBMODULE(chrono, m) {
|
TEST_SUBMODULE(chrono, m) {
|
||||||
using system_time = std::chrono::system_clock::time_point;
|
using system_time = std::chrono::system_clock::time_point;
|
||||||
|
@ -1,6 +1,9 @@
|
|||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
from pybind11_tests import chrono as m
|
from pybind11_tests import chrono as m
|
||||||
import datetime
|
import datetime
|
||||||
|
import pytest
|
||||||
|
|
||||||
|
import env # noqa: F401
|
||||||
|
|
||||||
|
|
||||||
def test_chrono_system_clock():
|
def test_chrono_system_clock():
|
||||||
@ -70,8 +73,30 @@ def test_chrono_system_clock_roundtrip_date():
|
|||||||
assert time2.microsecond == 0
|
assert time2.microsecond == 0
|
||||||
|
|
||||||
|
|
||||||
def test_chrono_system_clock_roundtrip_time():
|
SKIP_TZ_ENV_ON_WIN = pytest.mark.skipif(
|
||||||
time1 = datetime.datetime.today().time()
|
"env.WIN", reason="TZ environment variable only supported on POSIX"
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.parametrize("time1", [
|
||||||
|
datetime.datetime.today().time(),
|
||||||
|
datetime.time(0, 0, 0),
|
||||||
|
datetime.time(0, 0, 0, 1),
|
||||||
|
datetime.time(0, 28, 45, 109827),
|
||||||
|
datetime.time(0, 59, 59, 999999),
|
||||||
|
datetime.time(1, 0, 0),
|
||||||
|
datetime.time(5, 59, 59, 0),
|
||||||
|
datetime.time(5, 59, 59, 1),
|
||||||
|
])
|
||||||
|
@pytest.mark.parametrize("tz", [
|
||||||
|
None,
|
||||||
|
pytest.param("Europe/Brussels", marks=SKIP_TZ_ENV_ON_WIN),
|
||||||
|
pytest.param("Asia/Pyongyang", marks=SKIP_TZ_ENV_ON_WIN),
|
||||||
|
pytest.param("America/New_York", marks=SKIP_TZ_ENV_ON_WIN),
|
||||||
|
])
|
||||||
|
def test_chrono_system_clock_roundtrip_time(time1, tz, monkeypatch):
|
||||||
|
if tz is not None:
|
||||||
|
monkeypatch.setenv("TZ", "/usr/share/zoneinfo/{}".format(tz))
|
||||||
|
|
||||||
# Roundtrip the time
|
# Roundtrip the time
|
||||||
datetime2 = m.test_chrono2(time1)
|
datetime2 = m.test_chrono2(time1)
|
||||||
|
Loading…
Reference in New Issue
Block a user