Back to home page

EIC code displayed by LXR

 
 

    


File indexing completed on 2025-12-15 09:58:33

0001 //
0002 // Copyright (c) 2019-2025 Ruben Perez Hidalgo (rubenperez038 at gmail dot com)
0003 //
0004 // Distributed under the Boost Software License, Version 1.0. (See accompanying
0005 // file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
0006 //
0007 
0008 #ifndef BOOST_MYSQL_DATETIME_HPP
0009 #define BOOST_MYSQL_DATETIME_HPP
0010 
0011 #include <boost/mysql/days.hpp>
0012 
0013 #include <boost/mysql/detail/config.hpp>
0014 #include <boost/mysql/detail/datetime.hpp>
0015 
0016 #include <boost/assert.hpp>
0017 #include <boost/config.hpp>
0018 #include <boost/throw_exception.hpp>
0019 
0020 #include <chrono>
0021 #include <cstdint>
0022 #include <iosfwd>
0023 #include <ratio>
0024 #include <stdexcept>
0025 
0026 namespace boost {
0027 namespace mysql {
0028 
0029 /**
0030  * \brief Type representing MySQL `DATETIME` and `TIMESTAMP` data types.
0031  * \details Represents a Gregorian date and time broken by its year, month, day, hour, minute, second and
0032  * microsecond components, without a time zone.
0033  * \n
0034  * This type is close to the protocol and should not be used as a vocabulary type.
0035  * Instead, cast it to a `std::chrono::time_point` by calling \ref as_time_point,
0036  * \ref get_time_point, \ref as_local_time_point or \ref get_local_time_point.
0037  * \n
0038  * Datetimes retrieved from MySQL don't include any time zone information. Determining the time zone
0039  * is left to the application. Thus, any time point obtained from this class should be
0040  * interpreted as a local time in an unspecified time zone, like `std::chrono::local_time`.
0041  * For compatibility with older compilers, \ref as_time_point and \ref get_time_point return
0042  * `system_clock` time points. These should be interpreted as local times rather
0043  * than UTC. Prefer using \ref as_local_time_point or \ref get_local_time_point
0044  * if your compiler supports them, as they provide more accurate semantics.
0045  * \n
0046  * As opposed to `time_point`, this type allows representing MySQL invalid and zero datetimes.
0047  * These values are allowed by MySQL but don't represent real time points.
0048  * \n
0049  * Note: using `std::chrono` time zone functionality under MSVC may cause memory leaks to be reported.
0050  * See <a href="https://github.com/microsoft/STL/issues/2047">this issue</a> for an explanation and
0051  * <a href="https://github.com/microsoft/STL/issues/2504">this other issue</a> for a workaround.
0052  */
0053 class datetime
0054 {
0055 public:
0056     /**
0057      * \brief A `std::chrono::time_point` that can represent any valid datetime, with microsecond resolution.
0058      * \details
0059      * Time points used by this class are always local times, even if defined
0060      * to use the system clock. Prefer using \ref local_time_point, if your compiler
0061      * supports it.
0062      */
0063     using time_point = std::chrono::
0064         time_point<std::chrono::system_clock, std::chrono::duration<std::int64_t, std::micro>>;
0065 
0066 #ifdef BOOST_MYSQL_HAS_LOCAL_TIME
0067 
0068     /**
0069      * \brief A `std::chrono::local_time` that can represent any valid datetime, with microsecond resolution.
0070      * \details Requires C++20 calendar types.
0071      */
0072     using local_time_point = std::chrono::local_time<std::chrono::duration<std::int64_t, std::micro>>;
0073 #endif
0074 
0075     /**
0076      * \brief Constructs a zero datetime.
0077      * \details Results in a datetime with all of its components set to zero.
0078      * The resulting object has `this->valid() == false`.
0079      * \par Exception safety
0080      * No-throw guarantee.
0081      */
0082     constexpr datetime() noexcept = default;
0083 
0084     /**
0085      * \brief Constructs a datetime from its individual components.
0086      * \details
0087      * Component values that yield invalid datetimes (like zero or out-of-range
0088      * values) are allowed, resulting in an object with `this->valid() == false`.
0089      * \par Exception safety
0090      * No-throw guarantee.
0091      */
0092     constexpr datetime(
0093         std::uint16_t year,
0094         std::uint8_t month,
0095         std::uint8_t day,
0096         std::uint8_t hour = 0,
0097         std::uint8_t minute = 0,
0098         std::uint8_t second = 0,
0099         std::uint32_t microsecond = 0
0100     ) noexcept
0101         : year_(year),
0102           month_(month),
0103           day_(day),
0104           hour_(hour),
0105           minute_(minute),
0106           second_(second),
0107           microsecond_(microsecond)
0108     {
0109     }
0110 
0111     /**
0112      * \brief Constructs a datetime from a `time_point`.
0113      * \par Exception safety
0114      * Strong guarantee. Throws on invalid input.
0115      * \throws std::out_of_range If the resulting `datetime` object would be
0116      * out of the [\ref min_datetime, \ref max_datetime] range.
0117      */
0118     BOOST_CXX14_CONSTEXPR inline explicit datetime(time_point tp);
0119 
0120 #ifdef BOOST_MYSQL_HAS_LOCAL_TIME
0121     /**
0122      * \brief Constructs a datetime from a `local_time_point`.
0123      * \details
0124      * Equivalent to constructing a `date` from a `time_point` with the same
0125      * `time_since_epoch()` as `tp`.
0126      * \n
0127      * Requires C++20 calendar types.
0128      *
0129      * \par Exception safety
0130      * Strong guarantee. Throws on invalid input.
0131      * \throws std::out_of_range If the resulting `datetime` object would be
0132      * out of the [\ref min_datetime, \ref max_datetime] range.
0133      */
0134     constexpr explicit datetime(local_time_point tp) : datetime(time_point(tp.time_since_epoch())) {}
0135 #endif
0136 
0137     /**
0138      * \brief Retrieves the year component.
0139      * \details
0140      * Represents the year number in the Gregorian calendar.
0141      * If `this->valid() == true`, this value is within the `[0, 9999]` range.
0142      *
0143      * \par Exception safety
0144      * No-throw guarantee.
0145      */
0146     constexpr std::uint16_t year() const noexcept { return year_; }
0147 
0148     /**
0149      * \brief Retrieves the month component (1-based).
0150      * \details
0151      * A value of 1 represents January.
0152      * If `this->valid() == true`, this value is within the `[1, 12]` range.
0153      *
0154      * \par Exception safety
0155      * No-throw guarantee.
0156      */
0157     constexpr std::uint8_t month() const noexcept { return month_; }
0158 
0159     /**
0160      * \brief Retrieves the day component (1-based).
0161      * \details
0162      * A value of 1 represents the first day of the month.
0163      * If `this->valid() == true`, this value is within the `[1, last_month_day]` range
0164      * (where `last_month_day` is the last day of the month).
0165      *
0166      * \par Exception safety
0167      * No-throw guarantee.
0168      */
0169     constexpr std::uint8_t day() const noexcept { return day_; }
0170 
0171     /**
0172      * \brief Retrieves the hour component.
0173      * \details If `this->valid() == true`, this value is within the `[0, 23]` range.
0174      * \par Exception safety
0175      * No-throw guarantee.
0176      */
0177     constexpr std::uint8_t hour() const noexcept { return hour_; }
0178 
0179     /**
0180      * \brief Retrieves the minute component.
0181      * \details If `this->valid() == true`, this value is within the `[0, 59]` range.
0182      * \par Exception safety
0183      * No-throw guarantee.
0184      */
0185     constexpr std::uint8_t minute() const noexcept { return minute_; }
0186 
0187     /**
0188      * \brief Retrieves the second component.
0189      * \details If `this->valid() == true`, this value is within the `[0, 59]` range.
0190      * \par Exception safety
0191      * No-throw guarantee.
0192      */
0193     constexpr std::uint8_t second() const noexcept { return second_; }
0194 
0195     /**
0196      * \brief Retrieves the microsecond component.
0197      * \details If `this->valid() == true`, this value is within the `[0, 999999]` range.
0198      * \par Exception safety
0199      * No-throw guarantee.
0200      */
0201     constexpr std::uint32_t microsecond() const noexcept { return microsecond_; }
0202 
0203     /**
0204      * \brief Returns `true` if `*this` represents a valid `time_point`.
0205      * \details If any of the individual components is out of range, the datetime
0206      * doesn't represent an actual `time_point` (e.g. `datetime(2020, 2, 30)`) or
0207      * the datetime is not in the [\ref min_date, \ref max_date] validity range,
0208      * returns `false`. Otherwise, returns `true`.
0209      *
0210      * \par Exception safety
0211      * No-throw guarantee.
0212      */
0213     constexpr bool valid() const noexcept
0214     {
0215         return detail::is_valid(year_, month_, day_) && hour_ <= detail::max_hour &&
0216                minute_ <= detail::max_min && second_ <= detail::max_sec && microsecond_ <= detail::max_micro;
0217     }
0218 
0219     /**
0220      * \brief Converts `*this` into a `time_point` (unchecked access).
0221      * \details
0222      * If your compiler supports it, prefer using \ref get_local_time_point,
0223      * as it provides more accurate semantics.
0224      *
0225      * \par Preconditions
0226      * `this->valid() == true` (if violated, results in undefined behavior).
0227      *
0228      * \par Exception safety
0229      * No-throw guarantee.
0230      */
0231     BOOST_CXX14_CONSTEXPR time_point get_time_point() const noexcept
0232     {
0233         BOOST_ASSERT(valid());
0234         return time_point(unch_get_micros());
0235     }
0236 
0237     /**
0238      * \brief Converts `*this` into a `time_point` (checked access).
0239      * \details
0240      * If your compiler supports it, prefer using \ref as_local_time_point,
0241      * as it provides more accurate semantics.
0242      *
0243      * \par Exception safety
0244      * Strong guarantee.
0245      * \throws std::invalid_argument If `!this->valid()`.
0246      */
0247     BOOST_CXX14_CONSTEXPR inline time_point as_time_point() const
0248     {
0249         if (!valid())
0250             BOOST_THROW_EXCEPTION(std::invalid_argument("datetime::as_time_point: invalid datetime"));
0251         return time_point(unch_get_micros());
0252     }
0253 
0254 #ifdef BOOST_MYSQL_HAS_LOCAL_TIME
0255     /**
0256      * \brief Converts `*this` into a `local_time_point` (unchecked access).
0257      * \details
0258      * The returned object has the same `time_since_epoch()` as `this->get_time_point()`,
0259      * but uses the `std::chrono::local_t` pseudo-clock to better represent
0260      * the absence of time zone information.
0261      * \n
0262      * Requires C++20 calendar types.
0263      *
0264      * \par Preconditions
0265      * `this->valid() == true` (if violated, results in undefined behavior).
0266      *
0267      * \par Exception safety
0268      * No-throw guarantee.
0269      */
0270     constexpr local_time_point get_local_time_point() const noexcept
0271     {
0272         BOOST_ASSERT(valid());
0273         return local_time_point(unch_get_micros());
0274     }
0275 
0276     /**
0277      * \brief Converts `*this` into a local time point (checked access).
0278      * \details
0279      * The returned object has the same `time_since_epoch()` as `this->as_time_point()`,
0280      * but uses the `std::chrono::local_t` pseudo-clock to better represent
0281      * the absence of time zone information.
0282      * \n
0283      * Requires C++20 calendar types.
0284      *
0285      * \par Exception safety
0286      * Strong guarantee.
0287      * \throws std::invalid_argument If `!this->valid()`.
0288      */
0289     constexpr local_time_point as_local_time_point() const
0290     {
0291         if (!valid())
0292             BOOST_THROW_EXCEPTION(std::invalid_argument("date::as_local_time_point: invalid date"));
0293         return local_time_point(unch_get_micros());
0294     }
0295 #endif
0296 
0297     /**
0298      * \brief Tests for equality.
0299      * \details Two datetimes are considered equal if all of its individual components
0300      * are equal. This function works for invalid datetimes, too.
0301      *
0302      * \par Exception safety
0303      * No-throw guarantee.
0304      */
0305     constexpr bool operator==(const datetime& rhs) const noexcept
0306     {
0307         return year_ == rhs.year_ && month_ == rhs.month_ && day_ == rhs.day_ && hour_ == rhs.hour_ &&
0308                minute_ == rhs.minute_ && second_ == rhs.second_ && microsecond_ == rhs.microsecond_;
0309     }
0310 
0311     /**
0312      * \brief Tests for inequality.
0313      * \par Exception safety
0314      * No-throw guarantee.
0315      */
0316     constexpr bool operator!=(const datetime& rhs) const noexcept { return !(*this == rhs); }
0317 
0318     /**
0319      * \brief Returns the current system time as a datetime object.
0320      * \par Exception safety
0321      * Strong guarantee. Only throws if obtaining the current time throws.
0322      */
0323     static datetime now()
0324     {
0325         auto now = time_point::clock::now();
0326         return datetime(std::chrono::time_point_cast<time_point::duration>(now));
0327     }
0328 
0329 private:
0330     std::uint16_t year_{};
0331     std::uint8_t month_{};
0332     std::uint8_t day_{};
0333     std::uint8_t hour_{};
0334     std::uint8_t minute_{};
0335     std::uint8_t second_{};
0336     std::uint32_t microsecond_{};
0337 
0338     BOOST_CXX14_CONSTEXPR inline time_point::duration unch_get_micros() const
0339     {
0340         // Doing time of day independently to prevent overflow
0341         days d(detail::ymd_to_days(year_, month_, day_));
0342         auto time_of_day = std::chrono::hours(hour_) + std::chrono::minutes(minute_) +
0343                            std::chrono::seconds(second_) + std::chrono::microseconds(microsecond_);
0344         return time_point::duration(d) + time_of_day;
0345     }
0346 };
0347 
0348 /**
0349  * \relates datetime
0350  * \brief Streams a datetime.
0351  * \details This function works for invalid datetimes, too.
0352  */
0353 BOOST_MYSQL_DECL
0354 std::ostream& operator<<(std::ostream& os, const datetime& v);
0355 
0356 /// The minimum allowed value for \ref datetime.
0357 BOOST_INLINE_CONSTEXPR datetime min_datetime(0u, 1u, 1u);
0358 
0359 /// The maximum allowed value for \ref datetime.
0360 BOOST_INLINE_CONSTEXPR datetime max_datetime(9999u, 12u, 31u, 23u, 59u, 59u, 999999u);
0361 
0362 }  // namespace mysql
0363 }  // namespace boost
0364 
0365 // Implementations
0366 BOOST_CXX14_CONSTEXPR boost::mysql::datetime::datetime(time_point tp)
0367 {
0368     using std::chrono::duration_cast;
0369     using std::chrono::hours;
0370     using std::chrono::microseconds;
0371     using std::chrono::minutes;
0372     using std::chrono::seconds;
0373 
0374     // Avoiding using -= for durations as it's not constexpr until C++17
0375     auto input_dur = tp.time_since_epoch();
0376     auto rem = input_dur % days(1);
0377     auto num_days = duration_cast<days>(input_dur);
0378     if (rem.count() < 0)
0379     {
0380         rem = rem + days(1);
0381         num_days = num_days - days(1);
0382     }
0383     auto num_hours = duration_cast<hours>(rem);
0384     rem = rem - num_hours;
0385     auto num_minutes = duration_cast<minutes>(rem);
0386     rem = rem - num_minutes;
0387     auto num_seconds = duration_cast<seconds>(rem);
0388     rem = rem - num_seconds;
0389     auto num_microseconds = duration_cast<microseconds>(rem);
0390 
0391     BOOST_ASSERT(num_hours.count() >= 0 && num_hours.count() <= detail::max_hour);
0392     BOOST_ASSERT(num_minutes.count() >= 0 && num_minutes.count() <= detail::max_min);
0393     BOOST_ASSERT(num_seconds.count() >= 0 && num_seconds.count() <= detail::max_sec);
0394     BOOST_ASSERT(num_microseconds.count() >= 0 && num_microseconds.count() <= detail::max_micro);
0395 
0396     bool ok = detail::days_to_ymd(num_days.count(), year_, month_, day_);
0397     if (!ok)
0398         BOOST_THROW_EXCEPTION(std::out_of_range("datetime::datetime: time_point was out of range"));
0399 
0400     microsecond_ = static_cast<std::uint32_t>(num_microseconds.count());
0401     second_ = static_cast<std::uint8_t>(num_seconds.count());
0402     minute_ = static_cast<std::uint8_t>(num_minutes.count());
0403     hour_ = static_cast<std::uint8_t>(num_hours.count());
0404 }
0405 
0406 #ifdef BOOST_MYSQL_HEADER_ONLY
0407 #include <boost/mysql/impl/datetime.ipp>
0408 #endif
0409 
0410 #endif