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

Add from-Python conversions from datetime.time to `absl::Duration`, `absl::Time`

For compatibility with Google-internal Clif_PyObjAs/From implementations.

This is to support the PyCLIF-pybind11 integration.

PiperOrigin-RevId: 526718225
parent 633f2917
......@@ -34,6 +34,9 @@
#include <pybind11/pybind11.h>
#include <pybind11/stl.h>
// Must NOT appear before at least one pybind11 include.
#include <datetime.h> // Python datetime builtin.
#include <cmath>
#include <cstdint>
#include <tuple>
......@@ -107,6 +110,14 @@ struct type_caster<absl::TimeZone> {
}
};
namespace internal {
inline void EnsurePyDateTime_IMPORT() {
if (PyDateTimeAPI == nullptr) {
PyDateTime_IMPORT;
}
}
} // namespace internal
// Convert between absl::Duration and python datetime.timedelta.
template <>
struct type_caster<absl::Duration> {
......@@ -117,16 +128,26 @@ struct type_caster<absl::Duration> {
// Conversion part 1 (Python->C++)
bool load(handle src, bool convert) {
// As early as possible to avoid mid-process surprises.
internal::EnsurePyDateTime_IMPORT();
if (!convert) {
return false;
}
if (PyFloat_Check(src.ptr())) {
value = absl::Seconds(src.cast<double>());
return true;
} else if (PyLong_Check(src.ptr())) {
}
if (PyLong_Check(src.ptr())) {
value = absl::Seconds(src.cast<int64_t>());
return true;
} else {
}
if (PyTime_Check(src.ptr())) {
value = absl::Hours(PyDateTime_TIME_GET_HOUR(src.ptr())) +
absl::Minutes(PyDateTime_TIME_GET_MINUTE(src.ptr())) +
absl::Seconds(PyDateTime_TIME_GET_SECOND(src.ptr())) +
absl::Microseconds(PyDateTime_TIME_GET_MICROSECOND(src.ptr()));
return true;
}
// Ensure that absl::Duration is converted from a Python
// datetime.timedelta.
if (!hasattr(src, "days") || !hasattr(src, "seconds") ||
......@@ -143,8 +164,6 @@ struct type_caster<absl::Duration> {
}
return true;
}
return false;
}
// Conversion part 2 (C++ -> Python)
static handle cast(const absl::Duration& src, return_value_policy, handle) {
......@@ -172,15 +191,32 @@ struct type_caster<absl::Time> {
// Conversion part 1 (Python->C++)
bool load(handle src, bool convert) {
// As early as possible to avoid mid-process surprises.
internal::EnsurePyDateTime_IMPORT();
if (convert) {
if (PyLong_Check(src.ptr())) {
value = absl::FromUnixSeconds(src.cast<int64_t>());
return true;
} else if (PyFloat_Check(src.ptr())) {
}
if (PyFloat_Check(src.ptr())) {
value = absl::time_internal::FromUnixDuration(absl::Seconds(
src.cast<double>()));
return true;
}
if (PyTime_Check(src.ptr())) {
// Adapted from absl/python/time.cc
// Copyright 2018 The Abseil Authors.
timeval tv{PyDateTime_TIME_GET_HOUR(src.ptr()) * 3600 +
PyDateTime_TIME_GET_MINUTE(src.ptr()) * 60 +
PyDateTime_TIME_GET_SECOND(src.ptr()),
PyDateTime_TIME_GET_MICROSECOND(src.ptr())};
value = absl::TimeFromTimeval(tv);
int utcoffset;
if (PyTzOffset(src.ptr(), &utcoffset)) {
value += absl::Seconds(utcoffset);
}
return true;
}
}
if (!hasattr(src, "year") || !hasattr(src, "month") ||
!hasattr(src, "day")) {
......@@ -233,6 +269,25 @@ struct type_caster<absl::Time> {
auto py_datetime = py_from_timestamp(as_seconds, "tz"_a = py_timezone);
return py_datetime.release();
}
private:
// Adapted from absl/python/time.cc
// Copyright 2018 The Abseil Authors.
bool PyTzOffset(PyObject* datetime, int* utcoffset) {
PyObject* offset = PyObject_CallMethod(datetime, "utcoffset", nullptr);
if (!offset || !PyDelta_Check(offset)) {
return false;
}
if (utcoffset) {
*utcoffset = PyDateTime_DELTA_GET_SECONDS(offset) +
PyDateTime_DELTA_GET_DAYS(offset) * 24 * 3600;
}
Py_DECREF(offset);
return true;
}
};
template <typename CivilTimeUnitType>
......
......@@ -98,6 +98,12 @@ bool CheckCivilYear(absl::CivilYear datetime, double secs) {
return datetime == MakeCivilYear(secs);
}
absl::Duration RoundtripDuration(const absl::Duration& duration) {
return duration;
}
absl::Time RoundtripTime(const absl::Time& time) { return time; }
absl::TimeZone RoundtripTimeZone(absl::TimeZone timezone) { return timezone; }
// Since a span does not own its elements, we must create a class to own them
......@@ -340,7 +346,8 @@ PYBIND11_MODULE(absl_example, m) {
m.def("absl_time_overloads", [](int) { return "int"; });
m.def("absl_time_overloads", [](float) { return "float"; });
// absl::TimeZone bindings
m.def("roundtrip_duration", &RoundtripDuration, arg("duration"));
m.def("roundtrip_time", &RoundtripTime, arg("time"));
m.def("roundtrip_timezone", &RoundtripTimeZone, arg("timezone"));
// absl::CivilTime bindings
......
......@@ -15,6 +15,12 @@ import numpy as np
from pybind11_abseil.tests import absl_example
def dt_time(h=0, m=0, s=0, micros=0, tzoff=0):
return datetime.time(h, m, s, micros).replace(
tzinfo=datetime.timezone(datetime.timedelta(seconds=tzoff))
)
class AbslTimeTest(parameterized.TestCase):
SECONDS_IN_DAY = 24 * 60 * 60
POSITIVE_SECS = 3 * SECONDS_IN_DAY + 2.5
......@@ -242,6 +248,29 @@ class AbslTimeTest(parameterized.TestCase):
with self.assertRaises(TypeError):
absl_example.roundtrip_timezone('Not a timezone')
@parameterized.parameters(
absl_example.roundtrip_duration, absl_example.roundtrip_time
)
def test_from_datetime_time(self, rt):
dt1 = rt(dt_time(h=13))
dt2 = rt(dt_time(h=15))
self.assertEqual((dt2 - dt1).seconds, 2 * 3600)
dt1 = rt(dt_time(m=12))
dt2 = rt(dt_time(m=16))
self.assertEqual((dt2 - dt1).seconds, 4 * 60)
dt1 = rt(dt_time(s=11))
dt2 = rt(dt_time(s=17))
self.assertEqual((dt2 - dt1).seconds, 6)
dt1 = rt(dt_time(micros=10))
dt2 = rt(dt_time(micros=18))
self.assertEqual((dt2 - dt1).microseconds, 8)
dt1 = rt(dt_time(tzoff=9))
dt2 = rt(dt_time(tzoff=19))
# Conversion from datetime.time to absl::Duration ignores tzinfo!
self.assertEqual(
(dt2 - dt1).seconds, 0 if rt is absl_example.roundtrip_duration else 10
)
def make_read_only_numpy_array():
values = np.zeros(5, dtype=np.int32)
......
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