13#include <fmt/format.h>
15#include <userver/compiler/impl/three_way_comparison.hpp>
16#include <userver/utils/fmt_compat.hpp>
18USERVER_NAMESPACE_BEGIN
74template <
typename Duration>
77template <
typename Rep,
typename Period>
78class TimeOfDay<std::chrono::duration<Rep, Period>> {
80 using DurationType = std::chrono::duration<Rep, Period>;
82 constexpr TimeOfDay()
noexcept =
default;
83 constexpr explicit TimeOfDay(DurationType)
noexcept;
84 template <
typename ORep,
typename OPeriod>
85 constexpr explicit TimeOfDay(std::chrono::duration<ORep, OPeriod>)
noexcept;
86 constexpr explicit TimeOfDay(std::string_view);
91#ifdef USERVER_IMPL_HAS_THREE_WAY_COMPARISON
92 constexpr auto operator<=>(
const TimeOfDay&)
const =
default;
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;
99 constexpr bool operator>=(
const TimeOfDay&)
const;
107 return std::chrono::duration_cast<std::chrono::hours>(since_midnight_);
116 constexpr DurationType
Subseconds()
const noexcept;
126 DurationType since_midnight_{};
132template <
typename LDuration,
typename RDuration>
133auto operator-(TimeOfDay<LDuration> lhs, TimeOfDay<RDuration> rhs) {
134 return lhs.SinceMidnight() - rhs.SinceMidnight();
137template <
typename Duration,
typename Rep,
typename Period>
138TimeOfDay<Duration> operator+(TimeOfDay<Duration> lhs, std::chrono::duration<Rep, Period> rhs) {
139 return TimeOfDay<Duration>{lhs.SinceMidnight() + rhs};
142template <
typename Duration,
typename Rep,
typename Period>
143TimeOfDay<Duration> operator-(TimeOfDay<Duration> lhs, std::chrono::duration<Rep, Period> rhs) {
144 return TimeOfDay<Duration>{lhs.SinceMidnight() - rhs};
148template <
typename Duration>
149logging::LogHelper& operator<<(
logging::LogHelper& lh, TimeOfDay<Duration> value) {
150 lh << fmt::to_string(value);
155template <
typename Rep,
typename Period>
156inline constexpr std::chrono::duration<Rep, Period> kTwentyFourHours =
157 std::chrono::duration_cast<std::chrono::duration<Rep, Period>>(std::chrono::hours{24});
159template <
typename Rep,
typename Period,
typename ORep = Rep,
typename OPeriod = Period>
160constexpr std::chrono::duration<Rep, Period> NormalizeTimeOfDay(std::chrono::duration<ORep, OPeriod> d) {
161 auto res = std::chrono::duration_cast<std::chrono::duration<Rep, Period>>(d) % kTwentyFourHours<Rep, Period>;
162 return res.count() < 0 ? res + kTwentyFourHours<Rep, Period> : res;
165template <
typename Ratio>
166inline constexpr std::size_t kDecimalPositions = 0;
168inline constexpr std::size_t kDecimalPositions<std::milli> = 3;
170inline constexpr std::size_t kDecimalPositions<std::micro> = 6;
172inline constexpr const std::size_t kDecimalPositions<std::nano> = 9;
174constexpr std::intmax_t MissingDigits(std::size_t n) {
177 constexpr std::intmax_t powers[]{
194template <
typename Ratio>
195struct HasMinutes : std::false_type {};
197template <intmax_t Num, intmax_t Den>
198struct HasMinutes<std::ratio<Num, Den>> : std::integral_constant<
bool, (Num <= 60LL)> {};
200template <
typename Rep,
typename Period>
201struct HasMinutes<std::chrono::duration<Rep, Period>> : HasMinutes<Period> {};
203template <
typename Duration>
204struct HasMinutes<TimeOfDay<Duration>> : HasMinutes<Duration> {};
207constexpr inline bool kHasMinutes = HasMinutes<T>{};
209template <
typename Ratio>
210struct HasSeconds : std::false_type {};
212template <intmax_t Num, intmax_t Den>
213struct HasSeconds<std::ratio<Num, Den>> : std::integral_constant<
bool, (Num == 1)> {};
215template <
typename Rep,
typename Period>
216struct HasSeconds<std::chrono::duration<Rep, Period>> : HasSeconds<Period> {};
218template <
typename Duration>
219struct HasSeconds<TimeOfDay<Duration>> : HasSeconds<Duration> {};
222constexpr inline bool kHasSeconds = HasSeconds<T>{};
224template <
typename Ratio>
225struct HasSubseconds : std::false_type {};
227template <intmax_t Num, intmax_t Den>
228struct HasSubseconds<std::ratio<Num, Den>> : std::integral_constant<
bool, (Den > 1)> {};
230template <
typename Rep,
typename Period>
231struct HasSubseconds<std::chrono::duration<Rep, Period>> : HasSubseconds<Period> {};
233template <
typename Duration>
234struct HasSubseconds<TimeOfDay<Duration>> : HasSubseconds<Duration> {};
237constexpr inline bool kHasSubseconds = HasSubseconds<T>{};
239template <
typename Rep,
typename Period>
240class TimeOfDayParser {
242 using DurationType = std::chrono::duration<Rep, Period>;
244 constexpr DurationType operator()(std::string_view str) {
248 if (position_ >= kSeconds)
249 throw std::runtime_error{fmt::format(
"Extra colon in TimeOfDay string `{}`", str)};
250 AssignCurrentPosition(str);
251 position_ =
static_cast<TimePart>(position_ + 1);
254 if (position_ != kSeconds)
255 throw std::runtime_error{fmt::format(
"Unexpected decimal point in TimeOfDay string `{}`", str)};
256 AssignCurrentPosition(str);
257 position_ =
static_cast<TimePart>(position_ + 1);
260 if (!std::isdigit(c))
261 throw std::runtime_error{
262 fmt::format(
"Unexpected character {} in TimeOfDay string `{}`", c, str)};
263 if (position_ == kOverflow) {
265 }
else if (position_ == kSubseconds) {
266 if (digit_count_ >= kDecimalPositions<Period>) {
267 AssignCurrentPosition(str);
268 position_ = kOverflow;
271 }
else if (digit_count_ >= 2) {
272 throw std::runtime_error{fmt::format(
"Too much digits in TimeOfDay string `{}`", str)};
275 current_number_ = current_number_ * 10 + (c -
'0');
279 if (position_ == kHour)
280 throw std::runtime_error{fmt::format(
281 "Expected to have at least minutes after hours in "
282 "TimeOfDay string `{}`",
285 AssignCurrentPosition(str);
287 auto sum = hours_ + minutes_ + seconds_ + subseconds_;
288 if (sum > kTwentyFourHours<Rep, Period>) {
289 throw std::runtime_error(fmt::format(
"TimeOfDay value {} is out of range [00:00, 24:00)", str));
291 return NormalizeTimeOfDay<Rep, Period>(sum);
295 enum TimePart { kHour, kMinutes, kSeconds, kSubseconds, kOverflow };
297 void AssignCurrentPosition(std::string_view str) {
300 if (digit_count_ < 1)
301 throw std::runtime_error{fmt::format(
"Not enough digits for hours in TimeOfDay string `{}`", str)};
302 if (current_number_ > 24)
303 throw std::runtime_error{fmt::format(
"Invalid value for hours in TimeOfDay string `{}`", str)};
304 hours_ = std::chrono::hours{current_number_};
308 if (digit_count_ != 2)
309 throw std::runtime_error{
310 fmt::format(
"Not enough digits for minutes in TimeOfDay string `{}`", str)};
311 if (current_number_ > 59)
312 throw std::runtime_error{fmt::format(
"Invalid value for minutes in TimeOfDay string `{}`", str)};
313 minutes_ = std::chrono::minutes{current_number_};
317 if (digit_count_ != 2)
318 throw std::runtime_error{
319 fmt::format(
"Not enough digits for seconds in TimeOfDay string `{}`", str)};
320 if (current_number_ > 59)
321 throw std::runtime_error{fmt::format(
"Invalid value for seconds in TimeOfDay string `{}`", str)};
322 seconds_ = std::chrono::seconds{current_number_};
326 if (digit_count_ < 1)
327 throw std::runtime_error{
328 fmt::format(
"Not enough digits for subseconds in TimeOfDay string `{}`", str)};
329 if constexpr (kHasSubseconds<Period>) {
331 if (digit_count_ < kDecimalPositions<Period>) {
332 current_number_ *= MissingDigits(kDecimalPositions<Period> - digit_count_);
334 subseconds_ = DurationType{current_number_};
346 TimePart position_ = kHour;
347 std::chrono::hours hours_{0};
348 std::chrono::minutes minutes_{0};
349 std::chrono::seconds seconds_{0};
350 DurationType subseconds_{0};
352 std::size_t digit_count_{0};
353 std::size_t current_number_{0};
357inline constexpr std::string_view kLongHourFormat =
"{0:0>#2d}";
359inline constexpr std::string_view kMinutesFormat =
"{1:0>#2d}";
361inline constexpr std::string_view kSecondsFormat =
"{2:0>#2d}";
363inline constexpr std::string_view kSubsecondsFormat =
"{3}";
365template <
typename Ratio>
366constexpr inline std::string_view kSubsecondsPreformat =
".0";
368inline constexpr std::string_view kSubsecondsPreformat<std::milli> =
".{:0>#3d}";
370inline constexpr std::string_view kSubsecondsPreformat<std::micro> =
".{:0>#6d}";
372inline constexpr std::string_view kSubsecondsPreformat<std::nano> =
".{:0>#9d}";
375template <
typename Ratio>
376inline constexpr std::array<std::string_view, 5> kDefaultFormat{
377 {kLongHourFormat,
":", kMinutesFormat,
":", kSecondsFormat}};
381inline constexpr std::array<std::string_view, 3> kDefaultFormat<std::ratio<60, 1>>{
382 {kLongHourFormat,
":", kMinutesFormat}};
386inline constexpr std::array<std::string_view, 3> kDefaultFormat<std::ratio<3600, 1>>{
387 {kLongHourFormat,
":", kMinutesFormat}};
391template <
typename Rep,
typename Period>
392constexpr TimeOfDay<std::chrono::duration<Rep, Period>>::TimeOfDay(DurationType d)
noexcept
393 : since_midnight_{detail::NormalizeTimeOfDay<Rep, Period>(d)} {}
396template <
typename ORep,
typename OPeriod>
397constexpr TimeOfDay<std::chrono::duration<Rep, Period>>::TimeOfDay(std::chrono::duration<ORep, OPeriod> d)
noexcept
398 : since_midnight_{detail::NormalizeTimeOfDay<Rep, Period>(d)} {}
400template <
typename Rep,
typename Period>
401constexpr TimeOfDay<std::chrono::duration<Rep, Period>>::TimeOfDay(std::string_view str)
402 : since_midnight_{detail::TimeOfDayParser<Rep, Period>{}(str)} {}
404#ifndef USERVER_IMPL_HAS_THREE_WAY_COMPARISON
405template <
typename Rep,
typename Period>
406constexpr bool TimeOfDay<std::chrono::duration<Rep, Period>>::operator==(
const TimeOfDay& rhs)
const {
407 return since_midnight_ == rhs.since_midnight_;
410template <
typename Rep,
typename Period>
411constexpr bool TimeOfDay<std::chrono::duration<Rep, Period>>::operator!=(
const TimeOfDay& rhs)
const {
412 return !(*
this == rhs);
415template <
typename Rep,
typename Period>
416constexpr bool TimeOfDay<std::chrono::duration<Rep, Period>>::operator<(
const TimeOfDay& rhs)
const {
417 return since_midnight_ < rhs.since_midnight_;
420template <
typename Rep,
typename Period>
421constexpr bool TimeOfDay<std::chrono::duration<Rep, Period>>::operator<=(
const TimeOfDay& rhs)
const {
422 return since_midnight_ <= rhs.since_midnight_;
425template <
typename Rep,
typename Period>
426constexpr bool TimeOfDay<std::chrono::duration<Rep, Period>>::operator>(
const TimeOfDay& rhs)
const {
427 return since_midnight_ > rhs.since_midnight_;
430template <
typename Rep,
typename Period>
431constexpr bool TimeOfDay<std::chrono::duration<Rep, Period>>::operator>=(
const TimeOfDay& rhs)
const {
432 return since_midnight_ >= rhs.since_midnight_;
436template <
typename Rep,
typename Period>
438 if constexpr (detail::kHasMinutes<Period>) {
439 return std::chrono::duration_cast<std::chrono::minutes>(since_midnight_) -
440 std::chrono::duration_cast<std::chrono::minutes>(Hours());
442 return std::chrono::minutes{0};
446template <
typename Rep,
typename Period>
448 if constexpr (detail::kHasSeconds<Period>) {
449 return std::chrono::duration_cast<std::chrono::seconds>(since_midnight_) -
450 std::chrono::duration_cast<std::chrono::seconds>(
451 std::chrono::duration_cast<std::chrono::minutes>(since_midnight_)
454 return std::chrono::seconds{0};
458template <
typename Rep,
typename Period>
459constexpr typename TimeOfDay<std::chrono::duration<Rep, Period>>::DurationType
460TimeOfDay<std::chrono::duration<Rep, Period>>::
Subseconds()
const noexcept {
461 if constexpr (detail::kHasSubseconds<Period>) {
462 return since_midnight_ - std::chrono::duration_cast<std::chrono::seconds>(since_midnight_);
464 return DurationType{0};
468template <
typename Rep,
typename Period>
469constexpr typename TimeOfDay<std::chrono::duration<Rep, Period>>::DurationType
470TimeOfDay<std::chrono::duration<Rep, Period>>::
SinceMidnight()
const noexcept {
471 return since_midnight_;
474template <
typename Rep,
typename Period>
475constexpr TimeOfDay<std::chrono::duration<Rep, Period>> TimeOfDay<std::chrono::duration<Rep, Period>>::
FromHHMMInt(
478 auto mm = hh_mm % 100;
480 throw std::runtime_error{fmt::format(
"Invalid value for minutes {} in int representation {}", mm, hh_mm)};
481 return TimeOfDay{std::chrono::minutes{hh_mm / 100 * 60 + mm}};
490template <
typename Duration>
491class formatter<USERVER_NAMESPACE::utils::datetime::TimeOfDay<Duration>> {
493 static constexpr auto kLongHourFormat = USERVER_NAMESPACE::utils::datetime::detail::kLongHourFormat;
495 static constexpr auto kMinutesFormat = USERVER_NAMESPACE::utils::datetime::detail::kMinutesFormat;
497 static constexpr auto kSecondsFormat = USERVER_NAMESPACE::utils::datetime::detail::kSecondsFormat;
500 static constexpr auto kSubsecondsFormat = USERVER_NAMESPACE::utils::datetime::detail::kSubsecondsFormat;
502 static constexpr auto kSubsecondsPreformat =
503 USERVER_NAMESPACE::utils::datetime::detail::kSubsecondsPreformat<
typename Duration::period>;
505 static constexpr std::string_view kLiteralPercent =
"%";
507 static constexpr auto kDefaultFormat =
508 USERVER_NAMESPACE::utils::datetime::detail::kDefaultFormat<
typename Duration::period>;
510 constexpr std::string_view GetFormatForKey(
char key) {
514 return kLongHourFormat;
516 return kMinutesFormat;
518 return kSecondsFormat;
520 throw format_error{fmt::format(
"Unsupported format key {}", key)};
525 constexpr auto parse(format_parse_context& ctx) {
526 enum { kChar, kPercent, kKey } state = kChar;
527 const auto* it = ctx.begin();
528 const auto* end = ctx.end();
529 const auto* begin = it;
531 bool custom_format =
false;
532 std::size_t size = 0;
533 for (; it != end && *it !=
'}'; ++it) {
534 if (!custom_format) {
535 representation_size_ = 0;
536 custom_format =
true;
539 if (state == kPercent) {
540 PushBackFmt(kLiteralPercent);
543 if (state == kChar && size > 0) {
544 PushBackFmt({begin, size});
549 if (state == kPercent) {
550 PushBackFmt(GetFormatForKey(*it));
552 }
else if (state == kKey) {
562 if (!custom_format) {
563 for (
const auto fmt : kDefaultFormat) {
567 if (state == kChar) {
569 PushBackFmt({begin, size});
571 }
else if (state == kPercent) {
572 throw format_error{
"No format key after percent character"};
577 template <
typename FormatContext>
578 constexpr auto format(
const USERVER_NAMESPACE::utils::datetime::TimeOfDay<Duration>& value, FormatContext& ctx)
580 auto hours = value.Hours().count();
581 auto mins = value.Minutes().count();
582 auto secs = value.Seconds().count();
584 auto ss = value.Subseconds().count();
587 constexpr std::size_t buffer_size =
589 USERVER_NAMESPACE::utils::datetime::detail::kDecimalPositions<
typename Duration::period>, std::size_t{1}
592 char subseconds[buffer_size];
594 if (ss > 0 || !truncate_trailing_subseconds_) {
595 fmt::format_to(subseconds, kSubsecondsPreformat, ss);
596 subseconds[buffer_size - 1] = 0;
597 if (truncate_trailing_subseconds_) {
599 for (
auto last = buffer_size - 2; last > 0 && subseconds[last] ==
'0'; --last) subseconds[last] = 0;
603 auto res = ctx.out();
604 for (std::size_t i = 0; i < representation_size_; ++i) {
605 res = format_to(ctx.out(), fmt::runtime(representation_[i]), hours, mins, secs, subseconds);
611 constexpr void PushBackFmt(std::string_view fmt) {
612 if (representation_size_ >= kRepresentationCapacity) {
613 throw format_error(
"Format string complexity exceeds TimeOfDay limits");
615 representation_[representation_size_++] = fmt;
619 static constexpr std::size_t kRepresentationCapacity = 10;
621 std::string_view representation_[kRepresentationCapacity]{};
622 std::size_t representation_size_{0};
623 bool truncate_trailing_subseconds_{
true};