| 1 | // |
| 2 | // Copyright (c) 2019-2024 Ruben Perez Hidalgo (rubenperez038 at gmail dot com) |
| 3 | // |
| 4 | // Distributed under the Boost Software License, Version 1.0. (See accompanying |
| 5 | // file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) |
| 6 | // |
| 7 | |
| 8 | #ifndef BOOST_MYSQL_DATE_HPP |
| 9 | #define BOOST_MYSQL_DATE_HPP |
| 10 | |
| 11 | #include <boost/mysql/days.hpp> |
| 12 | |
| 13 | #include <boost/mysql/detail/config.hpp> |
| 14 | #include <boost/mysql/detail/datetime.hpp> |
| 15 | |
| 16 | #include <boost/assert.hpp> |
| 17 | #include <boost/config.hpp> |
| 18 | #include <boost/throw_exception.hpp> |
| 19 | |
| 20 | #include <chrono> |
| 21 | #include <cstdint> |
| 22 | #include <iosfwd> |
| 23 | #include <stdexcept> |
| 24 | |
| 25 | namespace boost { |
| 26 | namespace mysql { |
| 27 | |
| 28 | /** |
| 29 | * \brief Type representing MySQL `DATE` data type. |
| 30 | * \details Represents a broken date by its year, month and day components. |
| 31 | * This type is close to the protocol and should not be used as a vocabulary type. |
| 32 | * Instead, cast it to a `std::chrono::time_point` by calling \ref as_time_point |
| 33 | * or \ref get_time_point. |
| 34 | * \n |
| 35 | * As opposed to `time_point`, this type allows representing invalid and zero dates. |
| 36 | */ |
| 37 | class date |
| 38 | { |
| 39 | public: |
| 40 | /// A `std::chrono::time_point` that can represent any valid `date`. |
| 41 | using time_point = std::chrono::time_point<std::chrono::system_clock, days>; |
| 42 | |
| 43 | /** |
| 44 | * \brief Constructs a zero date. |
| 45 | * \details |
| 46 | * Results in a date with all of its components set to zero. |
| 47 | * |
| 48 | * \par Exception safety |
| 49 | * No-throw guarantee. |
| 50 | */ |
| 51 | constexpr date() noexcept = default; |
| 52 | |
| 53 | /** |
| 54 | * \brief Constructs a date from its year, month and date components. |
| 55 | * \par Exception safety |
| 56 | * No-throw guarantee. |
| 57 | */ |
| 58 | constexpr date(std::uint16_t year, std::uint8_t month, std::uint8_t day) noexcept |
| 59 | : year_(year), month_(month), day_(day) |
| 60 | { |
| 61 | } |
| 62 | |
| 63 | /** |
| 64 | * \brief Constructs a date from a `time_point`. |
| 65 | * \par Exception safety |
| 66 | * Strong guarantee. Throws on invalid input. |
| 67 | * \throws std::out_of_range If the resulting `date` would be |
| 68 | * out of the [\ref min_date, \ref max_date] range. |
| 69 | */ |
| 70 | BOOST_CXX14_CONSTEXPR explicit date(time_point tp) |
| 71 | { |
| 72 | bool ok = detail::days_to_ymd(num_days: tp.time_since_epoch().count(), years&: year_, month&: month_, day&: day_); |
| 73 | if (!ok) |
| 74 | BOOST_THROW_EXCEPTION(std::out_of_range("date::date: time_point was out of range" )); |
| 75 | } |
| 76 | |
| 77 | /** |
| 78 | * \brief Retrieves the year component. |
| 79 | * \par Exception safety |
| 80 | * No-throw guarantee. |
| 81 | */ |
| 82 | constexpr std::uint16_t year() const noexcept { return year_; } |
| 83 | |
| 84 | /** |
| 85 | * \brief Retrieves the month component. |
| 86 | * \par Exception safety |
| 87 | * No-throw guarantee. |
| 88 | */ |
| 89 | constexpr std::uint8_t month() const noexcept { return month_; } |
| 90 | |
| 91 | /** |
| 92 | * \brief Retrieves the day component. |
| 93 | * \par Exception safety |
| 94 | * No-throw guarantee. |
| 95 | */ |
| 96 | constexpr std::uint8_t day() const noexcept { return day_; } |
| 97 | |
| 98 | /** |
| 99 | * \brief Returns `true` if `*this` represents a valid `time_point`. |
| 100 | * \details If any of the individual components is out of range, the date |
| 101 | * doesn't represent an actual `time_point` (e.g. `date(2020, 2, 30)`) or |
| 102 | * the date is not in the [\ref min_date, \ref max_date] validity range, |
| 103 | * returns `false`. Otherwise, returns `true`. |
| 104 | * \par Exception safety |
| 105 | * No-throw guarantee. |
| 106 | */ |
| 107 | constexpr bool valid() const noexcept { return detail::is_valid(years: year_, month: month_, day: day_); } |
| 108 | |
| 109 | /** |
| 110 | * \brief Converts `*this` into a `time_point` (unchecked access). |
| 111 | * \par Preconditions |
| 112 | * `this->valid() == true` (if violated, results in undefined behavior). |
| 113 | * |
| 114 | * \par Exception safety |
| 115 | * No-throw guarantee. |
| 116 | */ |
| 117 | BOOST_CXX14_CONSTEXPR time_point get_time_point() const noexcept |
| 118 | { |
| 119 | BOOST_ASSERT(valid()); |
| 120 | return unch_get_time_point(); |
| 121 | } |
| 122 | |
| 123 | /** |
| 124 | * \brief Converts `*this` into a `time_point` (checked access). |
| 125 | * \par Exception safety |
| 126 | * Strong guarantee. |
| 127 | * \throws std::invalid_argument If `!this->valid()`. |
| 128 | */ |
| 129 | BOOST_CXX14_CONSTEXPR time_point as_time_point() const |
| 130 | { |
| 131 | if (!valid()) |
| 132 | BOOST_THROW_EXCEPTION(std::invalid_argument("date::as_time_point: invalid date" )); |
| 133 | return unch_get_time_point(); |
| 134 | } |
| 135 | |
| 136 | /** |
| 137 | * \brief Tests for equality. |
| 138 | * \details Two dates are considered equal if all of its individual components |
| 139 | * are equal. This function works for invalid dates, too. |
| 140 | * |
| 141 | * \par Exception safety |
| 142 | * No-throw guarantee. |
| 143 | */ |
| 144 | constexpr bool operator==(const date& rhs) const noexcept |
| 145 | { |
| 146 | return year_ == rhs.year_ && month_ == rhs.month_ && day_ == rhs.day_; |
| 147 | } |
| 148 | |
| 149 | /** |
| 150 | * \brief Tests for inequality. |
| 151 | * |
| 152 | * \par Exception safety |
| 153 | * No-throw guarantee. |
| 154 | */ |
| 155 | constexpr bool operator!=(const date& rhs) const noexcept { return !(rhs == *this); } |
| 156 | |
| 157 | /** |
| 158 | * \brief Returns the current system time as a date object. |
| 159 | * \par Exception safety |
| 160 | * Strong guarantee. Only throws if obtaining the current time throws. |
| 161 | */ |
| 162 | static date now() |
| 163 | { |
| 164 | auto now = time_point::clock::now(); |
| 165 | return date(std::chrono::time_point_cast<time_point::duration>(t: now)); |
| 166 | } |
| 167 | |
| 168 | private: |
| 169 | std::uint16_t year_{}; |
| 170 | std::uint8_t month_{}; |
| 171 | std::uint8_t day_{}; |
| 172 | |
| 173 | BOOST_CXX14_CONSTEXPR time_point unch_get_time_point() const noexcept |
| 174 | { |
| 175 | return time_point(days(detail::ymd_to_days(years: year_, month: month_, day: day_))); |
| 176 | } |
| 177 | }; |
| 178 | |
| 179 | /** |
| 180 | * \relates date |
| 181 | * \brief Streams a date. |
| 182 | * \details This function works for invalid dates, too. |
| 183 | */ |
| 184 | BOOST_MYSQL_DECL |
| 185 | std::ostream& operator<<(std::ostream& os, const date& v); |
| 186 | |
| 187 | /// The minimum allowed value for \ref date. |
| 188 | constexpr date min_date{0u, 1u, 1u}; |
| 189 | |
| 190 | /// The maximum allowed value for \ref date. |
| 191 | constexpr date max_date{9999u, 12u, 31u}; |
| 192 | |
| 193 | } // namespace mysql |
| 194 | } // namespace boost |
| 195 | |
| 196 | #ifdef BOOST_MYSQL_HEADER_ONLY |
| 197 | #include <boost/mysql/impl/date.ipp> |
| 198 | #endif |
| 199 | |
| 200 | #endif |
| 201 | |