datetime.hpp 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410
  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. #ifndef BOOST_MYSQL_DATETIME_HPP
  8. #define BOOST_MYSQL_DATETIME_HPP
  9. #include <boost/mysql/days.hpp>
  10. #include <boost/mysql/detail/config.hpp>
  11. #include <boost/mysql/detail/datetime.hpp>
  12. #include <boost/assert.hpp>
  13. #include <boost/config.hpp>
  14. #include <boost/throw_exception.hpp>
  15. #include <chrono>
  16. #include <cstdint>
  17. #include <iosfwd>
  18. #include <ratio>
  19. #include <stdexcept>
  20. namespace boost {
  21. namespace mysql {
  22. /**
  23. * \brief Type representing MySQL `DATETIME` and `TIMESTAMP` data types.
  24. * \details Represents a Gregorian date and time broken by its year, month, day, hour, minute, second and
  25. * microsecond components, without a time zone.
  26. * \n
  27. * This type is close to the protocol and should not be used as a vocabulary type.
  28. * Instead, cast it to a `std::chrono::time_point` by calling \ref as_time_point,
  29. * \ref get_time_point, \ref as_local_time_point or \ref get_local_time_point.
  30. * \n
  31. * Datetimes retrieved from MySQL don't include any time zone information. Determining the time zone
  32. * is left to the application. Thus, any time point obtained from this class should be
  33. * interpreted as a local time in an unspecified time zone, like `std::chrono::local_time`.
  34. * For compatibility with older compilers, \ref as_time_point and \ref get_time_point return
  35. * `system_clock` time points. These should be interpreted as local times rather
  36. * than UTC. Prefer using \ref as_local_time_point or \ref get_local_time_point
  37. * if your compiler supports them, as they provide more accurate semantics.
  38. * \n
  39. * As opposed to `time_point`, this type allows representing MySQL invalid and zero datetimes.
  40. * These values are allowed by MySQL but don't represent real time points.
  41. * \n
  42. * Note: using `std::chrono` time zone functionality under MSVC may cause memory leaks to be reported.
  43. * See <a href="https://github.com/microsoft/STL/issues/2047">this issue</a> for an explanation and
  44. * <a href="https://github.com/microsoft/STL/issues/2504">this other issue</a> for a workaround.
  45. */
  46. class datetime
  47. {
  48. public:
  49. /**
  50. * \brief A `std::chrono::time_point` that can represent any valid datetime, with microsecond resolution.
  51. * \details
  52. * Time points used by this class are always local times, even if defined
  53. * to use the system clock. Prefer using \ref local_time_point, if your compiler
  54. * supports it.
  55. */
  56. using time_point = std::chrono::
  57. time_point<std::chrono::system_clock, std::chrono::duration<std::int64_t, std::micro>>;
  58. #ifdef BOOST_MYSQL_HAS_LOCAL_TIME
  59. /**
  60. * \brief A `std::chrono::local_time` that can represent any valid datetime, with microsecond resolution.
  61. * \details Requires C++20 calendar types.
  62. */
  63. using local_time_point = std::chrono::local_time<std::chrono::duration<std::int64_t, std::micro>>;
  64. #endif
  65. /**
  66. * \brief Constructs a zero datetime.
  67. * \details Results in a datetime with all of its components set to zero.
  68. * The resulting object has `this->valid() == false`.
  69. * \par Exception safety
  70. * No-throw guarantee.
  71. */
  72. constexpr datetime() noexcept = default;
  73. /**
  74. * \brief Constructs a datetime from its individual components.
  75. * \details
  76. * Component values that yield invalid datetimes (like zero or out-of-range
  77. * values) are allowed, resulting in an object with `this->valid() == false`.
  78. * \par Exception safety
  79. * No-throw guarantee.
  80. */
  81. constexpr datetime(
  82. std::uint16_t year,
  83. std::uint8_t month,
  84. std::uint8_t day,
  85. std::uint8_t hour = 0,
  86. std::uint8_t minute = 0,
  87. std::uint8_t second = 0,
  88. std::uint32_t microsecond = 0
  89. ) noexcept
  90. : year_(year),
  91. month_(month),
  92. day_(day),
  93. hour_(hour),
  94. minute_(minute),
  95. second_(second),
  96. microsecond_(microsecond)
  97. {
  98. }
  99. /**
  100. * \brief Constructs a datetime from a `time_point`.
  101. * \par Exception safety
  102. * Strong guarantee. Throws on invalid input.
  103. * \throws std::out_of_range If the resulting `datetime` object would be
  104. * out of the [\ref min_datetime, \ref max_datetime] range.
  105. */
  106. BOOST_CXX14_CONSTEXPR inline explicit datetime(time_point tp);
  107. #ifdef BOOST_MYSQL_HAS_LOCAL_TIME
  108. /**
  109. * \brief Constructs a datetime from a `local_time_point`.
  110. * \details
  111. * Equivalent to constructing a `date` from a `time_point` with the same
  112. * `time_since_epoch()` as `tp`.
  113. * \n
  114. * Requires C++20 calendar types.
  115. *
  116. * \par Exception safety
  117. * Strong guarantee. Throws on invalid input.
  118. * \throws std::out_of_range If the resulting `datetime` object would be
  119. * out of the [\ref min_datetime, \ref max_datetime] range.
  120. */
  121. constexpr explicit datetime(local_time_point tp) : datetime(time_point(tp.time_since_epoch())) {}
  122. #endif
  123. /**
  124. * \brief Retrieves the year component.
  125. * \details
  126. * Represents the year number in the Gregorian calendar.
  127. * If `this->valid() == true`, this value is within the `[0, 9999]` range.
  128. *
  129. * \par Exception safety
  130. * No-throw guarantee.
  131. */
  132. constexpr std::uint16_t year() const noexcept { return year_; }
  133. /**
  134. * \brief Retrieves the month component (1-based).
  135. * \details
  136. * A value of 1 represents January.
  137. * If `this->valid() == true`, this value is within the `[1, 12]` range.
  138. *
  139. * \par Exception safety
  140. * No-throw guarantee.
  141. */
  142. constexpr std::uint8_t month() const noexcept { return month_; }
  143. /**
  144. * \brief Retrieves the day component (1-based).
  145. * \details
  146. * A value of 1 represents the first day of the month.
  147. * If `this->valid() == true`, this value is within the `[1, last_month_day]` range
  148. * (where `last_month_day` is the last day of the month).
  149. *
  150. * \par Exception safety
  151. * No-throw guarantee.
  152. */
  153. constexpr std::uint8_t day() const noexcept { return day_; }
  154. /**
  155. * \brief Retrieves the hour component.
  156. * \details If `this->valid() == true`, this value is within the `[0, 23]` range.
  157. * \par Exception safety
  158. * No-throw guarantee.
  159. */
  160. constexpr std::uint8_t hour() const noexcept { return hour_; }
  161. /**
  162. * \brief Retrieves the minute component.
  163. * \details If `this->valid() == true`, this value is within the `[0, 59]` range.
  164. * \par Exception safety
  165. * No-throw guarantee.
  166. */
  167. constexpr std::uint8_t minute() const noexcept { return minute_; }
  168. /**
  169. * \brief Retrieves the second component.
  170. * \details If `this->valid() == true`, this value is within the `[0, 59]` range.
  171. * \par Exception safety
  172. * No-throw guarantee.
  173. */
  174. constexpr std::uint8_t second() const noexcept { return second_; }
  175. /**
  176. * \brief Retrieves the microsecond component.
  177. * \details If `this->valid() == true`, this value is within the `[0, 999999]` range.
  178. * \par Exception safety
  179. * No-throw guarantee.
  180. */
  181. constexpr std::uint32_t microsecond() const noexcept { return microsecond_; }
  182. /**
  183. * \brief Returns `true` if `*this` represents a valid `time_point`.
  184. * \details If any of the individual components is out of range, the datetime
  185. * doesn't represent an actual `time_point` (e.g. `datetime(2020, 2, 30)`) or
  186. * the datetime is not in the [\ref min_date, \ref max_date] validity range,
  187. * returns `false`. Otherwise, returns `true`.
  188. *
  189. * \par Exception safety
  190. * No-throw guarantee.
  191. */
  192. constexpr bool valid() const noexcept
  193. {
  194. return detail::is_valid(year_, month_, day_) && hour_ <= detail::max_hour &&
  195. minute_ <= detail::max_min && second_ <= detail::max_sec && microsecond_ <= detail::max_micro;
  196. }
  197. /**
  198. * \brief Converts `*this` into a `time_point` (unchecked access).
  199. * \details
  200. * If your compiler supports it, prefer using \ref get_local_time_point,
  201. * as it provides more accurate semantics.
  202. *
  203. * \par Preconditions
  204. * `this->valid() == true` (if violated, results in undefined behavior).
  205. *
  206. * \par Exception safety
  207. * No-throw guarantee.
  208. */
  209. BOOST_CXX14_CONSTEXPR time_point get_time_point() const noexcept
  210. {
  211. BOOST_ASSERT(valid());
  212. return time_point(unch_get_micros());
  213. }
  214. /**
  215. * \brief Converts `*this` into a `time_point` (checked access).
  216. * \details
  217. * If your compiler supports it, prefer using \ref as_local_time_point,
  218. * as it provides more accurate semantics.
  219. *
  220. * \par Exception safety
  221. * Strong guarantee.
  222. * \throws std::invalid_argument If `!this->valid()`.
  223. */
  224. BOOST_CXX14_CONSTEXPR inline time_point as_time_point() const
  225. {
  226. if (!valid())
  227. BOOST_THROW_EXCEPTION(std::invalid_argument("datetime::as_time_point: invalid datetime"));
  228. return time_point(unch_get_micros());
  229. }
  230. #ifdef BOOST_MYSQL_HAS_LOCAL_TIME
  231. /**
  232. * \brief Converts `*this` into a `local_time_point` (unchecked access).
  233. * \details
  234. * The returned object has the same `time_since_epoch()` as `this->get_time_point()`,
  235. * but uses the `std::chrono::local_t` pseudo-clock to better represent
  236. * the absence of time zone information.
  237. * \n
  238. * Requires C++20 calendar types.
  239. *
  240. * \par Preconditions
  241. * `this->valid() == true` (if violated, results in undefined behavior).
  242. *
  243. * \par Exception safety
  244. * No-throw guarantee.
  245. */
  246. constexpr local_time_point get_local_time_point() const noexcept
  247. {
  248. BOOST_ASSERT(valid());
  249. return local_time_point(unch_get_micros());
  250. }
  251. /**
  252. * \brief Converts `*this` into a local time point (checked access).
  253. * \details
  254. * The returned object has the same `time_since_epoch()` as `this->as_time_point()`,
  255. * but uses the `std::chrono::local_t` pseudo-clock to better represent
  256. * the absence of time zone information.
  257. * \n
  258. * Requires C++20 calendar types.
  259. *
  260. * \par Exception safety
  261. * Strong guarantee.
  262. * \throws std::invalid_argument If `!this->valid()`.
  263. */
  264. constexpr local_time_point as_local_time_point() const
  265. {
  266. if (!valid())
  267. BOOST_THROW_EXCEPTION(std::invalid_argument("date::as_local_time_point: invalid date"));
  268. return local_time_point(unch_get_micros());
  269. }
  270. #endif
  271. /**
  272. * \brief Tests for equality.
  273. * \details Two datetimes are considered equal if all of its individual components
  274. * are equal. This function works for invalid datetimes, too.
  275. *
  276. * \par Exception safety
  277. * No-throw guarantee.
  278. */
  279. constexpr bool operator==(const datetime& rhs) const noexcept
  280. {
  281. return year_ == rhs.year_ && month_ == rhs.month_ && day_ == rhs.day_ && hour_ == rhs.hour_ &&
  282. minute_ == rhs.minute_ && second_ == rhs.second_ && microsecond_ == rhs.microsecond_;
  283. }
  284. /**
  285. * \brief Tests for inequality.
  286. * \par Exception safety
  287. * No-throw guarantee.
  288. */
  289. constexpr bool operator!=(const datetime& rhs) const noexcept { return !(*this == rhs); }
  290. /**
  291. * \brief Returns the current system time as a datetime object.
  292. * \par Exception safety
  293. * Strong guarantee. Only throws if obtaining the current time throws.
  294. */
  295. static datetime now()
  296. {
  297. auto now = time_point::clock::now();
  298. return datetime(std::chrono::time_point_cast<time_point::duration>(now));
  299. }
  300. private:
  301. std::uint16_t year_{};
  302. std::uint8_t month_{};
  303. std::uint8_t day_{};
  304. std::uint8_t hour_{};
  305. std::uint8_t minute_{};
  306. std::uint8_t second_{};
  307. std::uint32_t microsecond_{};
  308. BOOST_CXX14_CONSTEXPR inline time_point::duration unch_get_micros() const
  309. {
  310. // Doing time of day independently to prevent overflow
  311. days d(detail::ymd_to_days(year_, month_, day_));
  312. auto time_of_day = std::chrono::hours(hour_) + std::chrono::minutes(minute_) +
  313. std::chrono::seconds(second_) + std::chrono::microseconds(microsecond_);
  314. return time_point::duration(d) + time_of_day;
  315. }
  316. };
  317. /**
  318. * \relates datetime
  319. * \brief Streams a datetime.
  320. * \details This function works for invalid datetimes, too.
  321. */
  322. BOOST_MYSQL_DECL
  323. std::ostream& operator<<(std::ostream& os, const datetime& v);
  324. /// The minimum allowed value for \ref datetime.
  325. BOOST_INLINE_CONSTEXPR datetime min_datetime(0u, 1u, 1u);
  326. /// The maximum allowed value for \ref datetime.
  327. BOOST_INLINE_CONSTEXPR datetime max_datetime(9999u, 12u, 31u, 23u, 59u, 59u, 999999u);
  328. } // namespace mysql
  329. } // namespace boost
  330. // Implementations
  331. BOOST_CXX14_CONSTEXPR boost::mysql::datetime::datetime(time_point tp)
  332. {
  333. using std::chrono::duration_cast;
  334. using std::chrono::hours;
  335. using std::chrono::microseconds;
  336. using std::chrono::minutes;
  337. using std::chrono::seconds;
  338. // Avoiding using -= for durations as it's not constexpr until C++17
  339. auto input_dur = tp.time_since_epoch();
  340. auto rem = input_dur % days(1);
  341. auto num_days = duration_cast<days>(input_dur);
  342. if (rem.count() < 0)
  343. {
  344. rem = rem + days(1);
  345. num_days = num_days - days(1);
  346. }
  347. auto num_hours = duration_cast<hours>(rem);
  348. rem = rem - num_hours;
  349. auto num_minutes = duration_cast<minutes>(rem);
  350. rem = rem - num_minutes;
  351. auto num_seconds = duration_cast<seconds>(rem);
  352. rem = rem - num_seconds;
  353. auto num_microseconds = duration_cast<microseconds>(rem);
  354. BOOST_ASSERT(num_hours.count() >= 0 && num_hours.count() <= detail::max_hour);
  355. BOOST_ASSERT(num_minutes.count() >= 0 && num_minutes.count() <= detail::max_min);
  356. BOOST_ASSERT(num_seconds.count() >= 0 && num_seconds.count() <= detail::max_sec);
  357. BOOST_ASSERT(num_microseconds.count() >= 0 && num_microseconds.count() <= detail::max_micro);
  358. bool ok = detail::days_to_ymd(num_days.count(), year_, month_, day_);
  359. if (!ok)
  360. BOOST_THROW_EXCEPTION(std::out_of_range("datetime::datetime: time_point was out of range"));
  361. microsecond_ = static_cast<std::uint32_t>(num_microseconds.count());
  362. second_ = static_cast<std::uint8_t>(num_seconds.count());
  363. minute_ = static_cast<std::uint8_t>(num_minutes.count());
  364. hour_ = static_cast<std::uint8_t>(num_hours.count());
  365. }
  366. #ifdef BOOST_MYSQL_HEADER_ONLY
  367. #include <boost/mysql/impl/datetime.ipp>
  368. #endif
  369. #endif