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