File indexing completed on 2025-02-16 05:12:15

0001 /*
0002     pybind11/chrono.h: Transparent conversion between std::chrono and python's datetime
0003 
0004     Copyright (c) 2016 Trent Houliston <trent@houliston.me> and
0005                        Wenzel Jakob <wenzel.jakob@epfl.ch>
0006 
0007     All rights reserved. Use of this source code is governed by a
0008     BSD-style license that can be found in the LICENSE file.
0009 */
0010 
0011 #pragma once
0012 
0013 #include "pybind11.h"
0014 
0015 #include <chrono>
0016 #include <cmath>
0017 #include <ctime>
0018 #include <mutex>
0019 
0020 #include <datetime.h>
0021 
0022 // Backport the PyDateTime_DELTA functions from Python3.3 if required
0023 #ifndef PyDateTime_DELTA_GET_DAYS
0024 #define PyDateTime_DELTA_GET_DAYS(o)         (((PyDateTime_Delta*)o)->days)
0025 #endif
0026 #ifndef PyDateTime_DELTA_GET_SECONDS
0027 #define PyDateTime_DELTA_GET_SECONDS(o)      (((PyDateTime_Delta*)o)->seconds)
0028 #endif
0029 #ifndef PyDateTime_DELTA_GET_MICROSECONDS
0030 #define PyDateTime_DELTA_GET_MICROSECONDS(o) (((PyDateTime_Delta*)o)->microseconds)
0031 #endif
0032 
0033 PYBIND11_NAMESPACE_BEGIN(PYBIND11_NAMESPACE)
0034 PYBIND11_NAMESPACE_BEGIN(detail)
0035 
0036 template <typename type> class duration_caster {
0037 public:
0038     using rep = typename type::rep;
0039     using period = typename type::period;
0040 
0041     using days = std::chrono::duration<int_least32_t, std::ratio<86400>>; // signed 25 bits required by the standard.
0042 
0043     bool load(handle src, bool) {
0044         using namespace std::chrono;
0045 
0046         // Lazy initialise the PyDateTime import
0047         if (!PyDateTimeAPI) { PyDateTime_IMPORT; }
0048 
0049         if (!src) return false;
0050         // If invoked with datetime.delta object
0051         if (PyDelta_Check(src.ptr())) {
0052             value = type(duration_cast<duration<rep, period>>(
0053                   days(PyDateTime_DELTA_GET_DAYS(src.ptr()))
0054                 + seconds(PyDateTime_DELTA_GET_SECONDS(src.ptr()))
0055                 + microseconds(PyDateTime_DELTA_GET_MICROSECONDS(src.ptr()))));
0056             return true;
0057         }
0058         // If invoked with a float we assume it is seconds and convert
0059         if (PyFloat_Check(src.ptr())) {
0060             value = type(duration_cast<duration<rep, period>>(duration<double>(PyFloat_AsDouble(src.ptr()))));
0061             return true;
0062         }
0063         return false;
0064     }
0065 
0066     // If this is a duration just return it back
0067     static const std::chrono::duration<rep, period>& get_duration(const std::chrono::duration<rep, period> &src) {
0068         return src;
0069     }
0070 
0071     // If this is a time_point get the time_since_epoch
0072     template <typename Clock> static std::chrono::duration<rep, period> get_duration(const std::chrono::time_point<Clock, std::chrono::duration<rep, period>> &src) {
0073         return src.time_since_epoch();
0074     }
0075 
0076     static handle cast(const type &src, return_value_policy /* policy */, handle /* parent */) {
0077         using namespace std::chrono;
0078 
0079         // Use overloaded function to get our duration from our source
0080         // Works out if it is a duration or time_point and get the duration
0081         auto d = get_duration(src);
0082 
0083         // Lazy initialise the PyDateTime import
0084         if (!PyDateTimeAPI) { PyDateTime_IMPORT; }
0085 
0086         // Declare these special duration types so the conversions happen with the correct primitive types (int)
0087         using dd_t = duration<int, std::ratio<86400>>;
0088         using ss_t = duration<int, std::ratio<1>>;
0089         using us_t = duration<int, std::micro>;
0090 
0091         auto dd = duration_cast<dd_t>(d);
0092         auto subd = d - dd;
0093         auto ss = duration_cast<ss_t>(subd);
0094         auto us = duration_cast<us_t>(subd - ss);
0095         return PyDelta_FromDSU(dd.count(), ss.count(), us.count());
0096     }
0097 
0098     PYBIND11_TYPE_CASTER(type, const_name("datetime.timedelta"));
0099 };
0100 
0101 inline std::tm *localtime_thread_safe(const std::time_t *time, std::tm *buf) {
0102 #if (defined(__STDC_LIB_EXT1__) && defined(__STDC_WANT_LIB_EXT1__)) || defined(_MSC_VER)
0103     if (localtime_s(buf, time))
0104         return nullptr;
0105     return buf;
0106 #else
0107     static std::mutex mtx;
0108     std::lock_guard<std::mutex> lock(mtx);
0109     std::tm *tm_ptr = std::localtime(time);
0110     if (tm_ptr != nullptr) {
0111         *buf = *tm_ptr;
0112     }
0113     return tm_ptr;
0114 #endif
0115 }
0116 
0117 // This is for casting times on the system clock into datetime.datetime instances
0118 template <typename Duration> class type_caster<std::chrono::time_point<std::chrono::system_clock, Duration>> {
0119 public:
0120     using type = std::chrono::time_point<std::chrono::system_clock, Duration>;
0121     bool load(handle src, bool) {
0122         using namespace std::chrono;
0123 
0124         // Lazy initialise the PyDateTime import
0125         if (!PyDateTimeAPI) { PyDateTime_IMPORT; }
0126 
0127         if (!src) return false;
0128 
0129         std::tm cal;
0130         microseconds msecs;
0131 
0132         if (PyDateTime_Check(src.ptr())) {
0133             cal.tm_sec   = PyDateTime_DATE_GET_SECOND(src.ptr());
0134             cal.tm_min   = PyDateTime_DATE_GET_MINUTE(src.ptr());
0135             cal.tm_hour  = PyDateTime_DATE_GET_HOUR(src.ptr());
0136             cal.tm_mday  = PyDateTime_GET_DAY(src.ptr());
0137             cal.tm_mon   = PyDateTime_GET_MONTH(src.ptr()) - 1;
0138             cal.tm_year  = PyDateTime_GET_YEAR(src.ptr()) - 1900;
0139             cal.tm_isdst = -1;
0140             msecs        = microseconds(PyDateTime_DATE_GET_MICROSECOND(src.ptr()));
0141         } else if (PyDate_Check(src.ptr())) {
0142             cal.tm_sec   = 0;
0143             cal.tm_min   = 0;
0144             cal.tm_hour  = 0;
0145             cal.tm_mday  = PyDateTime_GET_DAY(src.ptr());
0146             cal.tm_mon   = PyDateTime_GET_MONTH(src.ptr()) - 1;
0147             cal.tm_year  = PyDateTime_GET_YEAR(src.ptr()) - 1900;
0148             cal.tm_isdst = -1;
0149             msecs        = microseconds(0);
0150         } else if (PyTime_Check(src.ptr())) {
0151             cal.tm_sec   = PyDateTime_TIME_GET_SECOND(src.ptr());
0152             cal.tm_min   = PyDateTime_TIME_GET_MINUTE(src.ptr());
0153             cal.tm_hour  = PyDateTime_TIME_GET_HOUR(src.ptr());
0154             cal.tm_mday  = 1;   // This date (day, month, year) = (1, 0, 70)
0155             cal.tm_mon   = 0;   // represents 1-Jan-1970, which is the first
0156             cal.tm_year  = 70;  // earliest available date for Python's datetime
0157             cal.tm_isdst = -1;
0158             msecs        = microseconds(PyDateTime_TIME_GET_MICROSECOND(src.ptr()));
0159         }
0160         else return false;
0161 
0162         value = time_point_cast<Duration>(system_clock::from_time_t(std::mktime(&cal)) + msecs);
0163         return true;
0164     }
0165 
0166     static handle cast(const std::chrono::time_point<std::chrono::system_clock, Duration> &src, return_value_policy /* policy */, handle /* parent */) {
0167         using namespace std::chrono;
0168 
0169         // Lazy initialise the PyDateTime import
0170         if (!PyDateTimeAPI) { PyDateTime_IMPORT; }
0171 
0172         // Get out microseconds, and make sure they are positive, to avoid bug in eastern hemisphere time zones
0173         // (cfr. https://github.com/pybind/pybind11/issues/2417)
0174         using us_t = duration<int, std::micro>;
0175         auto us = duration_cast<us_t>(src.time_since_epoch() % seconds(1));
0176         if (us.count() < 0)
0177             us += seconds(1);
0178 
0179         // Subtract microseconds BEFORE `system_clock::to_time_t`, because:
0180         // > If std::time_t has lower precision, it is implementation-defined whether the value is rounded or truncated.
0181         // (https://en.cppreference.com/w/cpp/chrono/system_clock/to_time_t)
0182         std::time_t tt = system_clock::to_time_t(time_point_cast<system_clock::duration>(src - us));
0183 
0184         std::tm localtime;
0185         std::tm *localtime_ptr = localtime_thread_safe(&tt, &localtime);
0186         if (!localtime_ptr)
0187             throw cast_error("Unable to represent system_clock in local time");
0188         return PyDateTime_FromDateAndTime(localtime.tm_year + 1900,
0189                                           localtime.tm_mon + 1,
0190                                           localtime.tm_mday,
0191                                           localtime.tm_hour,
0192                                           localtime.tm_min,
0193                                           localtime.tm_sec,
0194                                           us.count());
0195     }
0196     PYBIND11_TYPE_CASTER(type, const_name("datetime.datetime"));
0197 };
0198 
0199 // Other clocks that are not the system clock are not measured as datetime.datetime objects
0200 // since they are not measured on calendar time. So instead we just make them timedeltas
0201 // Or if they have passed us a time as a float we convert that
0202 template <typename Clock, typename Duration> class type_caster<std::chrono::time_point<Clock, Duration>>
0203 : public duration_caster<std::chrono::time_point<Clock, Duration>> {
0204 };
0205 
0206 template <typename Rep, typename Period> class type_caster<std::chrono::duration<Rep, Period>>
0207 : public duration_caster<std::chrono::duration<Rep, Period>> {
0208 };
0209 
0210 PYBIND11_NAMESPACE_END(detail)
0211 PYBIND11_NAMESPACE_END(PYBIND11_NAMESPACE)