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
21PYBIND11_NAMESPACE_BEGIN(PYBIND11_NAMESPACE)
22PYBIND11_NAMESPACE_BEGIN(detail)
23
24template <typename type>
25class duration_caster {
26public:
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
102inline 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
119template <typename Duration>
120class type_caster<std::chrono::time_point<std::chrono::system_clock, Duration>> {
121public:
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
216template <typename Clock, typename Duration>
217class type_caster<std::chrono::time_point<Clock, Duration>>
218 : public duration_caster<std::chrono::time_point<Clock, Duration>> {};
219
220template <typename Rep, typename Period>
221class type_caster<std::chrono::duration<Rep, Period>>
222 : public duration_caster<std::chrono::duration<Rep, Period>> {};
223
224PYBIND11_NAMESPACE_END(detail)
225PYBIND11_NAMESPACE_END(PYBIND11_NAMESPACE)
226