Unverified Commit 6a192781 authored by Yannick Jadoul's avatar Yannick Jadoul Committed by GitHub
Browse files

Fix bug roundtripping datetime.time objects after midnight in eastern...

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
parent 1abc4a9d
......@@ -150,21 +150,28 @@ public:
// Lazy initialise the 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
// otherwise other code that is using localtime may break this (not just python code)
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,
localtime.tm_mon + 1,
localtime.tm_mday,
localtime.tm_hour,
localtime.tm_min,
localtime.tm_sec,
(duration_cast<us_t>(src.time_since_epoch() % seconds(1))).count());
us.count());
}
PYBIND11_TYPE_CASTER(type, _("datetime.datetime"));
};
......
......@@ -10,6 +10,7 @@
#include "pybind11_tests.h"
#include <pybind11/chrono.h>
#include <chrono>
TEST_SUBMODULE(chrono, m) {
using system_time = std::chrono::system_clock::time_point;
......
# -*- coding: utf-8 -*-
from pybind11_tests import chrono as m
import datetime
import pytest
import env # noqa: F401
def test_chrono_system_clock():
......@@ -70,8 +73,30 @@ def test_chrono_system_clock_roundtrip_date():
assert time2.microsecond == 0
def test_chrono_system_clock_roundtrip_time():
time1 = datetime.datetime.today().time()
SKIP_TZ_ENV_ON_WIN = pytest.mark.skipif(
"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
datetime2 = m.test_chrono2(time1)
......
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment