Commit afa0489d by Ralf W. Grosse-Kunstleve Committed by Copybara-Service

Change pybind11_abseil `absl::Time` to-python conversion to return a datetime…

Change pybind11_abseil `absl::Time` to-python conversion to return a datetime object with UTC timezone.

The original motivation for this change was to achieve compatibility with absl/python behavior (currently only used Google-internally), but it is a good change in its own right: `absl::Time` does not carry any timezone information, using the local timezone in the to-python conversion is far more complicated than using the UTC timezone, in particular because

1. it makes the to-python conversion dependent on zoneinfo or equivalent.

2. potentially (and confusingly) 3 instead of 2 timezones can appear in a from-python - to-python conversion roundtrip, e.g. starting with a non-local timezone in Python, implicit UTC timezone in C++, then the local timezone when converted back to Python.

If this CL causes a breakage
----------------------------

The most likely root cause is a latent inconistency/bug in handling timezones, most notably due to a "naive" datetime object in the mix:

* https://docs.python.org/3/library/datetime.html#aware-and-naive-objects

In such a case, callers need to be updated to pass "aware" datetime objects instead. In the general case, this is the only way to ensure system-wide consistency. As a side-effect, such fixes are likely to prevent or resolve issues during DST transitions.

PiperOrigin-RevId: 538240189
parent 2bf606ce
......@@ -278,31 +278,25 @@ struct type_caster<absl::Time> {
// Conversion part 2 (C++ -> Python)
static handle cast(const absl::Time& src, return_value_policy, handle) {
// As early as possible to avoid mid-process surprises.
internal::EnsurePyDateTime_IMPORT();
// This function truncates fractional microseconds as the python datetime
// objects cannot support a resolution higher than this.
auto py_datetime_t = module::import("datetime").attr("datetime");
if (src == absl::InfiniteFuture()) {
// For compatibility with absl/python/time.cc
return replace_tzinfo_utc(py_datetime_t(9999, 12, 31, 23, 59, 59, 999999))
.release();
return PyDateTimeAPI->DateTime_FromDateAndTime(
9999, 12, 31, 23, 59, 59, 999999, PyDateTimeAPI->TimeZone_UTC,
PyDateTimeAPI->DateTimeType);
}
if (src == absl::InfinitePast()) {
// For compatibility with absl/python/time.cc
return replace_tzinfo_utc(py_datetime_t(1, 1, 1, 0, 0, 0, 0)).release();
}
auto py_from_timestamp = py_datetime_t.attr("fromtimestamp");
auto py_timezone_t = module::import("dateutil.tz").attr("gettz");
auto py_timezone = py_timezone_t(absl::LocalTimeZone().name());
double as_seconds = static_cast<double>(absl::ToUnixMicros(src)) / 1e6;
auto py_datetime = py_from_timestamp(as_seconds, "tz"_a = py_timezone);
return py_datetime.release();
}
private:
static object replace_tzinfo_utc(handle dt) {
auto py_timezone_utc =
module::import("datetime").attr("timezone").attr("utc");
return dt.attr("replace")(arg("tzinfo") = py_timezone_utc);
return PyDateTimeAPI->DateTime_FromDateAndTime(
1, 1, 1, 0, 0, 0, 0, PyDateTimeAPI->TimeZone_UTC,
PyDateTimeAPI->DateTimeType);
}
absl::Time::Breakdown t = src.In(absl::UTCTimeZone());
return PyDateTimeAPI->DateTime_FromDateAndTime(
t.year, t.month, t.day, t.hour, t.minute, t.second,
static_cast<int>(t.subsecond / absl::Microseconds(1)),
PyDateTimeAPI->TimeZone_UTC, PyDateTimeAPI->DateTimeType);
}
};
......
......@@ -133,6 +133,7 @@ class AbslTimeTest(parameterized.TestCase):
for time in (time_utc, time_local_aware, time_local_naive):
self.assertTrue(absl_example.check_datetime(time, secs))
self.assertEqual(int(absl_example.roundtrip_time(time).timestamp()), secs)
self.assertEqual(absl_example.roundtrip_time(time_utc), time_utc)
def test_pass_datetime_pre_unix_epoch(self):
dt = datetime.datetime(1969, 7, 16, 10, 56, 7, microsecond=140)
......
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