From 6ddfd1e090de6b5f1d1a14f7c51790275bff5818 Mon Sep 17 00:00:00 2001 From: Trent Houliston Date: Thu, 25 Aug 2016 19:59:41 +1000 Subject: [PATCH 01/10] Add in casts for c++11s chrono classes to pythons datetime --- include/pybind11/cast.h | 109 ++++++++++++++++++++++++++++++++++++++ include/pybind11/common.h | 1 + 2 files changed, 110 insertions(+) diff --git a/include/pybind11/cast.h b/include/pybind11/cast.h index b885298a7..4a0c703a5 100644 --- a/include/pybind11/cast.h +++ b/include/pybind11/cast.h @@ -472,6 +472,115 @@ public: PYBIND11_TYPE_CASTER(bool, _("bool")); }; +template class type_caster> { +public: + typedef std::chrono::duration type; + typedef std::chrono::duration> days; + + bool load(handle src, bool) { + using namespace std::chrono; + PyDateTime_IMPORT; + + if (!src) return false; + if (PyDelta_Check(src.ptr())) { + const PyDateTime_Delta* delta = reinterpret_cast(src.ptr()); + value = duration_cast>( + days(delta->days) + + seconds(delta->seconds) + + microseconds(delta->microseconds)); + return true; + } + else return false; + } + + static handle cast(const std::chrono::duration &src, return_value_policy /* policy */, handle /* parent */) { + using namespace std::chrono; + PyDateTime_IMPORT; + + int dd = duration_cast(src).count(); + int ss = duration_cast(src % days(1)).count(); + int us = duration_cast(src % seconds(1)).count(); + + return PyDelta_FromDSU(dd, ss, us); + } + PYBIND11_TYPE_CASTER(type, _("datetime.timedelta")); +}; + +template class type_caster> { +public: + typedef std::chrono::time_point type; + bool load(handle src, bool) { + using namespace std::chrono; + PyDateTime_IMPORT; + + if (!src) return false; + if (PyDateTime_Check(src.ptr())) { + std::tm cal; + cal.tm_sec = PyDateTime_DATE_GET_SECOND(src.ptr()); + cal.tm_min = PyDateTime_DATE_GET_MINUTE(src.ptr()); + cal.tm_hour = PyDateTime_DATE_GET_HOUR(src.ptr()); + cal.tm_mday = PyDateTime_GET_DAY(src.ptr()); + cal.tm_mon = PyDateTime_GET_MONTH(src.ptr()) - 1; + cal.tm_year = PyDateTime_GET_YEAR(src.ptr()); + cal.tm_isdst = -1; + + value = system_clock::from_time_t(mktime(&cal)); + return true; + } + else return false; + } + + static handle cast(const std::chrono::time_point &src, return_value_policy /* policy */, handle /* parent */) { + using namespace std::chrono; + PyDateTime_IMPORT; + + time_t tt = system_clock::to_time_t(src); + tm *ltime = localtime(&tt); // this function uses static memory so it's best + tm localtime = *ltime; // to copy it out asap just in case + + return PyDateTime_FromDateAndTime(localtime.tm_year, localtime.tm_mon + 1 + , localtime.tm_mday + , localtime.tm_hour + , localtime.tm_min + , localtime.tm_sec + , (duration_cast(src.time_since_epoch()) % seconds(1)).count()); + } + PYBIND11_TYPE_CASTER(type, _("datetime.datetime")); +}; + +template class type_caster> { +public: + typedef std::chrono::time_point type; + bool load(handle src, bool) { + using namespace std::chrono; + PyDateTime_IMPORT; + + if (!src) return false; + if (PyTime_Check(src.ptr())) { + value = type(duration_cast( + hours(PyDateTime_TIME_GET_HOUR(src.ptr())) + + minutes(PyDateTime_TIME_GET_MINUTE(src.ptr())) + + seconds(PyDateTime_TIME_GET_SECOND(src.ptr())) + + microseconds(PyDateTime_TIME_GET_MICROSECOND(src.ptr())) + )); + return true; + } + else return false; + } + + static handle cast(const std::chrono::time_point &src, return_value_policy /* policy */, handle /* parent */) { + using namespace std::chrono; + PyDateTime_IMPORT; + + Duration d = src.time_since_epoch(); + return PyTime_FromTime(duration_cast(d).count() + , duration_cast(d % hours(1)).count() + , duration_cast(d % minutes(1)).count() + , duration_cast(d % seconds(1)).count()); + } + PYBIND11_TYPE_CASTER(type, _("datetime.time")); +}; + template <> class type_caster { public: bool load(handle src, bool) { diff --git a/include/pybind11/common.h b/include/pybind11/common.h index fd65fe762..c1287dafa 100644 --- a/include/pybind11/common.h +++ b/include/pybind11/common.h @@ -46,6 +46,7 @@ #endif #include +#include #include #include From 352149e8929c1a2f0b13659dd46bf636cff0eb1d Mon Sep 17 00:00:00 2001 From: Trent Houliston Date: Thu, 25 Aug 2016 23:08:04 +1000 Subject: [PATCH 02/10] Refactor the chrono cast functions into chrono.h. Add unit tests and documentation for the chrono cast. --- CMakeLists.txt | 1 + docs/advanced.rst | 23 ++++++ docs/basics.rst | 4 ++ include/pybind11/cast.h | 109 ---------------------------- include/pybind11/chrono.h | 146 ++++++++++++++++++++++++++++++++++++++ tests/CMakeLists.txt | 1 + tests/test_chrono.cpp | 53 ++++++++++++++ tests/test_chrono.py | 102 ++++++++++++++++++++++++++ 8 files changed, 330 insertions(+), 109 deletions(-) create mode 100644 include/pybind11/chrono.h create mode 100644 tests/test_chrono.cpp create mode 100644 tests/test_chrono.py diff --git a/CMakeLists.txt b/CMakeLists.txt index 281de2480..6124cf052 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -161,6 +161,7 @@ if (PYBIND11_INSTALL) set(PYBIND11_HEADERS include/pybind11/attr.h include/pybind11/cast.h + include/pybind11/chrono.h include/pybind11/common.h include/pybind11/complex.h include/pybind11/descr.h diff --git a/docs/advanced.rst b/docs/advanced.rst index 1158f053e..ae43d5845 100644 --- a/docs/advanced.rst +++ b/docs/advanced.rst @@ -758,6 +758,29 @@ Please refer to the supplemental example for details. length queries (``__len__``), iterators (``__iter__``), the slicing protocol and other kinds of useful operations. +C++11 chrono datatypes +====================== +When including the additional header file :file:`pybind11/chrono.h` conversions from c++11 chrono datatypes +to corresponding python datetime objects are automatically enabled. +The following rules describe how the conversions are applied. + +Objects of type ``std::chrono::system_clock::time_point`` are converted into datetime.datetime objects. +These objects are those that specifically come from the system_clock as this is the only clock that measures wall time. + +Objects of type ``std::chrono::[other_clock]::time_point`` are converted into datetime.time objects. +These objects are those that come from all clocks that are not the system_clock (e.g. steady_clock). +Clocks other than the system_clock are not measured from wall date/time and instead have any start time +(often when the computer was turned on). +Therefore as these clocks can only measure time from an arbitrary start point they are represented as time without date. + +Objects of type ``std::chrono::duration`` are converted into datetime.timedelta objects. + +.. note:: + + Pythons datetime implementation is limited to microsecond precision. + The extra precision that c++11 clocks can have have (nanoseconds) will be lost upon conversion. + The rounding policy from c++ to python is via ``std::chrono::duration_cast<>`` (rounding towards 0 in microseconds). + Return value policies ===================== diff --git a/docs/basics.rst b/docs/basics.rst index 3e07e0e04..339d55955 100644 --- a/docs/basics.rst +++ b/docs/basics.rst @@ -297,6 +297,10 @@ as arguments and return values, refer to the section on binding :ref:`classes`. +---------------------------------+--------------------------+-------------------------------+ | ``std::function<...>`` | STL polymorphic function | :file:`pybind11/functional.h` | +---------------------------------+--------------------------+-------------------------------+ +| ``std::chrono::duration<...>`` | STL time duration | :file:`pybind11/chrono.h` | ++---------------------------------+--------------------------+-------------------------------+ +| ``std::chrono::time_point<...>``| STL date/time | :file:`pybind11/chrono.h` | ++---------------------------------+--------------------------+-------------------------------+ | ``Eigen::Matrix<...>`` | Eigen: dense matrix | :file:`pybind11/eigen.h` | +---------------------------------+--------------------------+-------------------------------+ | ``Eigen::Map<...>`` | Eigen: mapped memory | :file:`pybind11/eigen.h` | diff --git a/include/pybind11/cast.h b/include/pybind11/cast.h index 4a0c703a5..b885298a7 100644 --- a/include/pybind11/cast.h +++ b/include/pybind11/cast.h @@ -472,115 +472,6 @@ public: PYBIND11_TYPE_CASTER(bool, _("bool")); }; -template class type_caster> { -public: - typedef std::chrono::duration type; - typedef std::chrono::duration> days; - - bool load(handle src, bool) { - using namespace std::chrono; - PyDateTime_IMPORT; - - if (!src) return false; - if (PyDelta_Check(src.ptr())) { - const PyDateTime_Delta* delta = reinterpret_cast(src.ptr()); - value = duration_cast>( - days(delta->days) - + seconds(delta->seconds) - + microseconds(delta->microseconds)); - return true; - } - else return false; - } - - static handle cast(const std::chrono::duration &src, return_value_policy /* policy */, handle /* parent */) { - using namespace std::chrono; - PyDateTime_IMPORT; - - int dd = duration_cast(src).count(); - int ss = duration_cast(src % days(1)).count(); - int us = duration_cast(src % seconds(1)).count(); - - return PyDelta_FromDSU(dd, ss, us); - } - PYBIND11_TYPE_CASTER(type, _("datetime.timedelta")); -}; - -template class type_caster> { -public: - typedef std::chrono::time_point type; - bool load(handle src, bool) { - using namespace std::chrono; - PyDateTime_IMPORT; - - if (!src) return false; - if (PyDateTime_Check(src.ptr())) { - std::tm cal; - cal.tm_sec = PyDateTime_DATE_GET_SECOND(src.ptr()); - cal.tm_min = PyDateTime_DATE_GET_MINUTE(src.ptr()); - cal.tm_hour = PyDateTime_DATE_GET_HOUR(src.ptr()); - cal.tm_mday = PyDateTime_GET_DAY(src.ptr()); - cal.tm_mon = PyDateTime_GET_MONTH(src.ptr()) - 1; - cal.tm_year = PyDateTime_GET_YEAR(src.ptr()); - cal.tm_isdst = -1; - - value = system_clock::from_time_t(mktime(&cal)); - return true; - } - else return false; - } - - static handle cast(const std::chrono::time_point &src, return_value_policy /* policy */, handle /* parent */) { - using namespace std::chrono; - PyDateTime_IMPORT; - - time_t tt = system_clock::to_time_t(src); - tm *ltime = localtime(&tt); // this function uses static memory so it's best - tm localtime = *ltime; // to copy it out asap just in case - - return PyDateTime_FromDateAndTime(localtime.tm_year, localtime.tm_mon + 1 - , localtime.tm_mday - , localtime.tm_hour - , localtime.tm_min - , localtime.tm_sec - , (duration_cast(src.time_since_epoch()) % seconds(1)).count()); - } - PYBIND11_TYPE_CASTER(type, _("datetime.datetime")); -}; - -template class type_caster> { -public: - typedef std::chrono::time_point type; - bool load(handle src, bool) { - using namespace std::chrono; - PyDateTime_IMPORT; - - if (!src) return false; - if (PyTime_Check(src.ptr())) { - value = type(duration_cast( - hours(PyDateTime_TIME_GET_HOUR(src.ptr())) - + minutes(PyDateTime_TIME_GET_MINUTE(src.ptr())) - + seconds(PyDateTime_TIME_GET_SECOND(src.ptr())) - + microseconds(PyDateTime_TIME_GET_MICROSECOND(src.ptr())) - )); - return true; - } - else return false; - } - - static handle cast(const std::chrono::time_point &src, return_value_policy /* policy */, handle /* parent */) { - using namespace std::chrono; - PyDateTime_IMPORT; - - Duration d = src.time_since_epoch(); - return PyTime_FromTime(duration_cast(d).count() - , duration_cast(d % hours(1)).count() - , duration_cast(d % minutes(1)).count() - , duration_cast(d % seconds(1)).count()); - } - PYBIND11_TYPE_CASTER(type, _("datetime.time")); -}; - template <> class type_caster { public: bool load(handle src, bool) { diff --git a/include/pybind11/chrono.h b/include/pybind11/chrono.h new file mode 100644 index 000000000..d82f14309 --- /dev/null +++ b/include/pybind11/chrono.h @@ -0,0 +1,146 @@ +/* + pybind11/chrono.h: Transparent conversion between std::chrono and python's datetime + + Copyright (c) 2016 Trent Houliston and + Wenzel Jakob + + All rights reserved. Use of this source code is governed by a + BSD-style license that can be found in the LICENSE file. +*/ + +#pragma once + +#include "pybind11.h" +#include + +NAMESPACE_BEGIN(pybind11) +NAMESPACE_BEGIN(detail) + +template class type_caster> { +public: + typedef std::chrono::duration type; + typedef std::chrono::duration> days; + + bool load(handle src, bool) { + using namespace std::chrono; + PyDateTime_IMPORT; + + if (!src) return false; + if (PyDelta_Check(src.ptr())) { + // The accessor macros for timedelta exist in some versions of python but not others (e.g. Mac OSX default python) + // Therefore we are just doing what the macros do explicitly + const PyDateTime_Delta* delta = reinterpret_cast(src.ptr()); + value = duration_cast>( + days(delta->days) + + seconds(delta->seconds) + + microseconds(delta->microseconds)); + return true; + } + else return false; + } + + static handle cast(const std::chrono::duration &src, return_value_policy /* policy */, handle /* parent */) { + using namespace std::chrono; + PyDateTime_IMPORT; + + // Declare these special duration types so the conversions happen with the correct primitive types (int) + typedef duration> dd_t; + typedef duration> ss_t; + typedef duration us_t; + + return PyDelta_FromDSU( + duration_cast(src).count() + , duration_cast(src % days(1)).count() + , duration_cast(src % seconds(1)).count()); + } + PYBIND11_TYPE_CASTER(type, _("datetime.timedelta")); +}; + +template class type_caster> { +public: + typedef std::chrono::time_point type; + bool load(handle src, bool) { + using namespace std::chrono; + PyDateTime_IMPORT; + + if (!src) return false; + if (PyDateTime_Check(src.ptr())) { + std::tm cal; + cal.tm_sec = PyDateTime_DATE_GET_SECOND(src.ptr()); + cal.tm_min = PyDateTime_DATE_GET_MINUTE(src.ptr()); + cal.tm_hour = PyDateTime_DATE_GET_HOUR(src.ptr()); + cal.tm_mday = PyDateTime_GET_DAY(src.ptr()); + cal.tm_mon = PyDateTime_GET_MONTH(src.ptr()) - 1; + cal.tm_year = PyDateTime_GET_YEAR(src.ptr()) - 1900; + cal.tm_isdst = -1; + + value = system_clock::from_time_t(mktime(&cal)) + microseconds(PyDateTime_DATE_GET_MICROSECOND(src.ptr())); + return true; + } + else return false; + } + + static handle cast(const std::chrono::time_point &src, return_value_policy /* policy */, handle /* parent */) { + using namespace std::chrono; + PyDateTime_IMPORT; + + time_t tt = system_clock::to_time_t(src); + // this function uses static memory so it's best to copy it out asap just in case + tm *ltime = localtime(&tt); + tm localtime = *ltime; + + // Declare these special duration types so the conversions happen with the correct primitive types (int) + using us_t = duration; + + 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(src.time_since_epoch() % seconds(1))).count()); + } + PYBIND11_TYPE_CASTER(type, _("datetime.datetime")); +}; + +template class type_caster> { +public: + typedef std::chrono::time_point type; + bool load(handle src, bool) { + using namespace std::chrono; + PyDateTime_IMPORT; + + if (!src) return false; + if (PyTime_Check(src.ptr())) { + value = type(duration_cast( + hours(PyDateTime_TIME_GET_HOUR(src.ptr())) + + minutes(PyDateTime_TIME_GET_MINUTE(src.ptr())) + + seconds(PyDateTime_TIME_GET_SECOND(src.ptr())) + + microseconds(PyDateTime_TIME_GET_MICROSECOND(src.ptr())) + )); + return true; + } + else return false; + } + + static handle cast(const std::chrono::time_point &src, return_value_policy /* policy */, handle /* parent */) { + using namespace std::chrono; + PyDateTime_IMPORT; + + // Declare these special duration types so the conversions happen with the correct primitive types (int) + typedef duration> hh_t; + typedef duration> mm_t; + typedef duration> ss_t; + typedef duration us_t; + + Duration d = src.time_since_epoch(); + return PyTime_FromTime(duration_cast(d).count() + , duration_cast(d % hours(1)).count() + , duration_cast(d % minutes(1)).count() + , duration_cast(d % seconds(1)).count()); + } + PYBIND11_TYPE_CASTER(type, _("datetime.time")); +}; + +NAMESPACE_END(detail) +NAMESPACE_END(pybind11) diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index 3be8cf898..6274ca139 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -7,6 +7,7 @@ set(PYBIND11_TEST_FILES test_alias_initialization.cpp test_buffers.cpp test_callbacks.cpp + test_chrono.cpp test_class_args.cpp test_constants_and_functions.cpp test_eigen.cpp diff --git a/tests/test_chrono.cpp b/tests/test_chrono.cpp new file mode 100644 index 000000000..e07192404 --- /dev/null +++ b/tests/test_chrono.cpp @@ -0,0 +1,53 @@ +/* + tests/test_chrono.cpp -- test conversions to/from std::chrono types + + Copyright (c) 2016 Trent Houliston and + Wenzel Jakob + + All rights reserved. Use of this source code is governed by a + BSD-style license that can be found in the LICENSE file. +*/ + + +#include "pybind11_tests.h" +#include "constructor_stats.h" +#include + +// Return the current time off the wall clock +std::chrono::system_clock::time_point test_chrono1() { + return std::chrono::system_clock::now(); +} + +// Round trip the passed in system clock time +std::chrono::system_clock::time_point test_chrono2(std::chrono::system_clock::time_point t) { + return t; +} + +// Round trip the passed in duration +std::chrono::system_clock::duration test_chrono3(std::chrono::system_clock::duration d) { + return d; +} + +// Difference between two passed in time_points +std::chrono::system_clock::duration test_chrono4(std::chrono::system_clock::time_point a, std::chrono::system_clock::time_point b) { + return a - b; +} + +// Return the current time off the steady_clock +std::chrono::steady_clock::time_point test_chrono5() { + return std::chrono::steady_clock::now(); +} + +// Round trip a steady clock timepoint +std::chrono::steady_clock::time_point test_chrono6(std::chrono::steady_clock::time_point t) { + return t; +} + +test_initializer chrono([] (py::module &m) { + m.def("test_chrono1", &test_chrono1); + m.def("test_chrono2", &test_chrono2); + m.def("test_chrono3", &test_chrono3); + m.def("test_chrono4", &test_chrono4); + m.def("test_chrono5", &test_chrono5); + m.def("test_chrono6", &test_chrono6); +}); diff --git a/tests/test_chrono.py b/tests/test_chrono.py new file mode 100644 index 000000000..aa4748092 --- /dev/null +++ b/tests/test_chrono.py @@ -0,0 +1,102 @@ + + +def test_chrono_system_clock(): + from pybind11_tests import test_chrono1 + import datetime + + # Get the time from both c++ and datetime + date1 = test_chrono1() + date2 = datetime.datetime.today() + + # The returned value should be a datetime + assert isinstance(date1, datetime.datetime) + + # The numbers should vary by a very small amount (time it took to execute) + diff = abs(date1 - date2) + + # There should never be a days/seconds difference + assert diff.days == 0 + assert diff.seconds == 0 + + # 500 microseconds is a very long time to execute this + assert diff.microseconds < 500 + + +def test_chrono_system_clock_roundtrip(): + from pybind11_tests import test_chrono2 + import datetime + + date1 = datetime.datetime.today() + + # Roundtrip the time + date2 = test_chrono2(date1) + + # The returned value should be a datetime + assert isinstance(date2, datetime.datetime) + + # They should be identical (no information lost on roundtrip) + diff = abs(date1 - date2) + assert diff.days == 0 + assert diff.seconds == 0 + assert diff.microseconds == 0 + + +def test_chrono_duration_roundtrip(): + from pybind11_tests import test_chrono3 + import datetime + + # Get the difference betwen two times (a timedelta) + date1 = datetime.datetime.today() + date2 = datetime.datetime.today() + diff = date2 - date1 + + # Make sure this is a timedelta + assert isinstance(diff, datetime.timedelta) + + cpp_diff = test_chrono3(diff) + + assert cpp_diff.days == diff.days + assert cpp_diff.seconds == diff.seconds + assert cpp_diff.microseconds == diff.microseconds + + +def test_chrono_duration_subtraction_equivalence(): + from pybind11_tests import test_chrono4 + import datetime + + date1 = datetime.datetime.today() + date2 = datetime.datetime.today() + + diff = date2 - date1 + cpp_diff = test_chrono4(date2, date1) + + assert cpp_diff.days == diff.days + assert cpp_diff.seconds == diff.seconds + assert cpp_diff.microseconds == diff.microseconds + + +def test_chrono_steady_clock(): + from pybind11_tests import test_chrono5 + import datetime + + time1 = test_chrono5() + time2 = test_chrono5() + + assert isinstance(time1, datetime.time) + assert isinstance(time2, datetime.time) + + +def test_chrono_steady_clock_roundtrip(): + from pybind11_tests import test_chrono6 + import datetime + + time1 = datetime.time(second=10, microsecond=100) + time2 = test_chrono6(time1) + + assert isinstance(time2, datetime.time) + + # They should be identical (no information lost on roundtrip) + assert time1.hour == time2.hour + assert time1.minute == time2.minute + assert time1.second == time2.second + assert time1.microsecond == time2.microsecond From 8fe2fa7ebae1c34950f5a372a8f095f502053618 Mon Sep 17 00:00:00 2001 From: Trent Houliston Date: Thu, 25 Aug 2016 23:25:48 +1000 Subject: [PATCH 03/10] Increase the amount of time to execute the functions to 50ms --- tests/test_chrono.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/test_chrono.py b/tests/test_chrono.py index aa4748092..1f112131d 100644 --- a/tests/test_chrono.py +++ b/tests/test_chrono.py @@ -18,8 +18,8 @@ def test_chrono_system_clock(): assert diff.days == 0 assert diff.seconds == 0 - # 500 microseconds is a very long time to execute this - assert diff.microseconds < 500 + # 50 milliseconds is a very long time to execute this + assert diff.microseconds < 50000 def test_chrono_system_clock_roundtrip(): From 0ee97dd6d08bc7de9da612876947012d20b4666c Mon Sep 17 00:00:00 2001 From: Trent Houliston Date: Thu, 25 Aug 2016 23:44:18 +1000 Subject: [PATCH 04/10] Only import PyDateTime if we have to --- include/pybind11/chrono.h | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/include/pybind11/chrono.h b/include/pybind11/chrono.h index d82f14309..e60db3fa0 100644 --- a/include/pybind11/chrono.h +++ b/include/pybind11/chrono.h @@ -23,7 +23,7 @@ public: bool load(handle src, bool) { using namespace std::chrono; - PyDateTime_IMPORT; + if(!PyDateTimeAPI) { PyDateTime_IMPORT; } if (!src) return false; if (PyDelta_Check(src.ptr())) { @@ -41,7 +41,7 @@ public: static handle cast(const std::chrono::duration &src, return_value_policy /* policy */, handle /* parent */) { using namespace std::chrono; - PyDateTime_IMPORT; + if(!PyDateTimeAPI) { PyDateTime_IMPORT; } // Declare these special duration types so the conversions happen with the correct primitive types (int) typedef duration> dd_t; @@ -61,7 +61,7 @@ public: typedef std::chrono::time_point type; bool load(handle src, bool) { using namespace std::chrono; - PyDateTime_IMPORT; + if(!PyDateTimeAPI) { PyDateTime_IMPORT; } if (!src) return false; if (PyDateTime_Check(src.ptr())) { @@ -82,7 +82,7 @@ public: static handle cast(const std::chrono::time_point &src, return_value_policy /* policy */, handle /* parent */) { using namespace std::chrono; - PyDateTime_IMPORT; + if(!PyDateTimeAPI) { PyDateTime_IMPORT; } time_t tt = system_clock::to_time_t(src); // this function uses static memory so it's best to copy it out asap just in case @@ -108,7 +108,7 @@ public: typedef std::chrono::time_point type; bool load(handle src, bool) { using namespace std::chrono; - PyDateTime_IMPORT; + if(!PyDateTimeAPI) { PyDateTime_IMPORT; } if (!src) return false; if (PyTime_Check(src.ptr())) { @@ -125,7 +125,7 @@ public: static handle cast(const std::chrono::time_point &src, return_value_policy /* policy */, handle /* parent */) { using namespace std::chrono; - PyDateTime_IMPORT; + if(!PyDateTimeAPI) { PyDateTime_IMPORT; } // Declare these special duration types so the conversions happen with the correct primitive types (int) typedef duration> hh_t; From 207d0da31cecbbe2ba0ee59714defd25013f350d Mon Sep 17 00:00:00 2001 From: Trent Houliston Date: Fri, 26 Aug 2016 15:40:13 +1000 Subject: [PATCH 05/10] Move the python datetime header into the chrono header --- include/pybind11/chrono.h | 1 + include/pybind11/common.h | 1 - 2 files changed, 1 insertion(+), 1 deletion(-) diff --git a/include/pybind11/chrono.h b/include/pybind11/chrono.h index e60db3fa0..4eaed2a9d 100644 --- a/include/pybind11/chrono.h +++ b/include/pybind11/chrono.h @@ -12,6 +12,7 @@ #include "pybind11.h" #include +#include NAMESPACE_BEGIN(pybind11) NAMESPACE_BEGIN(detail) diff --git a/include/pybind11/common.h b/include/pybind11/common.h index c1287dafa..fd65fe762 100644 --- a/include/pybind11/common.h +++ b/include/pybind11/common.h @@ -46,7 +46,6 @@ #endif #include -#include #include #include From 2f597687e719a9a0321e694036023598e5528da8 Mon Sep 17 00:00:00 2001 From: Trent Houliston Date: Tue, 13 Sep 2016 20:40:28 +1000 Subject: [PATCH 06/10] Changed non system clocks to be time deltas Allowed durations and non system clocks to be set from floats. --- docs/advanced.rst | 14 +++++--- include/pybind11/chrono.h | 69 +++++++++++++++++++++++++++------------ tests/test_chrono.cpp | 6 ++++ tests/test_chrono.py | 28 +++++++++++----- 4 files changed, 85 insertions(+), 32 deletions(-) diff --git a/docs/advanced.rst b/docs/advanced.rst index ae43d5845..abb9ec0e3 100644 --- a/docs/advanced.rst +++ b/docs/advanced.rst @@ -764,19 +764,25 @@ When including the additional header file :file:`pybind11/chrono.h` conversions to corresponding python datetime objects are automatically enabled. The following rules describe how the conversions are applied. -Objects of type ``std::chrono::system_clock::time_point`` are converted into datetime.datetime objects. +When passed to python objects of type ``std::chrono::system_clock::time_point`` are converted into datetime.datetime objects. These objects are those that specifically come from the system_clock as this is the only clock that measures wall time. -Objects of type ``std::chrono::[other_clock]::time_point`` are converted into datetime.time objects. +When passed to python of type ``std::chrono::[other_clock]::time_point`` are converted into datetime.timedelta objects. These objects are those that come from all clocks that are not the system_clock (e.g. steady_clock). Clocks other than the system_clock are not measured from wall date/time and instead have any start time (often when the computer was turned on). -Therefore as these clocks can only measure time from an arbitrary start point they are represented as time without date. +Therefore as these clocks can only measure time from an arbitrary start point they are represented as timedelta from this start point. -Objects of type ``std::chrono::duration`` are converted into datetime.timedelta objects. +When passed to python of type ``std::chrono::duration`` are converted into datetime.timedelta objects. + +When python objects are passed to c++ for the case of non system clocks and durations instances of both datetime.timedelta +and float are accepted. The float arguments are interpreted as a number of seconds since the epoch. .. note:: + Other clocks may be the same as system_clock. For example on many platforms std::high_resolution_clock is the same as system_clock. + Because of this if you are converting a timepoint from one of these clocks they may appear to python as a datetime.datetime object. + Pythons datetime implementation is limited to microsecond precision. The extra precision that c++11 clocks can have have (nanoseconds) will be lost upon conversion. The rounding policy from c++ to python is via ``std::chrono::duration_cast<>`` (rounding towards 0 in microseconds). diff --git a/include/pybind11/chrono.h b/include/pybind11/chrono.h index 4eaed2a9d..58ecb06d3 100644 --- a/include/pybind11/chrono.h +++ b/include/pybind11/chrono.h @@ -24,9 +24,12 @@ public: bool load(handle src, bool) { using namespace std::chrono; + + // Lazy initialise the PyDateTime import if(!PyDateTimeAPI) { PyDateTime_IMPORT; } if (!src) return false; + // If they have passed us a datetime.delta object if (PyDelta_Check(src.ptr())) { // The accessor macros for timedelta exist in some versions of python but not others (e.g. Mac OSX default python) // Therefore we are just doing what the macros do explicitly @@ -37,6 +40,13 @@ public: + microseconds(delta->microseconds)); return true; } + // If they have passed us a float we can assume it is seconds and convert + else if (PyFloat_Check(src.ptr())) { + double val = PyFloat_AsDouble(src.ptr()); + // Multiply by the reciprocal of the ratio and round + value = type(std::lround(val * type::period::den / type::period::num)); + return true; + } else return false; } @@ -45,9 +55,9 @@ public: if(!PyDateTimeAPI) { PyDateTime_IMPORT; } // Declare these special duration types so the conversions happen with the correct primitive types (int) - typedef duration> dd_t; - typedef duration> ss_t; - typedef duration us_t; + using dd_t = duration>; + using ss_t = duration>; + using us_t = duration; return PyDelta_FromDSU( duration_cast(src).count() @@ -57,11 +67,14 @@ public: PYBIND11_TYPE_CASTER(type, _("datetime.timedelta")); }; +// This is for casting times on the system clock into datetime.datetime instances template class type_caster> { public: typedef std::chrono::time_point type; bool load(handle src, bool) { using namespace std::chrono; + + // Lazy initialise the PyDateTime import if(!PyDateTimeAPI) { PyDateTime_IMPORT; } if (!src) return false; @@ -83,6 +96,8 @@ public: static handle cast(const std::chrono::time_point &src, return_value_policy /* policy */, handle /* parent */) { using namespace std::chrono; + + // Lazy initialise the PyDateTime import if(!PyDateTimeAPI) { PyDateTime_IMPORT; } time_t tt = system_clock::to_time_t(src); @@ -104,21 +119,33 @@ public: PYBIND11_TYPE_CASTER(type, _("datetime.datetime")); }; +// Other clocks that are not the system clock are not measured as datetime.datetime objects +// since they are not measured on calendar time. So instead we just make them timedeltas +// Or if they have passed us a time as a float we convert that template class type_caster> { public: typedef std::chrono::time_point type; + typedef std::chrono::duration> days; + bool load(handle src, bool) { using namespace std::chrono; if(!PyDateTimeAPI) { PyDateTime_IMPORT; } - if (!src) return false; - if (PyTime_Check(src.ptr())) { - value = type(duration_cast( - hours(PyDateTime_TIME_GET_HOUR(src.ptr())) - + minutes(PyDateTime_TIME_GET_MINUTE(src.ptr())) - + seconds(PyDateTime_TIME_GET_SECOND(src.ptr())) - + microseconds(PyDateTime_TIME_GET_MICROSECOND(src.ptr())) - )); + // If they have passed us a datetime.delta object + if (PyDelta_Check(src.ptr())) { + // The accessor macros for timedelta exist in some versions of python but not others (e.g. Mac OSX default python) + // Therefore we are just doing what the macros do explicitly + const PyDateTime_Delta* delta = reinterpret_cast(src.ptr()); + value = time_point( + days(delta->days) + + seconds(delta->seconds) + + microseconds(delta->microseconds)); + return true; + } + // If they have passed us a float we can assume it is seconds and convert + else if (PyFloat_Check(src.ptr())) { + double val = PyFloat_AsDouble(src.ptr()); + value = time_point(Duration(std::lround((val / Clock::period::num) * Clock::period::den))); return true; } else return false; @@ -126,21 +153,23 @@ public: static handle cast(const std::chrono::time_point &src, return_value_policy /* policy */, handle /* parent */) { using namespace std::chrono; + + // Lazy initialise the PyDateTime import if(!PyDateTimeAPI) { PyDateTime_IMPORT; } // Declare these special duration types so the conversions happen with the correct primitive types (int) - typedef duration> hh_t; - typedef duration> mm_t; - typedef duration> ss_t; - typedef duration us_t; + using dd_t = duration>; + using ss_t = duration>; + using us_t = duration; Duration d = src.time_since_epoch(); - return PyTime_FromTime(duration_cast(d).count() - , duration_cast(d % hours(1)).count() - , duration_cast(d % minutes(1)).count() - , duration_cast(d % seconds(1)).count()); + + return PyDelta_FromDSU( + duration_cast(d).count() + , duration_cast(d % days(1)).count() + , duration_cast(d % seconds(1)).count()); } - PYBIND11_TYPE_CASTER(type, _("datetime.time")); + PYBIND11_TYPE_CASTER(type, _("datetime.timedelta")); }; NAMESPACE_END(detail) diff --git a/tests/test_chrono.cpp b/tests/test_chrono.cpp index e07192404..b86f57adf 100644 --- a/tests/test_chrono.cpp +++ b/tests/test_chrono.cpp @@ -43,6 +43,11 @@ std::chrono::steady_clock::time_point test_chrono6(std::chrono::steady_clock::ti return t; } +// Roundtrip a duration in microseconds from a float argument +std::chrono::microseconds test_chrono7(std::chrono::microseconds t) { + return t; +} + test_initializer chrono([] (py::module &m) { m.def("test_chrono1", &test_chrono1); m.def("test_chrono2", &test_chrono2); @@ -50,4 +55,5 @@ test_initializer chrono([] (py::module &m) { m.def("test_chrono4", &test_chrono4); m.def("test_chrono5", &test_chrono5); m.def("test_chrono6", &test_chrono6); + m.def("test_chrono7", &test_chrono7); }); diff --git a/tests/test_chrono.py b/tests/test_chrono.py index 1f112131d..0253550d9 100644 --- a/tests/test_chrono.py +++ b/tests/test_chrono.py @@ -82,21 +82,33 @@ def test_chrono_steady_clock(): time1 = test_chrono5() time2 = test_chrono5() - assert isinstance(time1, datetime.time) - assert isinstance(time2, datetime.time) + assert isinstance(time1, datetime.timedelta) + assert isinstance(time2, datetime.timedelta) def test_chrono_steady_clock_roundtrip(): from pybind11_tests import test_chrono6 import datetime - time1 = datetime.time(second=10, microsecond=100) + time1 = datetime.timedelta(days=10, seconds=10, microseconds=100) time2 = test_chrono6(time1) - assert isinstance(time2, datetime.time) + assert isinstance(time2, datetime.timedelta) # They should be identical (no information lost on roundtrip) - assert time1.hour == time2.hour - assert time1.minute == time2.minute - assert time1.second == time2.second - assert time1.microsecond == time2.microsecond + assert time1.days == time2.days + assert time1.seconds == time2.seconds + assert time1.microseconds == time2.microseconds + + +def test_floating_point_duration(): + from pybind11_tests import test_chrono7 + import datetime + + # Test using 35.525123 seconds as an example floating point number in seconds + time = test_chrono7(35.525123) + + assert isinstance(time, datetime.timedelta) + + assert time.seconds == 35 + assert time.microseconds == 525123 From ad3bb9bbab1944408b0d013d2d04984f29daaa2b Mon Sep 17 00:00:00 2001 From: Trent Houliston Date: Wed, 14 Sep 2016 19:27:53 +1000 Subject: [PATCH 07/10] Include the cmath header for std::lround. Fix spaces before parens for style guide. --- include/pybind11/chrono.h | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/include/pybind11/chrono.h b/include/pybind11/chrono.h index 58ecb06d3..110031e29 100644 --- a/include/pybind11/chrono.h +++ b/include/pybind11/chrono.h @@ -11,6 +11,7 @@ #pragma once #include "pybind11.h" +#include #include #include @@ -26,7 +27,7 @@ public: using namespace std::chrono; // Lazy initialise the PyDateTime import - if(!PyDateTimeAPI) { PyDateTime_IMPORT; } + if (!PyDateTimeAPI) { PyDateTime_IMPORT; } if (!src) return false; // If they have passed us a datetime.delta object @@ -52,7 +53,7 @@ public: static handle cast(const std::chrono::duration &src, return_value_policy /* policy */, handle /* parent */) { using namespace std::chrono; - if(!PyDateTimeAPI) { PyDateTime_IMPORT; } + if (!PyDateTimeAPI) { PyDateTime_IMPORT; } // Declare these special duration types so the conversions happen with the correct primitive types (int) using dd_t = duration>; @@ -75,7 +76,7 @@ public: using namespace std::chrono; // Lazy initialise the PyDateTime import - if(!PyDateTimeAPI) { PyDateTime_IMPORT; } + if (!PyDateTimeAPI) { PyDateTime_IMPORT; } if (!src) return false; if (PyDateTime_Check(src.ptr())) { @@ -98,7 +99,7 @@ public: using namespace std::chrono; // Lazy initialise the PyDateTime import - if(!PyDateTimeAPI) { PyDateTime_IMPORT; } + if (!PyDateTimeAPI) { PyDateTime_IMPORT; } time_t tt = system_clock::to_time_t(src); // this function uses static memory so it's best to copy it out asap just in case @@ -129,7 +130,7 @@ public: bool load(handle src, bool) { using namespace std::chrono; - if(!PyDateTimeAPI) { PyDateTime_IMPORT; } + if (!PyDateTimeAPI) { PyDateTime_IMPORT; } // If they have passed us a datetime.delta object if (PyDelta_Check(src.ptr())) { @@ -155,7 +156,7 @@ public: using namespace std::chrono; // Lazy initialise the PyDateTime import - if(!PyDateTimeAPI) { PyDateTime_IMPORT; } + if (!PyDateTimeAPI) { PyDateTime_IMPORT; } // Declare these special duration types so the conversions happen with the correct primitive types (int) using dd_t = duration>; From 253e41ccad7216a9a656a82d101b98b40b112e16 Mon Sep 17 00:00:00 2001 From: Trent Houliston Date: Wed, 28 Sep 2016 00:59:21 +1000 Subject: [PATCH 08/10] Relax constraints on testing to ensure they work in all cases. --- tests/test_chrono.py | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/tests/test_chrono.py b/tests/test_chrono.py index 0253550d9..b1d4dc72f 100644 --- a/tests/test_chrono.py +++ b/tests/test_chrono.py @@ -18,8 +18,10 @@ def test_chrono_system_clock(): assert diff.days == 0 assert diff.seconds == 0 - # 50 milliseconds is a very long time to execute this - assert diff.microseconds < 50000 + # We test that no more than about 0.5 seconds passes here + # This makes sure that the dates created are very close to the same + # but if the testing system is incredibly overloaded this should still pass + assert diff.microseconds < 500000 def test_chrono_system_clock_roundtrip(): @@ -111,4 +113,4 @@ def test_floating_point_duration(): assert isinstance(time, datetime.timedelta) assert time.seconds == 35 - assert time.microseconds == 525123 + assert 525122 <= time.microseconds <= 525123 From a3603e6df0bc72b652ca76eeec8d0fdccebf15da Mon Sep 17 00:00:00 2001 From: Trent Houliston Date: Wed, 28 Sep 2016 01:01:44 +1000 Subject: [PATCH 09/10] Simplify redundant code, conform to style suggestions, improve logic --- include/pybind11/chrono.h | 139 +++++++++++++++++--------------------- 1 file changed, 61 insertions(+), 78 deletions(-) diff --git a/include/pybind11/chrono.h b/include/pybind11/chrono.h index 110031e29..2b37f56f1 100644 --- a/include/pybind11/chrono.h +++ b/include/pybind11/chrono.h @@ -12,16 +12,30 @@ #include "pybind11.h" #include +#include #include #include +// Backport the PyDateTime_DELTA functions from Python3.3 if required +#ifndef PyDateTime_DELTA_GET_DAYS +#define PyDateTime_DELTA_GET_DAYS(o) (((PyDateTime_Delta*)o)->days) +#endif +#ifndef PyDateTime_DELTA_GET_SECONDS +#define PyDateTime_DELTA_GET_SECONDS(o) (((PyDateTime_Delta*)o)->seconds) +#endif +#ifndef PyDateTime_DELTA_GET_MICROSECONDS +#define PyDateTime_DELTA_GET_MICROSECONDS(o) (((PyDateTime_Delta*)o)->microseconds) +#endif + NAMESPACE_BEGIN(pybind11) NAMESPACE_BEGIN(detail) -template class type_caster> { +template class duration_caster { public: - typedef std::chrono::duration type; - typedef std::chrono::duration> days; + typedef typename type::rep rep; + typedef typename type::period period; + + typedef std::chrono::duration> days; bool load(handle src, bool) { using namespace std::chrono; @@ -30,29 +44,40 @@ public: if (!PyDateTimeAPI) { PyDateTime_IMPORT; } if (!src) return false; - // If they have passed us a datetime.delta object + // If invoked with datetime.delta object if (PyDelta_Check(src.ptr())) { - // The accessor macros for timedelta exist in some versions of python but not others (e.g. Mac OSX default python) - // Therefore we are just doing what the macros do explicitly - const PyDateTime_Delta* delta = reinterpret_cast(src.ptr()); - value = duration_cast>( - days(delta->days) - + seconds(delta->seconds) - + microseconds(delta->microseconds)); + value = type(duration_cast>( + days(PyDateTime_DELTA_GET_DAYS(src.ptr())) + + seconds(PyDateTime_DELTA_GET_SECONDS(src.ptr())) + + microseconds(PyDateTime_DELTA_GET_MICROSECONDS(src.ptr())))); return true; } - // If they have passed us a float we can assume it is seconds and convert + // If invoked with a float we assume it is seconds and convert else if (PyFloat_Check(src.ptr())) { - double val = PyFloat_AsDouble(src.ptr()); - // Multiply by the reciprocal of the ratio and round - value = type(std::lround(val * type::period::den / type::period::num)); + value = type(duration_cast>(duration(PyFloat_AsDouble(src.ptr())))); return true; } else return false; } - static handle cast(const std::chrono::duration &src, return_value_policy /* policy */, handle /* parent */) { + // If this is a duration just return it back + static const std::chrono::duration& get_duration(const std::chrono::duration &src) { + return src; + } + + // If this is a time_point get the time_since_epoch + template static std::chrono::duration get_duration(const std::chrono::time_point> &src) { + return src.time_since_epoch(); + } + + static handle cast(const type &src, return_value_policy /* policy */, handle /* parent */) { using namespace std::chrono; + + // Use overloaded function to get our duration from our source + // Works out if it is a duration or time_point and get the duration + auto d = get_duration(src); + + // Lazy initialise the PyDateTime import if (!PyDateTimeAPI) { PyDateTime_IMPORT; } // Declare these special duration types so the conversions happen with the correct primitive types (int) @@ -60,11 +85,11 @@ public: using ss_t = duration>; using us_t = duration; - return PyDelta_FromDSU( - duration_cast(src).count() - , duration_cast(src % days(1)).count() - , duration_cast(src % seconds(1)).count()); + return PyDelta_FromDSU(duration_cast(d).count(), + duration_cast(d % days(1)).count(), + duration_cast(d % seconds(1)).count()); } + PYBIND11_TYPE_CASTER(type, _("datetime.timedelta")); }; @@ -89,7 +114,7 @@ public: cal.tm_year = PyDateTime_GET_YEAR(src.ptr()) - 1900; cal.tm_isdst = -1; - value = system_clock::from_time_t(mktime(&cal)) + microseconds(PyDateTime_DATE_GET_MICROSECOND(src.ptr())); + value = system_clock::from_time_t(std::mktime(&cal)) + microseconds(PyDateTime_DATE_GET_MICROSECOND(src.ptr())); return true; } else return false; @@ -101,21 +126,21 @@ public: // Lazy initialise the PyDateTime import if (!PyDateTimeAPI) { PyDateTime_IMPORT; } - time_t tt = system_clock::to_time_t(src); + std::time_t tt = system_clock::to_time_t(src); // this function uses static memory so it's best to copy it out asap just in case - tm *ltime = localtime(&tt); - tm localtime = *ltime; + // 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; - 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(src.time_since_epoch() % seconds(1))).count()); + 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(src.time_since_epoch() % seconds(1))).count()); } PYBIND11_TYPE_CASTER(type, _("datetime.datetime")); }; @@ -123,54 +148,12 @@ public: // Other clocks that are not the system clock are not measured as datetime.datetime objects // since they are not measured on calendar time. So instead we just make them timedeltas // Or if they have passed us a time as a float we convert that -template class type_caster> { -public: - typedef std::chrono::time_point type; - typedef std::chrono::duration> days; +template class type_caster> +: public duration_caster> { +}; - bool load(handle src, bool) { - using namespace std::chrono; - if (!PyDateTimeAPI) { PyDateTime_IMPORT; } - - // If they have passed us a datetime.delta object - if (PyDelta_Check(src.ptr())) { - // The accessor macros for timedelta exist in some versions of python but not others (e.g. Mac OSX default python) - // Therefore we are just doing what the macros do explicitly - const PyDateTime_Delta* delta = reinterpret_cast(src.ptr()); - value = time_point( - days(delta->days) - + seconds(delta->seconds) - + microseconds(delta->microseconds)); - return true; - } - // If they have passed us a float we can assume it is seconds and convert - else if (PyFloat_Check(src.ptr())) { - double val = PyFloat_AsDouble(src.ptr()); - value = time_point(Duration(std::lround((val / Clock::period::num) * Clock::period::den))); - return true; - } - else return false; - } - - static handle cast(const std::chrono::time_point &src, return_value_policy /* policy */, handle /* parent */) { - using namespace std::chrono; - - // Lazy initialise the PyDateTime import - if (!PyDateTimeAPI) { PyDateTime_IMPORT; } - - // Declare these special duration types so the conversions happen with the correct primitive types (int) - using dd_t = duration>; - using ss_t = duration>; - using us_t = duration; - - Duration d = src.time_since_epoch(); - - return PyDelta_FromDSU( - duration_cast(d).count() - , duration_cast(d % days(1)).count() - , duration_cast(d % seconds(1)).count()); - } - PYBIND11_TYPE_CASTER(type, _("datetime.timedelta")); +template class type_caster> +: public duration_caster> { }; NAMESPACE_END(detail) From 10a6a90738d27eb732e53bd1ed9c7e05677d22ca Mon Sep 17 00:00:00 2001 From: Trent Houliston Date: Wed, 28 Sep 2016 01:01:59 +1000 Subject: [PATCH 10/10] Redo documentation to make it easier to read --- docs/advanced.rst | 90 ++++++++++++++++++++++++++++++++++++----------- 1 file changed, 70 insertions(+), 20 deletions(-) diff --git a/docs/advanced.rst b/docs/advanced.rst index abb9ec0e3..8cf771bef 100644 --- a/docs/advanced.rst +++ b/docs/advanced.rst @@ -760,32 +760,82 @@ Please refer to the supplemental example for details. C++11 chrono datatypes ====================== -When including the additional header file :file:`pybind11/chrono.h` conversions from c++11 chrono datatypes -to corresponding python datetime objects are automatically enabled. -The following rules describe how the conversions are applied. -When passed to python objects of type ``std::chrono::system_clock::time_point`` are converted into datetime.datetime objects. -These objects are those that specifically come from the system_clock as this is the only clock that measures wall time. +When including the additional header file :file:`pybind11/chrono.h` conversions +from C++11 chrono datatypes to python datetime objects are automatically enabled. +This header also enables conversions of python floats (often from sources such +as `time.monotonic()`, `time.perf_counter()` and `time.process_time()`) into +durations. -When passed to python of type ``std::chrono::[other_clock]::time_point`` are converted into datetime.timedelta objects. -These objects are those that come from all clocks that are not the system_clock (e.g. steady_clock). -Clocks other than the system_clock are not measured from wall date/time and instead have any start time -(often when the computer was turned on). -Therefore as these clocks can only measure time from an arbitrary start point they are represented as timedelta from this start point. +An overview of clocks in C++11 +------------------------------ -When passed to python of type ``std::chrono::duration`` are converted into datetime.timedelta objects. +A point of confusion when using these conversions is the differences between +clocks provided in C++11. There are three clock types defined by the C++11 +standard and users can define their own if needed. Each of these clocks have +different properties and when converting to and from python will give different +results. -When python objects are passed to c++ for the case of non system clocks and durations instances of both datetime.timedelta -and float are accepted. The float arguments are interpreted as a number of seconds since the epoch. +The first clock defined by the standard is ``std::chrono::system_clock``. This +clock measures the current date and time. However, this clock changes with to +updates to the operating system time. For example, if your time is synchronised +with a time server this clock will change. This makes this clock a poor choice +for timing purposes but good for measuring the wall time. -.. note:: +The second clock defined in the standard is ``std::chrono::steady_clock``. +This clock ticks at a steady rate and is never adjusted. This makes it excellent +for timing purposes, however the value in this clock does not correspond to the +current date and time. Often this clock will be the amount of time your system +has been on, although it does not have to be. This clock will never be the same +clock as the system clock as the system clock can change but steady clocks +cannot. - Other clocks may be the same as system_clock. For example on many platforms std::high_resolution_clock is the same as system_clock. - Because of this if you are converting a timepoint from one of these clocks they may appear to python as a datetime.datetime object. +The third clock defined in the standard is ``std::chrono::high_resolution_clock``. +This clock is the clock that has the highest resolution out of the clocks in the +system. It is normally a typedef to either the system clock or the steady clock +but can be its own independent clock. This is important as when using these +conversions as the types you get in python for this clock might be different +depending on the system. +If it is a typedef of the system clock, python will get datetime objects, but if +it is a different clock they will be timedelta objects. - Pythons datetime implementation is limited to microsecond precision. - The extra precision that c++11 clocks can have have (nanoseconds) will be lost upon conversion. - The rounding policy from c++ to python is via ``std::chrono::duration_cast<>`` (rounding towards 0 in microseconds). +Conversions Provided +-------------------- + +C++ to Python + - ``std::chrono::system_clock::time_point`` → ``datetime.datetime`` + System clock times are converted to python datetime instances. They are + in the local timezone, but do not have any timezone information attached + to them (they are naive datetime objects). + + - ``std::chrono::duration`` → ``datetime.timedelta`` + Durations are converted to timedeltas, any precision in the duration + greater than microseconds is lost by rounding towards zero. + + - ``std::chrono::[other_clocks]::time_point`` → ``datetime.timedelta`` + Any clock time that is not the system clock is converted to a time delta. This timedelta measures the time from the clocks epoch to now. + +Python to C++ + - ``datetime.datetime`` → ``std::chrono::system_clock::time_point`` + Date/time objects are converted into system clock timepoints. Any + timezone information is ignored and the type is treated as a naive + object. + + - ``datetime.timedelta`` → ``std::chrono::duration`` + Time delta are converted into durations with microsecond precision. + + - ``datetime.timedelta`` → ``std::chrono::[other_clocks]::time_point`` + Time deltas that are converted into clock timepoints are treated as + the amount of time from the start of the clocks epoch. + + - ``float`` → ``std::chrono::duration`` + Floats that are passed to C++ as durations be interpreted as a number of + seconds. These will be converted to the duration using ``duration_cast`` + from the float. + + - ``float`` → ``std::chrono::[other_clocks]::time_point`` + Floats that are passed to C++ as time points will be interpreted as the + number of seconds from the start of the clocks epoch. Return value policies ===================== @@ -853,7 +903,7 @@ The following table provides an overview of the available return value policies: | | ``keep_alive<0, 1>`` *call policy* (described in the next section) that | | | prevents the parent object from being garbage collected as long as the | | | return value is referenced by Python. This is the default policy for | -| | property getters created via ``def_property``, ``def_readwrite``, etc.) | +| | property getters created via ``def_property``, ``def_readwrite``, etc. | +--------------------------------------------------+----------------------------------------------------------------------------+ .. warning::