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