13#include <fmt/format.h>
15#include <userver/utils/fmt_compat.hpp>
17USERVER_NAMESPACE_BEGIN
73template <
typename Duration>
76template <
typename Rep,
typename Period>
77class TimeOfDay<std::chrono::duration<Rep, Period>> {
79 using DurationType = std::chrono::duration<Rep, Period>;
81 constexpr TimeOfDay()
noexcept =
default;
82 constexpr explicit TimeOfDay(DurationType)
noexcept;
83 template <
typename ORep,
typename OPeriod>
84 constexpr explicit TimeOfDay(std::chrono::duration<ORep, OPeriod>)
noexcept;
85 constexpr explicit TimeOfDay(std::string_view);
90#ifdef __cpp_lib_three_way_comparison
91 constexpr auto operator<=>(
const TimeOfDay&)
const =
default;
93 constexpr bool operator==(
const TimeOfDay&)
const;
94 constexpr bool operator!=(
const TimeOfDay&)
const;
95 constexpr bool operator<(
const TimeOfDay&)
const;
96 constexpr bool operator<=(
const TimeOfDay&)
const;
97 constexpr bool operator>(
const TimeOfDay&)
const;
98 constexpr bool operator>=(
const TimeOfDay&)
const;
112 constexpr DurationType
Subseconds()
const noexcept;
122 DurationType since_midnight_{};
128template <
typename LDuration,
typename RDuration>
129auto operator-(TimeOfDay<LDuration> lhs, TimeOfDay<RDuration> rhs) {
130 return lhs.SinceMidnight() - rhs.SinceMidnight();
133template <
typename Duration,
typename Rep,
typename Period>
134TimeOfDay<Duration> operator+(TimeOfDay<Duration> lhs, std::chrono::duration<Rep, Period> rhs) {
135 return TimeOfDay<Duration>{lhs.SinceMidnight() + rhs};
138template <
typename Duration,
typename Rep,
typename Period>
139TimeOfDay<Duration> operator-(TimeOfDay<Duration> lhs, std::chrono::duration<Rep, Period> rhs) {
140 return TimeOfDay<Duration>{lhs.SinceMidnight() - rhs};
144template <
typename Duration>
145logging::LogHelper& operator<<(
logging::LogHelper& lh, TimeOfDay<Duration> value) {
146 lh << fmt::to_string(value);
151template <
typename Rep,
typename Period>
152inline constexpr std::chrono::duration<Rep, Period> kTwentyFourHours =
153 std::chrono::duration_cast<std::chrono::duration<Rep, Period>>(std::chrono::hours{24});
155template <
typename Rep,
typename Period,
typename ORep = Rep,
typename OPeriod = Period>
156constexpr std::chrono::duration<Rep, Period> NormalizeTimeOfDay(std::chrono::duration<ORep, OPeriod> d) {
157 auto res = std::chrono::duration_cast<std::chrono::duration<Rep, Period>>(d) % kTwentyFourHours<Rep, Period>;
158 return res.count() < 0 ? res + kTwentyFourHours<Rep, Period> : res;
161template <
typename Ratio>
162inline constexpr std::size_t kDecimalPositions = 0;
164inline constexpr std::size_t kDecimalPositions<std::milli> = 3;
166inline constexpr std::size_t kDecimalPositions<std::micro> = 6;
168inline constexpr const std::size_t kDecimalPositions<std::nano> = 9;
170constexpr std::intmax_t MissingDigits(std::size_t n) {
173 constexpr std::intmax_t powers[]{
190template <
typename Ratio>
191struct HasMinutes : std::false_type {};
193template <intmax_t Num, intmax_t Den>
194struct HasMinutes<std::ratio<Num, Den>> : std::integral_constant<
bool, (Num <= 60LL)> {};
196template <
typename Rep,
typename Period>
197struct HasMinutes<std::chrono::duration<Rep, Period>> : HasMinutes<Period> {};
199template <
typename Duration>
200struct HasMinutes<TimeOfDay<Duration>> : HasMinutes<Duration> {};
203constexpr inline bool kHasMinutes = HasMinutes<T>{};
205template <
typename Ratio>
206struct HasSeconds : std::false_type {};
208template <intmax_t Num, intmax_t Den>
209struct HasSeconds<std::ratio<Num, Den>> : std::integral_constant<
bool, (Num == 1)> {};
211template <
typename Rep,
typename Period>
212struct HasSeconds<std::chrono::duration<Rep, Period>> : HasSeconds<Period> {};
214template <
typename Duration>
215struct HasSeconds<TimeOfDay<Duration>> : HasSeconds<Duration> {};
218constexpr inline bool kHasSeconds = HasSeconds<T>{};
220template <
typename Ratio>
221struct HasSubseconds : std::false_type {};
223template <intmax_t Num, intmax_t Den>
224struct HasSubseconds<std::ratio<Num, Den>> : std::integral_constant<
bool, (Den > 1)> {};
226template <
typename Rep,
typename Period>
227struct HasSubseconds<std::chrono::duration<Rep, Period>> : HasSubseconds<Period> {};
229template <
typename Duration>
230struct HasSubseconds<TimeOfDay<Duration>> : HasSubseconds<Duration> {};
233constexpr inline bool kHasSubseconds = HasSubseconds<T>{};
235template <
typename Rep,
typename Period>
236class TimeOfDayParser {
238 using DurationType = std::chrono::duration<Rep, Period>;
240 constexpr DurationType operator()(std::string_view str) {
244 if (position_ >= kSeconds)
245 throw std::runtime_error{fmt::format(
"Extra colon in TimeOfDay string `{}`", str)};
246 AssignCurrentPosition(str);
247 position_ =
static_cast<TimePart>(position_ + 1);
250 if (position_ != kSeconds)
251 throw std::runtime_error{fmt::format(
"Unexpected decimal point in TimeOfDay string `{}`", str)};
252 AssignCurrentPosition(str);
253 position_ =
static_cast<TimePart>(position_ + 1);
256 if (!std::isdigit(c))
257 throw std::runtime_error{
258 fmt::format(
"Unexpected character {} in TimeOfDay string `{}`", c, str)};
259 if (position_ == kOverflow) {
261 }
else if (position_ == kSubseconds) {
262 if (digit_count_ >= kDecimalPositions<Period>) {
263 AssignCurrentPosition(str);
264 position_ = kOverflow;
267 }
else if (digit_count_ >= 2) {
268 throw std::runtime_error{fmt::format(
"Too much digits in TimeOfDay string `{}`", str)};
271 current_number_ = current_number_ * 10 + (c -
'0');
275 if (position_ == kHour)
276 throw std::runtime_error{fmt::format(
277 "Expected to have at least minutes after hours in "
278 "TimeOfDay string `{}`",
281 AssignCurrentPosition(str);
283 auto sum = hours_ + minutes_ + seconds_ + subseconds_;
284 if (sum > kTwentyFourHours<Rep, Period>) {
285 throw std::runtime_error(fmt::format(
"TimeOfDay value {} is out of range [00:00, 24:00)", str));
287 return NormalizeTimeOfDay<Rep, Period>(sum);
291 enum TimePart { kHour, kMinutes, kSeconds, kSubseconds, kOverflow };
293 void AssignCurrentPosition(std::string_view str) {
296 if (digit_count_ < 1)
297 throw std::runtime_error{fmt::format(
"Not enough digits for hours in TimeOfDay string `{}`", str)};
298 if (current_number_ > 24)
299 throw std::runtime_error{fmt::format(
"Invalid value for hours in TimeOfDay string `{}`", str)};
300 hours_ = std::chrono::hours{current_number_};
304 if (digit_count_ != 2)
305 throw std::runtime_error{
306 fmt::format(
"Not enough digits for minutes in TimeOfDay string `{}`", str)};
307 if (current_number_ > 59)
308 throw std::runtime_error{fmt::format(
"Invalid value for minutes in TimeOfDay string `{}`", str)};
309 minutes_ = std::chrono::minutes{current_number_};
313 if (digit_count_ != 2)
314 throw std::runtime_error{
315 fmt::format(
"Not enough digits for seconds in TimeOfDay string `{}`", str)};
316 if (current_number_ > 59)
317 throw std::runtime_error{fmt::format(
"Invalid value for seconds in TimeOfDay string `{}`", str)};
318 seconds_ = std::chrono::seconds{current_number_};
322 if (digit_count_ < 1)
323 throw std::runtime_error{
324 fmt::format(
"Not enough digits for subseconds in TimeOfDay string `{}`", str)};
325 if constexpr (kHasSubseconds<Period>) {
327 if (digit_count_ < kDecimalPositions<Period>) {
328 current_number_ *= MissingDigits(kDecimalPositions<Period> - digit_count_);
330 subseconds_ = DurationType{current_number_};
342 TimePart position_ = kHour;
343 std::chrono::hours hours_{0};
344 std::chrono::minutes minutes_{0};
345 std::chrono::seconds seconds_{0};
346 DurationType subseconds_{0};
348 std::size_t digit_count_{0};
349 std::size_t current_number_{0};
353inline constexpr std::string_view kLongHourFormat =
"{0:0>#2d}";
355inline constexpr std::string_view kMinutesFormat =
"{1:0>#2d}";
357inline constexpr std::string_view kSecondsFormat =
"{2:0>#2d}";
359inline constexpr std::string_view kSubsecondsFormat =
"{3}";
361template <
typename Ratio>
362constexpr inline std::string_view kSubsecondsPreformat =
".0";
364inline constexpr std::string_view kSubsecondsPreformat<std::milli> =
".{:0>#3d}";
366inline constexpr std::string_view kSubsecondsPreformat<std::micro> =
".{:0>#6d}";
368inline constexpr std::string_view kSubsecondsPreformat<std::nano> =
".{:0>#9d}";
371template <
typename Ratio>
372inline constexpr std::array<std::string_view, 5> kDefaultFormat{
373 {kLongHourFormat,
":", kMinutesFormat,
":", kSecondsFormat}};
377inline constexpr std::array<std::string_view, 3> kDefaultFormat<std::ratio<60, 1>>{
378 {kLongHourFormat,
":", kMinutesFormat}};
382inline constexpr std::array<std::string_view, 3> kDefaultFormat<std::ratio<3600, 1>>{
383 {kLongHourFormat,
":", kMinutesFormat}};
387template <
typename Rep,
typename Period>
388constexpr TimeOfDay<std::chrono::duration<Rep, Period>>::TimeOfDay(DurationType d)
noexcept
389 : since_midnight_{detail::NormalizeTimeOfDay<Rep, Period>(d)} {}
392template <
typename ORep,
typename OPeriod>
393constexpr TimeOfDay<std::chrono::duration<Rep, Period>>::TimeOfDay(std::chrono::duration<ORep, OPeriod> d)
noexcept
394 : since_midnight_{detail::NormalizeTimeOfDay<Rep, Period>(d)} {}
396template <
typename Rep,
typename Period>
397constexpr TimeOfDay<std::chrono::duration<Rep, Period>>::TimeOfDay(std::string_view str)
398 : since_midnight_{detail::TimeOfDayParser<Rep, Period>{}(str)} {}
400#ifndef __cpp_lib_three_way_comparison
401template <
typename Rep,
typename Period>
402constexpr bool TimeOfDay<std::chrono::duration<Rep, Period>>::operator==(
const TimeOfDay& rhs)
const {
403 return since_midnight_ == rhs.since_midnight_;
406template <
typename Rep,
typename Period>
407constexpr bool TimeOfDay<std::chrono::duration<Rep, Period>>::operator!=(
const TimeOfDay& rhs)
const {
408 return !(*
this == rhs);
411template <
typename Rep,
typename Period>
412constexpr bool TimeOfDay<std::chrono::duration<Rep, Period>>::operator<(
const TimeOfDay& rhs)
const {
413 return since_midnight_ < rhs.since_midnight_;
416template <
typename Rep,
typename Period>
417constexpr bool TimeOfDay<std::chrono::duration<Rep, Period>>::operator<=(
const TimeOfDay& rhs)
const {
418 return since_midnight_ <= rhs.since_midnight_;
421template <
typename Rep,
typename Period>
422constexpr bool TimeOfDay<std::chrono::duration<Rep, Period>>::operator>(
const TimeOfDay& rhs)
const {
423 return since_midnight_ > rhs.since_midnight_;
426template <
typename Rep,
typename Period>
427constexpr bool TimeOfDay<std::chrono::duration<Rep, Period>>::operator>=(
const TimeOfDay& rhs)
const {
428 return since_midnight_ >= rhs.since_midnight_;
432template <
typename Rep,
typename Period>
434 return std::chrono::duration_cast<std::chrono::hours>(since_midnight_);
437template <
typename Rep,
typename Period>
439 if constexpr (detail::kHasMinutes<Period>) {
440 return std::chrono::duration_cast<std::chrono::minutes>(since_midnight_) -
441 std::chrono::duration_cast<std::chrono::minutes>(Hours());
443 return std::chrono::minutes{0};
447template <
typename Rep,
typename Period>
449 if constexpr (detail::kHasSeconds<Period>) {
450 return std::chrono::duration_cast<std::chrono::seconds>(since_midnight_) -
451 std::chrono::duration_cast<std::chrono::seconds>(
452 std::chrono::duration_cast<std::chrono::minutes>(since_midnight_)
455 return std::chrono::seconds{0};
459template <
typename Rep,
typename Period>
460constexpr typename TimeOfDay<std::chrono::duration<Rep, Period>>::DurationType
461TimeOfDay<std::chrono::duration<Rep, Period>>::
Subseconds()
const noexcept {
462 if constexpr (detail::kHasSubseconds<Period>) {
463 return since_midnight_ - std::chrono::duration_cast<std::chrono::seconds>(since_midnight_);
465 return DurationType{0};
469template <
typename Rep,
typename Period>
470constexpr typename TimeOfDay<std::chrono::duration<Rep, Period>>::DurationType
471TimeOfDay<std::chrono::duration<Rep, Period>>::
SinceMidnight()
const noexcept {
472 return since_midnight_;
475template <
typename Rep,
typename Period>
476constexpr TimeOfDay<std::chrono::duration<Rep, Period>> TimeOfDay<std::chrono::duration<Rep, Period>>::
FromHHMMInt(
479 auto mm = hh_mm % 100;
481 throw std::runtime_error{fmt::format(
"Invalid value for minutes {} in int representation {}", mm, hh_mm)};
482 return TimeOfDay{std::chrono::minutes{hh_mm / 100 * 60 + mm}};
491template <
typename Duration>
492class formatter<USERVER_NAMESPACE::utils::datetime::TimeOfDay<Duration>> {
494 static constexpr auto kLongHourFormat = USERVER_NAMESPACE::utils::datetime::detail::kLongHourFormat;
496 static constexpr auto kMinutesFormat = USERVER_NAMESPACE::utils::datetime::detail::kMinutesFormat;
498 static constexpr auto kSecondsFormat = USERVER_NAMESPACE::utils::datetime::detail::kSecondsFormat;
501 static constexpr auto kSubsecondsFormat = USERVER_NAMESPACE::utils::datetime::detail::kSubsecondsFormat;
503 static constexpr auto kSubsecondsPreformat =
504 USERVER_NAMESPACE::utils::datetime::detail::kSubsecondsPreformat<
typename Duration::period>;
506 static constexpr std::string_view kLiteralPercent =
"%";
508 static constexpr auto kDefaultFormat =
509 USERVER_NAMESPACE::utils::datetime::detail::kDefaultFormat<
typename Duration::period>;
511 constexpr std::string_view GetFormatForKey(
char key) {
515 return kLongHourFormat;
517 return kMinutesFormat;
519 return kSecondsFormat;
521 throw format_error{fmt::format(
"Unsupported format key {}", key)};
526 constexpr auto parse(format_parse_context& ctx) {
527 enum { kChar, kPercent, kKey } state = kChar;
528 const auto* it = ctx.begin();
529 const auto* end = ctx.end();
530 const auto* begin = it;
532 bool custom_format =
false;
533 std::size_t size = 0;
534 for (; it != end && *it !=
'}'; ++it) {
535 if (!custom_format) {
536 representation_size_ = 0;
537 custom_format =
true;
540 if (state == kPercent) {
541 PushBackFmt(kLiteralPercent);
544 if (state == kChar && size > 0) {
545 PushBackFmt({begin, size});
550 if (state == kPercent) {
551 PushBackFmt(GetFormatForKey(*it));
553 }
else if (state == kKey) {
563 if (!custom_format) {
564 for (
const auto fmt : kDefaultFormat) {
568 if (state == kChar) {
570 PushBackFmt({begin, size});
572 }
else if (state == kPercent) {
573 throw format_error{
"No format key after percent character"};
578 template <
typename FormatContext>
579 constexpr auto format(
const USERVER_NAMESPACE::utils::datetime::TimeOfDay<Duration>& value, FormatContext& ctx)
581 auto hours = value.Hours().count();
582 auto mins = value.Minutes().count();
583 auto secs = value.Seconds().count();
585 auto ss = value.Subseconds().count();
588 constexpr std::size_t buffer_size =
590 USERVER_NAMESPACE::utils::datetime::detail::kDecimalPositions<
typename Duration::period>, std::size_t{1}
593 char subseconds[buffer_size];
595 if (ss > 0 || !truncate_trailing_subseconds_) {
596 fmt::format_to(subseconds, kSubsecondsPreformat, ss);
597 subseconds[buffer_size - 1] = 0;
598 if (truncate_trailing_subseconds_) {
600 for (
auto last = buffer_size - 2; last > 0 && subseconds[last] ==
'0'; --last) subseconds[last] = 0;
604 auto res = ctx.out();
605 for (std::size_t i = 0; i < representation_size_; ++i) {
606 res = format_to(ctx.out(), fmt::runtime(representation_[i]), hours, mins, secs, subseconds);
612 constexpr void PushBackFmt(std::string_view fmt) {
613 if (representation_size_ >= kRepresentationCapacity) {
614 throw format_error(
"Format string complexity exceeds TimeOfDay limits");
616 representation_[representation_size_++] = fmt;
620 static constexpr std::size_t kRepresentationCapacity = 10;
622 std::string_view representation_[kRepresentationCapacity]{};
623 std::size_t representation_size_{0};
624 bool truncate_trailing_subseconds_{
true};