Back to home page

EIC code displayed by LXR

 
 

    


File indexing completed on 2026-05-25 08:18:42

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