userver: userver/ugrpc/datetime_utils.hpp Source File
All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Friends Macros Modules Pages Concepts
datetime_utils.hpp
Go to the documentation of this file.
1#pragma once
2
3/// @file
4/// @brief Utilities for @c google::protobuf::Timestamp, @c google::type::Date, and @c google::protobuf::Duration types.
5
6#include <chrono>
7#include <stdexcept>
8#include <version>
9
10#include <google/protobuf/duration.pb.h>
11#include <google/protobuf/timestamp.pb.h>
12#include <google/type/date.pb.h>
13
14#include <userver/formats/json_fwd.hpp>
15#include <userver/formats/parse/to.hpp>
16#include <userver/formats/serialize/to.hpp>
17#include <userver/utils/assert.hpp>
18#include <userver/utils/datetime/cpp_20_calendar.hpp>
19#include <userver/utils/datetime/date.hpp>
20
21USERVER_NAMESPACE_BEGIN
22
23namespace ugrpc {
24
25/// @brief Checks if @c google::protobuf::Timestamp contains a valid value according to protobuf documentation.
26///
27/// The timestamp must be from 0001-01-01T00:00:00Z to 9999-12-31T23:59:59Z inclusive
28/// and 'nanos' must be from 0 to 999,999,999 inclusive.
29bool IsValid(const google::protobuf::Timestamp& grpc_ts);
30
31/// @brief Exception thrown when timestamp conversion functions receive/produce invalid @c google::protobuf::Timestamp
32/// accrording to @c IsValid or otherwise lead to undefined behavior due to integer overflow.
33class TimestampConversionError : public std::overflow_error {
34 using std::overflow_error::overflow_error;
35};
36
37/// @brief Creates @c google::protobuf::Timestamp from @c std::chrono::time_point.
38///
39/// @throws TimestampConversionError if the value is not valid according to @c IsValid.
40template <class Duration>
41google::protobuf::Timestamp ToProtoTimestamp(
42 const std::chrono::time_point<std::chrono::system_clock, Duration>& system_tp
43) {
44 const auto seconds = std::chrono::duration_cast<std::chrono::seconds>(system_tp.time_since_epoch());
45 const auto nanos = std::chrono::duration_cast<std::chrono::nanoseconds>(system_tp.time_since_epoch() - seconds);
46
47 google::protobuf::Timestamp timestamp;
48 timestamp.set_seconds(seconds.count());
49 timestamp.set_nanos(nanos.count());
50
51 if (!IsValid(timestamp)) {
52 throw TimestampConversionError("system_tp is invalid");
53 }
54 return timestamp;
55}
56
57/// @brief Creates @c std::chrono::system_clock::time_point from @c google::protobuf::Timestamp.
58///
59/// @throws TimestampConversionError if the value is not valid according to @c IsValid or does not fit the time_point.
60template <class Duration = std::chrono::system_clock::duration>
61std::chrono::time_point<std::chrono::system_clock, Duration> ToTimePoint(const google::protobuf::Timestamp& grpc_ts) {
62 if (!IsValid(grpc_ts)) {
63 throw TimestampConversionError("grpc_ts is invalid");
64 }
65
66 const std::chrono::seconds seconds(grpc_ts.seconds());
67 // Equality is important here. Otherwise, grpc_duration.seconds() == std::chrono::seconds::max()
68 // will lead to overflow if grpc_duration.nanos() != 0.
69 if (seconds >= std::chrono::duration_cast<std::chrono::seconds>(Duration::max())) {
70 throw TimestampConversionError("grpc_ts does not fit the output type");
71 }
72
73 return std::chrono::time_point<std::chrono::system_clock, Duration>(std::chrono::duration_cast<Duration>(
74 seconds + std::chrono::duration_cast<Duration>(std::chrono::nanoseconds(grpc_ts.nanos()))
75 ));
76}
77
78/// @brief Returns current (possibly, mocked) timestamp as a @c google::protobuf::Timestamp.
79google::protobuf::Timestamp NowTimestamp();
80
81/// @brief Checks if @c google::type::Date contains a valid value according to protobuf documentation.
82///
83/// Year must be from 1 to 9999.
84/// Month must be from 1 to 12.
85/// Day must be from 1 to 31 and valid for the year and month.
86bool IsValid(const google::type::Date& grpc_date);
87
88/// @brief Exception thrown when date conversion functions receive/produce invalid @c google::type::Date
89/// accrording to @c IsValid or otherwise lead to undefined behavior due to integer overflow.
90class DateConversionError : public std::overflow_error {
91 using std::overflow_error::overflow_error;
92};
93
94#if __cpp_lib_chrono >= 201907L
95
96/// @brief Creates @c google::type::Date from @c std::chrono::year_month_day.
97///
98/// @throws DateConversionError if the value is not valid according to @c IsValid.
99google::type::Date ToProtoDate(const std::chrono::year_month_day& system_date);
100
101/// @brief Creates @c std::chrono::year_month_day from @c google::type::Date.
102///
103/// @throws DateConversionError if the value is not valid according to @c IsValid.
104std::chrono::year_month_day ToYearMonthDay(const google::type::Date& grpc_date);
105
106#endif
107
108/// @brief Creates @c google::type::Date from @c utils::datetime::Date.
109///
110/// @throws DateConversionError if the value is not valid according to @c IsValid.
111google::type::Date ToProtoDate(const utils::datetime::Date& utils_date);
112
113/// @brief Creates @c utils::datetime::Date from @c google::type::Date.
114///
115/// @throws DateConversionError if the value is not valid according to @c IsValid.
116utils::datetime::Date ToUtilsDate(const google::type::Date& grpc_date);
117
118/// @brief Creates @c google::type::Date from @c std::chrono::time_point.
119///
120/// @throws DateConversionError if the value is not valid according to @c IsValid.
121template <class Duration>
122google::type::Date ToProtoDate(const std::chrono::time_point<std::chrono::system_clock, Duration>& system_tp) {
123 return ToProtoDate(utils::datetime::Date(std::chrono::floor<utils::datetime::Date::Days>(system_tp)));
124}
125
126/// @brief Creates @c std::chrono::system_clock::time_point from @c google::type::Date.
127///
128/// @throws DateConversionError if the value is not valid according to @c IsValid.
129std::chrono::time_point<std::chrono::system_clock, utils::datetime::Days> ToTimePoint(
130 const google::type::Date& grpc_date
131);
132
133/// @brief Returns current (possibly, mocked) timestamp as a @c google::type::Date.
134google::type::Date NowDate();
135
136/// @brief Checks if @c google::protobuf::Duration contains a valid value according to protobuf documentation.
137///
138/// Seconds must be from -315,576,000,000 to +315,576,000,000 inclusive.
139/// Note: these bounds are computed from: 60 sec/min * 60 min/hr * 24 hr/day * 365.25 days/year * 10000 years
140/// For durations of one second or more, a non-zero value for the `nanos` field must be of the same sign as `seconds`.
141/// Nanos must also be from -999,999,999 to +999,999,999 inclusive.
142bool IsValid(const google::protobuf::Duration& grpc_duration);
143
144/// @brief Exception thrown when duration conversion functions receive/produce invalid @c google::protobuf::Duration
145/// accrording to @c IsValid or otherwise lead to undefined behavior due to integer overflow.
146class DurationConversionError : public std::overflow_error {
147 using std::overflow_error::overflow_error;
148};
149
150/// @brief Creates @c std::chrono::duration from @c google::protobuf::Duration.
151///
152/// @throws DurationConversionError if the value is not valid according to @c IsValid or does not fit the Duration.
153template <class Duration = std::chrono::microseconds>
154Duration ToDuration(const google::protobuf::Duration& grpc_duration) {
155 if (!IsValid(grpc_duration)) {
156 throw DurationConversionError("grpc_duration is invalid");
157 }
158
159 const std::chrono::seconds seconds(grpc_duration.seconds());
160 // Equality is important here. Otherwise, grpc_duration.seconds() == std::chrono::seconds::max()
161 // will lead to overflow if grpc_duration.nanos() != 0.
162 if (seconds >= std::chrono::duration_cast<std::chrono::seconds>(Duration::max())) {
163 throw DurationConversionError("grpc_duration does not fit the output type");
164 }
165 return std::chrono::duration_cast<Duration>(
166 seconds + std::chrono::duration_cast<Duration>(std::chrono::nanoseconds(grpc_duration.nanos()))
167 );
168}
169
170/// @brief Creates @c google::protobuf::Duration from @c std::chrono::duration.
171///
172/// @throws DurationConversionError if the value is not valid according to @c IsValid.
173template <class Rep, class Period>
174google::protobuf::Duration ToProtoDuration(const std::chrono::duration<Rep, Period>& duration) {
175 const auto seconds = std::chrono::duration_cast<std::chrono::seconds>(duration);
176 const auto nanos = std::chrono::duration_cast<std::chrono::nanoseconds>(duration - seconds);
177
178 google::protobuf::Duration result;
179 result.set_seconds(seconds.count());
180 result.set_nanos(nanos.count());
181 if (!IsValid(result)) {
182 throw DurationConversionError("duration is invalid");
183 }
184 return result;
185}
186
187} // namespace ugrpc
188
189namespace formats::parse {
190
191google::protobuf::Timestamp Parse(const formats::json::Value& json, formats::parse::To<google::protobuf::Timestamp>);
192
193google::type::Date Parse(const formats::json::Value& json, formats::parse::To<google::type::Date>);
194
195} // namespace formats::parse
196
197namespace formats::serialize {
198
199formats::json::Value Serialize(const google::protobuf::Timestamp& value, formats::serialize::To<formats::json::Value>);
200
201formats::json::Value Serialize(const google::type::Date& value, formats::serialize::To<formats::json::Value>);
202
203} // namespace formats::serialize
204
205USERVER_NAMESPACE_END