13#include <fmt/format.h>
15#include <userver/compiler/impl/three_way_comparison.hpp>
16#include <userver/utils/fmt_compat.hpp>
18USERVER_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 USERVER_IMPL_HAS_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;
105 constexpr std::chrono::hours
Hours()
const noexcept {
106 return std::chrono::duration_cast<std::chrono::hours>(since_midnight_);
110 constexpr std::chrono::minutes
Minutes()
const noexcept;
112 constexpr std::chrono::seconds
Seconds()
const noexcept;
115 constexpr DurationType
Subseconds()
const noexcept;
125 DurationType since_midnight_{};
131template <
typename LDuration,
typename RDuration>
132auto operator-(TimeOfDay<LDuration> lhs, TimeOfDay<RDuration> rhs) {
133 return lhs.SinceMidnight() - rhs.SinceMidnight();
136template <
typename Duration,
typename Rep,
typename Period>
137TimeOfDay<Duration> operator+(TimeOfDay<Duration> lhs, std::chrono::duration<Rep, Period> rhs) {
138 return TimeOfDay<Duration>{lhs.SinceMidnight() + rhs};
141template <
typename Duration,
typename Rep,
typename Period>
142TimeOfDay<Duration> operator-(TimeOfDay<Duration> lhs, std::chrono::duration<Rep, Period> rhs) {
143 return TimeOfDay<Duration>{lhs.SinceMidnight() - rhs};
147template <
typename Duration>
148logging::LogHelper& operator<<(
logging::LogHelper& lh, TimeOfDay<Duration> value) {
149 lh << fmt::to_string(value);
154template <
typename Rep,
typename Period>
155inline constexpr std::chrono::duration<Rep, Period>
156 kTwentyFourHours = std::chrono::duration_cast<std::chrono::duration<Rep, Period>>(std::chrono::hours{24});
158template <
typename Rep,
typename Period,
typename ORep = Rep,
typename OPeriod = Period>
159constexpr std::chrono::duration<Rep, Period> NormalizeTimeOfDay(std::chrono::duration<ORep, OPeriod> d) {
160 auto res = std::chrono::duration_cast<std::chrono::duration<Rep, Period>>(d) % kTwentyFourHours<Rep, Period>;
161 return res.count() < 0 ? res + kTwentyFourHours<Rep, Period> : res;
164template <
typename Ratio>
165inline constexpr std::size_t kDecimalPositions = 0;
167inline constexpr std::size_t kDecimalPositions<std::milli> = 3;
169inline constexpr std::size_t kDecimalPositions<std::micro> = 6;
171inline constexpr const std::size_t kDecimalPositions<std::nano> = 9;
173constexpr std::intmax_t MissingDigits(std::size_t n) {
176 constexpr std::intmax_t powers[]{
193template <
typename Ratio>
194struct HasMinutes : std::false_type {};
196template <intmax_t Num, intmax_t Den>
197struct HasMinutes<std::ratio<Num, Den>> : std::integral_constant<
bool, (Num <= 60LL)> {};
199template <
typename Rep,
typename Period>
200struct HasMinutes<std::chrono::duration<Rep, Period>> : HasMinutes<Period> {};
202template <
typename Duration>
203struct HasMinutes<TimeOfDay<Duration>> : HasMinutes<Duration> {};
206constexpr inline bool kHasMinutes = HasMinutes<T>{};
208template <
typename Ratio>
209struct HasSeconds : std::false_type {};
211template <intmax_t Num, intmax_t Den>
212struct HasSeconds<std::ratio<Num, Den>> : std::integral_constant<
bool, (Num == 1)> {};
214template <
typename Rep,
typename Period>
215struct HasSeconds<std::chrono::duration<Rep, Period>> : HasSeconds<Period> {};
217template <
typename Duration>
218struct HasSeconds<TimeOfDay<Duration>> : HasSeconds<Duration> {};
221constexpr inline bool kHasSeconds = HasSeconds<T>{};
223template <
typename Ratio>
224struct HasSubseconds : std::false_type {};
226template <intmax_t Num, intmax_t Den>
227struct HasSubseconds<std::ratio<Num, Den>> : std::integral_constant<
bool, (Den > 1)> {};
229template <
typename Rep,
typename Period>
230struct HasSubseconds<std::chrono::duration<Rep, Period>> : HasSubseconds<Period> {};
232template <
typename Duration>
233struct HasSubseconds<TimeOfDay<Duration>> : HasSubseconds<Duration> {};
236constexpr inline bool kHasSubseconds = HasSubseconds<T>{};
238template <
typename Rep,
typename Period>
239class TimeOfDayParser {
241 using DurationType = std::chrono::duration<Rep, Period>;
243 constexpr DurationType operator()(std::string_view str) {
247 if (position_ >= kSeconds) {
248 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)};
257 AssignCurrentPosition(str);
258 position_ =
static_cast<TimePart>(position_ + 1);
261 if (!std::isdigit(c)) {
262 throw std::runtime_error{fmt::format(
"Unexpected character {} in TimeOfDay string `{}`", c, str)
265 if (position_ == kOverflow) {
267 }
else if (position_ == kSubseconds) {
268 if (digit_count_ >= kDecimalPositions<Period>) {
269 AssignCurrentPosition(str);
270 position_ = kOverflow;
273 }
else if (digit_count_ >= 2) {
274 throw std::runtime_error{fmt::format(
"Too much digits in TimeOfDay string `{}`", str)};
277 current_number_ = current_number_ * 10 + (c -
'0');
281 if (position_ == kHour) {
282 throw std::runtime_error{fmt::format(
283 "Expected to have at least minutes after hours in "
284 "TimeOfDay string `{}`",
288 AssignCurrentPosition(str);
290 auto sum = hours_ + minutes_ + seconds_ + subseconds_;
291 if (sum > kTwentyFourHours<Rep, Period>) {
292 throw std::runtime_error(fmt::format(
"TimeOfDay value {} is out of range [00:00, 24:00)", str));
294 return NormalizeTimeOfDay<Rep, Period>(sum);
306 void AssignCurrentPosition(std::string_view str) {
309 if (digit_count_ < 1) {
310 throw std::runtime_error{fmt::format(
"Not enough digits for hours in TimeOfDay string `{}`", str)};
312 if (current_number_ > 24) {
313 throw std::runtime_error{fmt::format(
"Invalid value for hours in TimeOfDay string `{}`", str)};
315 hours_ = std::chrono::hours{current_number_};
319 if (digit_count_ != 2) {
320 throw std::runtime_error{fmt::format(
"Not enough digits for minutes in TimeOfDay string `{}`", str)
323 if (current_number_ > 59) {
324 throw std::runtime_error{fmt::format(
"Invalid value for minutes in TimeOfDay string `{}`", str)};
326 minutes_ = std::chrono::minutes{current_number_};
330 if (digit_count_ != 2) {
331 throw std::runtime_error{fmt::format(
"Not enough digits for seconds in TimeOfDay string `{}`", str)
334 if (current_number_ > 59) {
335 throw std::runtime_error{fmt::format(
"Invalid value for seconds in TimeOfDay string `{}`", str)};
337 seconds_ = std::chrono::seconds{current_number_};
341 if (digit_count_ < 1) {
342 throw std::runtime_error{
343 fmt::format(
"Not enough digits for subseconds in TimeOfDay string `{}`", str)
346 if constexpr (kHasSubseconds<Period>) {
348 if (digit_count_ < kDecimalPositions<Period>) {
349 current_number_ *= MissingDigits(kDecimalPositions<Period> - digit_count_);
351 subseconds_ = DurationType{current_number_};
363 TimePart position_ = kHour;
364 std::chrono::hours hours_{0};
365 std::chrono::minutes minutes_{0};
366 std::chrono::seconds seconds_{0};
367 DurationType subseconds_{0};
369 std::size_t digit_count_{0};
370 std::size_t current_number_{0};
374inline constexpr std::string_view kLongHourFormat =
"{0:0>#2d}";
376inline constexpr std::string_view kMinutesFormat =
"{1:0>#2d}";
378inline constexpr std::string_view kSecondsFormat =
"{2:0>#2d}";
380inline constexpr std::string_view kSubsecondsFormat =
"{3}";
382template <
typename Ratio>
383constexpr inline std::string_view kSubsecondsPreformat =
".0";
385inline constexpr std::string_view kSubsecondsPreformat<std::milli> =
".{:0>#3d}";
387inline constexpr std::string_view kSubsecondsPreformat<std::micro> =
".{:0>#6d}";
389inline constexpr std::string_view kSubsecondsPreformat<std::nano> =
".{:0>#9d}";
392template <
typename Ratio>
393inline constexpr std::array<std::string_view, 5> kDefaultFormat{
394 {kLongHourFormat,
":", kMinutesFormat,
":", kSecondsFormat}
399inline constexpr std::array<std::string_view, 3> kDefaultFormat<std::ratio<60, 1>>{
400 {kLongHourFormat,
":", kMinutesFormat}
405inline constexpr std::array<std::string_view, 3> kDefaultFormat<std::ratio<3600, 1>>{
406 {kLongHourFormat,
":", kMinutesFormat}
411template <
typename Rep,
typename Period>
412constexpr TimeOfDay<std::chrono::duration<Rep, Period>>::TimeOfDay(DurationType d)
noexcept
413 : since_midnight_{detail::NormalizeTimeOfDay<Rep, Period>(d)} {}
416template <
typename ORep,
typename OPeriod>
417constexpr TimeOfDay<std::chrono::duration<Rep, Period>>::TimeOfDay(std::chrono::duration<ORep, OPeriod> d)
noexcept
418 : since_midnight_{detail::NormalizeTimeOfDay<Rep, Period>(d)} {}
420template <
typename Rep,
typename Period>
421constexpr TimeOfDay<std::chrono::duration<Rep, Period>>::TimeOfDay(std::string_view str)
422 : since_midnight_{detail::TimeOfDayParser<Rep, Period>{}(str)}
425#ifndef USERVER_IMPL_HAS_THREE_WAY_COMPARISON
433 return !(*
this ==
rhs);
457template <
typename Rep,
typename Period>
458constexpr std::chrono::minutes TimeOfDay<std::chrono::duration<Rep, Period>>::
Minutes()
const noexcept {
459 if constexpr (detail::kHasMinutes<Period>) {
460 return std::chrono::duration_cast<std::chrono::minutes>(since_midnight_) -
461 std::chrono::duration_cast<std::chrono::minutes>(
Hours());
463 return std::chrono::minutes{0};
467template <
typename Rep,
typename Period>
468constexpr std::chrono::seconds TimeOfDay<std::chrono::duration<Rep, Period>>::
Seconds()
const noexcept {
469 if constexpr (detail::kHasSeconds<Period>) {
470 return std::chrono::duration_cast<std::chrono::seconds>(since_midnight_) -
471 std::chrono::duration_cast<
472 std::chrono::seconds>(std::chrono::duration_cast<std::chrono::minutes>(since_midnight_));
474 return std::chrono::seconds{0};
478template <
typename Rep,
typename Period>
479constexpr typename TimeOfDay<std::chrono::duration<Rep, Period>>::DurationType TimeOfDay<
480 std::chrono::duration<Rep, Period>>::
Subseconds()
const noexcept {
481 if constexpr (detail::kHasSubseconds<Period>) {
482 return since_midnight_ - std::chrono::duration_cast<std::chrono::seconds>(since_midnight_);
484 return DurationType{0};
488template <
typename Rep,
typename Period>
489constexpr typename TimeOfDay<std::chrono::duration<Rep, Period>>::DurationType TimeOfDay<
491 return since_midnight_;
494template <
typename Rep,
typename Period>
495constexpr TimeOfDay<std::chrono::duration<Rep, Period>> TimeOfDay<
497 auto mm = hh_mm % 100;
499 throw std::runtime_error{fmt::format(
"Invalid value for minutes {} in int representation {}", mm, hh_mm)};
501 return TimeOfDay{std::chrono::minutes{hh_mm / 100 * 60 + mm}};
510template <
typename Duration>
511class formatter<USERVER_NAMESPACE::utils::
datetime::TimeOfDay<Duration>> {
513 static constexpr auto kLongHourFormat = USERVER_NAMESPACE::utils::
datetime::detail::kLongHourFormat;
515 static constexpr auto kMinutesFormat = USERVER_NAMESPACE::utils::
datetime::detail::kMinutesFormat;
517 static constexpr auto kSecondsFormat = USERVER_NAMESPACE::utils::
datetime::detail::kSecondsFormat;
520 static constexpr auto kSubsecondsFormat = USERVER_NAMESPACE::utils::
datetime::detail::kSubsecondsFormat;
522 static constexpr auto kSubsecondsPreformat = USERVER_NAMESPACE::utils::
datetime::detail::kSubsecondsPreformat<
523 typename Duration::period>;
525 static constexpr std::string_view kLiteralPercent =
"%";
527 static constexpr auto
528 kDefaultFormat = USERVER_NAMESPACE::utils::
datetime::detail::kDefaultFormat<
typename Duration::period>;
530 constexpr std::string_view GetFormatForKey(
char key) {
534 return kLongHourFormat;
536 return kMinutesFormat;
538 return kSecondsFormat;
540 throw format_error{fmt::format(
"Unsupported format key {}", key)};
545 constexpr auto parse(format_parse_context& ctx) {
546 enum { kChar, kPercent, kKey } state = kChar;
547 const auto* it = ctx.begin();
548 const auto* end = ctx.end();
549 const auto* begin = it;
551 bool custom_format =
false;
552 std::size_t size = 0;
553 for (; it != end && *it !=
'}'; ++it) {
554 if (!custom_format) {
555 representation_size_ = 0;
556 custom_format =
true;
559 if (state == kPercent) {
560 PushBackFmt(kLiteralPercent);
563 if (state == kChar && size > 0) {
564 PushBackFmt({begin, size});
569 if (state == kPercent) {
570 PushBackFmt(GetFormatForKey(*it));
572 }
else if (state == kKey) {
582 if (!custom_format) {
583 for (
const auto fmt : kDefaultFormat) {
587 if (state == kChar) {
589 PushBackFmt({begin, size});
591 }
else if (state == kPercent) {
592 throw format_error{
"No format key after percent character"};
597 template <
typename FormatContext>
598 constexpr auto format(
const USERVER_NAMESPACE::utils::
datetime::TimeOfDay<Duration>& value, FormatContext& ctx)
600 auto hours = value.Hours().count();
601 auto mins = value.Minutes().count();
602 auto secs = value.Seconds().count();
604 auto ss = value.Subseconds().count();
607 constexpr std::size_t buffer_size =
609 USERVER_NAMESPACE::utils::
datetime::detail::kDecimalPositions<
typename Duration::period>,
613 char subseconds[buffer_size];
615 if (ss > 0 || !truncate_trailing_subseconds_) {
616 fmt::format_to(subseconds, kSubsecondsPreformat, ss);
617 subseconds[buffer_size - 1] = 0;
618 if (truncate_trailing_subseconds_) {
620 for (
auto last = buffer_size - 2; last > 0 && subseconds[last] ==
'0'; --last) {
621 subseconds[last] = 0;
626 auto res = ctx.out();
627 for (std::size_t i = 0; i < representation_size_; ++i) {
628 res = format_to(ctx.out(), fmt::runtime(representation_[i]), hours, mins, secs, subseconds);
634 constexpr void PushBackFmt(std::string_view fmt) {
635 if (representation_size_ >= kRepresentationCapacity) {
636 throw format_error(
"Format string complexity exceeds TimeOfDay limits");
638 representation_[representation_size_++] = fmt;
642 static constexpr std::size_t kRepresentationCapacity = 10;
644 std::string_view representation_[kRepresentationCapacity]{};
645 std::size_t representation_size_{0};
646 bool truncate_trailing_subseconds_{
true};