μHAL (v2.8.17)
Part of the IPbus software repository
All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Friends Macros Modules Pages
chrono.h
Go to the documentation of this file.
1/*
2 pybind11/chrono.h: Transparent conversion between std::chrono and python's datetime
3
4 Copyright (c) 2016 Trent Houliston <trent@houliston.me> and
5 Wenzel Jakob <wenzel.jakob@epfl.ch>
6
7 All rights reserved. Use of this source code is governed by a
8 BSD-style license that can be found in the LICENSE file.
9*/
10
11#pragma once
12
13#include "pybind11.h"
14
15#include <chrono>
16#include <cmath>
17#include <ctime>
18#include <datetime.h>
19#include <mutex>
20
21// Backport the PyDateTime_DELTA functions from Python3.3 if required
22#ifndef PyDateTime_DELTA_GET_DAYS
23# define PyDateTime_DELTA_GET_DAYS(o) (((PyDateTime_Delta *) o)->days)
24#endif
25#ifndef PyDateTime_DELTA_GET_SECONDS
26# define PyDateTime_DELTA_GET_SECONDS(o) (((PyDateTime_Delta *) o)->seconds)
27#endif
28#ifndef PyDateTime_DELTA_GET_MICROSECONDS
29# define PyDateTime_DELTA_GET_MICROSECONDS(o) (((PyDateTime_Delta *) o)->microseconds)
30#endif
31
34
35template <typename type>
37public:
38 using rep = typename type::rep;
39 using period = typename type::period;
40
41 // signed 25 bits required by the standard.
42 using days = std::chrono::duration<int_least32_t, std::ratio<86400>>;
43
44 bool load(handle src, bool) {
45 using namespace std::chrono;
46
47 // Lazy initialise the PyDateTime import
48 if (!PyDateTimeAPI) {
49 PyDateTime_IMPORT;
50 }
51
52 if (!src) {
53 return false;
54 }
55 // If invoked with datetime.delta object
56 if (PyDelta_Check(src.ptr())) {
57 value = type(duration_cast<duration<rep, period>>(
59 + seconds(PyDateTime_DELTA_GET_SECONDS(src.ptr()))
60 + microseconds(PyDateTime_DELTA_GET_MICROSECONDS(src.ptr()))));
61 return true;
62 }
63 // If invoked with a float we assume it is seconds and convert
64 if (PyFloat_Check(src.ptr())) {
65 value = type(duration_cast<duration<rep, period>>(
66 duration<double>(PyFloat_AsDouble(src.ptr()))));
67 return true;
68 }
69 return false;
70 }
71
72 // If this is a duration just return it back
73 static const std::chrono::duration<rep, period> &
74 get_duration(const std::chrono::duration<rep, period> &src) {
75 return src;
76 }
77
78 // If this is a time_point get the time_since_epoch
79 template <typename Clock>
80 static std::chrono::duration<rep, period>
81 get_duration(const std::chrono::time_point<Clock, std::chrono::duration<rep, period>> &src) {
82 return src.time_since_epoch();
83 }
84
85 static handle cast(const type &src, return_value_policy /* policy */, handle /* parent */) {
86 using namespace std::chrono;
87
88 // Use overloaded function to get our duration from our source
89 // Works out if it is a duration or time_point and get the duration
90 auto d = get_duration(src);
91
92 // Lazy initialise the PyDateTime import
93 if (!PyDateTimeAPI) {
94 PyDateTime_IMPORT;
95 }
96
97 // Declare these special duration types so the conversions happen with the correct
98 // primitive types (int)
99 using dd_t = duration<int, std::ratio<86400>>;
100 using ss_t = duration<int, std::ratio<1>>;
101 using us_t = duration<int, std::micro>;
102
103 auto dd = duration_cast<dd_t>(d);
104 auto subd = d - dd;
105 auto ss = duration_cast<ss_t>(subd);
106 auto us = duration_cast<us_t>(subd - ss);
107 return PyDelta_FromDSU(dd.count(), ss.count(), us.count());
108 }
109
110 PYBIND11_TYPE_CASTER(type, const_name("datetime.timedelta"));
111};
112
113inline std::tm *localtime_thread_safe(const std::time_t *time, std::tm *buf) {
114#if (defined(__STDC_LIB_EXT1__) && defined(__STDC_WANT_LIB_EXT1__)) || defined(_MSC_VER)
115 if (localtime_s(buf, time))
116 return nullptr;
117 return buf;
118#else
119 static std::mutex mtx;
120 std::lock_guard<std::mutex> lock(mtx);
121 std::tm *tm_ptr = std::localtime(time);
122 if (tm_ptr != nullptr) {
123 *buf = *tm_ptr;
124 }
125 return tm_ptr;
126#endif
127}
128
129// This is for casting times on the system clock into datetime.datetime instances
130template <typename Duration>
131class type_caster<std::chrono::time_point<std::chrono::system_clock, Duration>> {
132public:
133 using type = std::chrono::time_point<std::chrono::system_clock, Duration>;
134 bool load(handle src, bool) {
135 using namespace std::chrono;
136
137 // Lazy initialise the PyDateTime import
138 if (!PyDateTimeAPI) {
139 PyDateTime_IMPORT;
140 }
141
142 if (!src) {
143 return false;
144 }
145
146 std::tm cal;
147 microseconds msecs;
148
149 if (PyDateTime_Check(src.ptr())) {
150 cal.tm_sec = PyDateTime_DATE_GET_SECOND(src.ptr());
151 cal.tm_min = PyDateTime_DATE_GET_MINUTE(src.ptr());
152 cal.tm_hour = PyDateTime_DATE_GET_HOUR(src.ptr());
153 cal.tm_mday = PyDateTime_GET_DAY(src.ptr());
154 cal.tm_mon = PyDateTime_GET_MONTH(src.ptr()) - 1;
155 cal.tm_year = PyDateTime_GET_YEAR(src.ptr()) - 1900;
156 cal.tm_isdst = -1;
157 msecs = microseconds(PyDateTime_DATE_GET_MICROSECOND(src.ptr()));
158 } else if (PyDate_Check(src.ptr())) {
159 cal.tm_sec = 0;
160 cal.tm_min = 0;
161 cal.tm_hour = 0;
162 cal.tm_mday = PyDateTime_GET_DAY(src.ptr());
163 cal.tm_mon = PyDateTime_GET_MONTH(src.ptr()) - 1;
164 cal.tm_year = PyDateTime_GET_YEAR(src.ptr()) - 1900;
165 cal.tm_isdst = -1;
166 msecs = microseconds(0);
167 } else if (PyTime_Check(src.ptr())) {
168 cal.tm_sec = PyDateTime_TIME_GET_SECOND(src.ptr());
169 cal.tm_min = PyDateTime_TIME_GET_MINUTE(src.ptr());
170 cal.tm_hour = PyDateTime_TIME_GET_HOUR(src.ptr());
171 cal.tm_mday = 1; // This date (day, month, year) = (1, 0, 70)
172 cal.tm_mon = 0; // represents 1-Jan-1970, which is the first
173 cal.tm_year = 70; // earliest available date for Python's datetime
174 cal.tm_isdst = -1;
175 msecs = microseconds(PyDateTime_TIME_GET_MICROSECOND(src.ptr()));
176 } else {
177 return false;
178 }
179
180 value = time_point_cast<Duration>(system_clock::from_time_t(std::mktime(&cal)) + msecs);
181 return true;
182 }
183
184 static handle cast(const std::chrono::time_point<std::chrono::system_clock, Duration> &src,
185 return_value_policy /* policy */,
186 handle /* parent */) {
187 using namespace std::chrono;
188
189 // Lazy initialise the PyDateTime import
190 if (!PyDateTimeAPI) {
191 PyDateTime_IMPORT;
192 }
193
194 // Get out microseconds, and make sure they are positive, to avoid bug in eastern
195 // hemisphere time zones (cfr. https://github.com/pybind/pybind11/issues/2417)
196 using us_t = duration<int, std::micro>;
197 auto us = duration_cast<us_t>(src.time_since_epoch() % seconds(1));
198 if (us.count() < 0) {
199 us += seconds(1);
200 }
201
202 // Subtract microseconds BEFORE `system_clock::to_time_t`, because:
203 // > If std::time_t has lower precision, it is implementation-defined whether the value is
204 // rounded or truncated. (https://en.cppreference.com/w/cpp/chrono/system_clock/to_time_t)
205 std::time_t tt
206 = system_clock::to_time_t(time_point_cast<system_clock::duration>(src - us));
207
208 std::tm localtime;
209 std::tm *localtime_ptr = localtime_thread_safe(&tt, &localtime);
210 if (!localtime_ptr) {
211 throw cast_error("Unable to represent system_clock in local time");
212 }
213 return PyDateTime_FromDateAndTime(localtime.tm_year + 1900,
214 localtime.tm_mon + 1,
215 localtime.tm_mday,
216 localtime.tm_hour,
217 localtime.tm_min,
218 localtime.tm_sec,
219 us.count());
220 }
221 PYBIND11_TYPE_CASTER(type, const_name("datetime.datetime"));
222};
223
224// Other clocks that are not the system clock are not measured as datetime.datetime objects
225// since they are not measured on calendar time. So instead we just make them timedeltas
226// Or if they have passed us a time as a float we convert that
227template <typename Clock, typename Duration>
228class type_caster<std::chrono::time_point<Clock, Duration>>
229 : public duration_caster<std::chrono::time_point<Clock, Duration>> {};
230
231template <typename Rep, typename Period>
232class type_caster<std::chrono::duration<Rep, Period>>
233 : public duration_caster<std::chrono::duration<Rep, Period>> {};
234
static std::chrono::duration< rep, period > get_duration(const std::chrono::time_point< Clock, std::chrono::duration< rep, period > > &src)
Definition: chrono.h:81
PYBIND11_TYPE_CASTER(type, const_name("datetime.timedelta"))
typename type::period period
Definition: chrono.h:39
static handle cast(const type &src, return_value_policy, handle)
Definition: chrono.h:85
typename type::rep rep
Definition: chrono.h:38
std::chrono::duration< int_least32_t, std::ratio< 86400 > > days
Definition: chrono.h:42
bool load(handle src, bool)
Definition: chrono.h:44
static const std::chrono::duration< rep, period > & get_duration(const std::chrono::duration< rep, period > &src)
Definition: chrono.h:74
\rst Holds a reference to a Python object (no reference counting)
Definition: pytypes.h:194
PyObject * ptr() const
Return the underlying PyObject * pointer.
Definition: pytypes.h:203
static handle cast(const std::chrono::time_point< std::chrono::system_clock, Duration > &src, return_value_policy, handle)
Definition: chrono.h:184
Definition: pytypes.h:1167
std::tm * localtime_thread_safe(const std::time_t *time, std::tm *buf)
Definition: chrono.h:113
#define PyDateTime_DELTA_GET_SECONDS(o)
Definition: chrono.h:26
#define PyDateTime_DELTA_GET_MICROSECONDS(o)
Definition: chrono.h:29
#define PyDateTime_DELTA_GET_DAYS(o)
Definition: chrono.h:23
#define PYBIND11_NAMESPACE_END(name)
Definition: common.h:21
#define PYBIND11_NAMESPACE_BEGIN(name)
Definition: common.h:20
return_value_policy
Approach used to cast a previously unknown C++ instance into a Python object.
Definition: common.h:470
constexpr descr< N - 1 > const_name(char const (&text)[N])
Definition: descr.h:60