userver: userver/storages/postgres/io/chrono.hpp Source File
Loading...
Searching...
No Matches
chrono.hpp
Go to the documentation of this file.
1#pragma once
2
3/// @file userver/storages/postgres/io/chrono.hpp
4/// @brief Timestamp (std::chrono::*) I/O support
5/// @ingroup userver_postgres_parse_and_format
6
7#include <chrono>
8#include <functional>
9#include <iosfwd>
10#include <limits>
11
12#include <userver/dump/fwd.hpp>
13#include <userver/storages/postgres/io/buffer_io.hpp>
14#include <userver/storages/postgres/io/buffer_io_base.hpp>
15#include <userver/storages/postgres/io/interval.hpp>
16#include <userver/storages/postgres/io/transform_io.hpp>
17#include <userver/storages/postgres/io/type_mapping.hpp>
18#include <userver/utils/strong_typedef.hpp>
19
20USERVER_NAMESPACE_BEGIN
21
22namespace storages::postgres {
23
24namespace detail {
25struct TimePointTzTag {};
26struct TimePointWithoutTzTag {};
27} // namespace detail
28
29using ClockType = std::chrono::system_clock;
30using TimePoint = ClockType::time_point;
31using IntervalType = std::chrono::microseconds;
32
33/// @brief Corresponds to TIMESTAMP WITH TIME ZONE database type.
34///
35/// @warning Make sure that no unwanted
36/// `TIMESTAMP` <> `TIMESTAMP WITHOUT TIME ZONE` conversions are performed on
37/// the DB side, see @ref pg_timestamp.
38///
39/// @see @ref Now
40struct TimePointTz final : public USERVER_NAMESPACE::utils::StrongTypedef<
41 detail::TimePointTzTag,
42 TimePoint,
43 USERVER_NAMESPACE::utils::StrongTypedefOps::kCompareTransparent> {
44 using StrongTypedef::StrongTypedef;
45
46 /*implicit*/ operator TimePoint() const { return GetUnderlying(); }
47};
48
49/// @brief Logging support for TimePointTz.
50logging::LogHelper& operator<<(logging::LogHelper&, TimePointTz);
51
52/// @brief gtest logging support for TimePointTz.
53std::ostream& operator<<(std::ostream&, TimePointTz);
54
55/// @brief Corresponds to TIMESTAMP WITHOUT TIME ZONE database type.
56///
57/// @warning Make sure that no unwanted
58/// `TIMESTAMP` <> `TIMESTAMP WITHOUT TIME ZONE` conversions are performed on
59/// the DB side, see @ref pg_timestamp.
60///
61/// @see @ref NowWithoutTz
62struct TimePointWithoutTz final : public USERVER_NAMESPACE::utils::StrongTypedef<
63 detail::TimePointWithoutTzTag,
64 TimePoint,
65 USERVER_NAMESPACE::utils::StrongTypedefOps::kCompareTransparent> {
66 using StrongTypedef::StrongTypedef;
67
68 /*implicit*/ operator TimePoint() const { return GetUnderlying(); }
69};
70
71/// @brief Logging support for TimePointWithoutTz.
72logging::LogHelper& operator<<(logging::LogHelper&, TimePointWithoutTz);
73
74/// @brief gtest logging support for TimePointWithoutTz.
75std::ostream& operator<<(std::ostream&, TimePointWithoutTz);
76
77/// @brief @ref utils::datetime::Now "Mockable" now that is written as
78/// `TIMESTAMP WITH TIME ZONE`.
79///
80/// @see pg_timestamp
81TimePointTz Now();
82
83/// @brief @ref utils::datetime::Now "Mockable" now that is written as
84/// `TIMESTAMP WITHOUT TIME ZONE`.
85///
86/// @see pg_timestamp
87TimePointWithoutTz NowWithoutTz();
88
89/// Postgres epoch timestamp (2000-01-01 00:00 UTC)
91
92/// Constant equivalent to PostgreSQL 'infinity'::timestamp, a time point that
93/// is later than all other time points
94/// https://www.postgresql.org/docs/current/datatype-datetime.html#DATATYPE-DATETIME-SPECIAL-TABLE
95inline constexpr TimePoint kTimestampPositiveInfinity = TimePoint::max();
96/// Constant equivalent to PostgreSQL '-infinity'::timestamp, a time point that
97/// is earlier than all other time points
98/// https://www.postgresql.org/docs/current/datatype-datetime.html#DATATYPE-DATETIME-SPECIAL-TABLE
99inline constexpr TimePoint kTimestampNegativeInfinity = TimePoint::min();
100
101namespace io {
102
103namespace detail {
104
105template <typename Duration, typename Buffer>
106void DoFormatTimePoint(std::chrono::time_point<ClockType, Duration> value, const UserTypes& types, Buffer& buf) {
107 static const auto pg_epoch = std::chrono::time_point_cast<Duration>(PostgresEpochTimePoint());
108 if (value == kTimestampPositiveInfinity) {
109 WriteBuffer(types, buf, std::numeric_limits<Bigint>::max());
110 } else if (value == kTimestampNegativeInfinity) {
111 WriteBuffer(types, buf, std::numeric_limits<Bigint>::min());
112 } else {
113 const auto tmp = std::chrono::duration_cast<std::chrono::microseconds>(value - pg_epoch).count();
114 WriteBuffer(types, buf, tmp);
115 }
116}
117
118template <typename Duration>
119void DoParseTimePoint(std::chrono::time_point<ClockType, Duration>& value, const FieldBuffer& buffer) {
120 static const auto pg_epoch = std::chrono::time_point_cast<Duration>(PostgresEpochTimePoint());
121 Bigint usec{0};
122 ReadBuffer(buffer, usec);
123 if (usec == std::numeric_limits<Bigint>::max()) {
124 value = kTimestampPositiveInfinity;
125 } else if (usec == std::numeric_limits<Bigint>::min()) {
126 value = kTimestampNegativeInfinity;
127 } else {
128 value = pg_epoch + std::chrono::microseconds{usec};
129 }
130}
131
132template <typename T>
133struct TimePointStrongTypedefFormatter {
134 const T value;
135
136 explicit TimePointStrongTypedefFormatter(T val) : value{val} {}
137
138 template <typename Buffer>
139 void operator()(const UserTypes& types, Buffer& buf) const {
140 detail::DoFormatTimePoint(value.GetUnderlying(), types, buf);
141 }
142};
143
144template <typename T>
145struct TimePointStrongTypedefParser : BufferParserBase<T> {
146 using BufferParserBase<T>::BufferParserBase;
147
148 void operator()(const FieldBuffer& buffer) { detail::DoParseTimePoint(this->value.GetUnderlying(), buffer); }
149};
150
151} // namespace detail
152
153/// @brief Binary formatter for TimePointTz.
154template <>
157};
158
159/// @brief Binary formatter for TimePointWithoutTz.
160template <>
163};
164
165/// @brief Binary parser for TimePointTz.
166template <>
169};
170
171/// @brief Binary parser for TimePointWithoutTz.
172template <>
175};
176
177/// @cond
178
179// @brief Binary formatter for TimePoint. Implicitly converts
180// TimePointWithoutTz to TimePoint.
181template <>
183 using ValueType = TimePoint;
184
185 const ValueType value;
186
187 explicit BufferFormatter(ValueType val) : value{val} {}
188
189 template <typename Buffer>
190 void operator()(const UserTypes& types, Buffer& buf) const {
191#if !USERVER_POSTGRES_ENABLE_LEGACY_TIMESTAMP
192 static_assert(
193 sizeof(Buffer) == 0,
194 "====================> userver: Writing "
195 "std::chrono::system_clock::time_point is not supported. "
196 "Rewrite using the TimePointWithoutTz or TimePointTz types, "
197 "or define USERVER_POSTGRES_ENABLE_LEGACY_TIMESTAMP to 1."
198 );
199#endif
201 }
202};
203
204/// @endcond
205
206/// @brief Binary parser for TimePoint. Implicitly converts TimePoint
207/// to TimePointWithoutTz.
208template <>
211
212 void operator()(const FieldBuffer& buffer) { detail::DoParseTimePoint(this->value, buffer); }
213};
214
215namespace detail {
216
217template <typename Rep, typename Period>
218struct DurationIntervalCvt {
219 using UserType = std::chrono::duration<Rep, Period>;
220 UserType operator()(const Interval& wire_val) const {
221 return std::chrono::duration_cast<UserType>(wire_val.GetDuration());
222 }
223 Interval operator()(const UserType& user_val) const {
224 return Interval{std::chrono::duration_cast<IntervalType>(user_val)};
225 }
226};
227
228} // namespace detail
229
230namespace traits {
231
232/// @brief Binary formatter for std::chrono::duration
233template <typename Rep, typename Period>
235 using type = TransformFormatter<
239};
240
241/// @brief Binary parser for std::chrono::duration
242template <typename Rep, typename Period>
244 using type = TransformParser<
248};
249
250} // namespace traits
251
252template <>
253struct CppToSystemPg<TimePointTz> : PredefinedOid<PredefinedOids::kTimestamptz> {};
254
255template <>
256struct CppToSystemPg<TimePointWithoutTz> : PredefinedOid<PredefinedOids::kTimestamp> {};
257
258// For raw time_point, only parsing is normally allowed, not serialization.
259template <typename Duration>
260struct CppToSystemPg<std::chrono::time_point<ClockType, Duration>> : PredefinedOid<PredefinedOids::kTimestamp> {};
261
262template <typename Rep, typename Period>
263struct CppToSystemPg<std::chrono::duration<Rep, Period>> : PredefinedOid<PredefinedOids::kInterval> {};
264
265} // namespace io
266
267/// @brief @ref scripts/docs/en/userver/cache_dumps.md "Cache dumps" support
268/// for storages::postgres::TimePointTz.
269/// @{
270void Write(dump::Writer& writer, const TimePointTz& value);
271TimePointTz Read(dump::Reader& reader, dump::To<TimePointTz>);
272/// @}
273
274/// @brief @ref scripts/docs/en/userver/cache_dumps.md "Cache dumps" support
275/// for storages::postgres::TimePointWithoutTz.
276/// @{
277void Write(dump::Writer& writer, const TimePointWithoutTz& value);
278TimePointWithoutTz Read(dump::Reader& reader, dump::To<TimePointWithoutTz>);
279/// @}
280
281} // namespace storages::postgres
282
283USERVER_NAMESPACE_END
284
285/// @brief `std::hash` support for storages::postgres::TimePointTz.
286template <>
287struct std::hash<USERVER_NAMESPACE::storages::postgres::TimePointTz> {
288 std::size_t operator()(const USERVER_NAMESPACE::storages::postgres::TimePointTz& v) //
289 const noexcept {
290 return std::hash<USERVER_NAMESPACE::storages::postgres::TimePoint::duration::rep>{}(
291 v.GetUnderlying().time_since_epoch().count()
292 );
293 }
294};
295
296/// @brief `std::hash` support for storages::postgres::TimePointWithoutTz.
297template <>
298struct std::hash<USERVER_NAMESPACE::storages::postgres::TimePointWithoutTz> {
299 std::size_t operator()(const USERVER_NAMESPACE::storages::postgres::TimePointWithoutTz& v) //
300 const noexcept {
301 return std::hash<USERVER_NAMESPACE::storages::postgres::TimePoint::duration::rep>{}(
302 v.GetUnderlying().time_since_epoch().count()
303 );
304 }
305};