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);
89 constexpr bool operator==(
const TimeOfDay&)
const;
90 constexpr bool operator!=(
const TimeOfDay&)
const;
91 constexpr bool operator<(
const TimeOfDay&)
const;
92 constexpr bool operator<=(
const TimeOfDay&)
const;
93 constexpr bool operator>(
const TimeOfDay&)
const;
94 constexpr bool operator>=(
const TimeOfDay&)
const;
107 constexpr DurationType
Subseconds()
const noexcept;
117 DurationType since_midnight_{};
123template <
typename LDuration,
typename RDuration>
124auto operator-(TimeOfDay<LDuration> lhs, TimeOfDay<RDuration> rhs) {
125 return lhs.SinceMidnight() - rhs.SinceMidnight();
128template <
typename Duration,
typename Rep,
typename Period>
129TimeOfDay<Duration> operator+(TimeOfDay<Duration> lhs,
130 std::chrono::duration<Rep, Period> rhs) {
131 return TimeOfDay<Duration>{lhs.SinceMidnight() + rhs};
134template <
typename Duration,
typename Rep,
typename Period>
135TimeOfDay<Duration> operator-(TimeOfDay<Duration> lhs,
136 std::chrono::duration<Rep, Period> rhs) {
137 return TimeOfDay<Duration>{lhs.SinceMidnight() - rhs};
141template <
typename Duration>
143 TimeOfDay<Duration> value) {
144 lh << fmt::to_string(value);
149template <
typename Rep,
typename Period>
150inline constexpr std::chrono::duration<Rep, Period> kTwentyFourHours =
151 std::chrono::duration_cast<std::chrono::duration<Rep, Period>>(
152 std::chrono::hours{24});
154template <
typename Rep,
typename Period,
typename ORep = Rep,
155 typename OPeriod = Period>
156constexpr std::chrono::duration<Rep, Period> NormalizeTimeOfDay(
157 std::chrono::duration<ORep, OPeriod> d) {
158 auto res = std::chrono::duration_cast<std::chrono::duration<Rep, Period>>(d) %
159 kTwentyFourHours<Rep, Period>;
160 return res.count() < 0 ? res + kTwentyFourHours<Rep, Period> : res;
163template <
typename Ratio>
164inline constexpr std::size_t kDecimalPositions = 0;
166inline constexpr std::size_t kDecimalPositions<std::milli> = 3;
168inline constexpr std::size_t kDecimalPositions<std::micro> = 6;
170inline constexpr const std::size_t kDecimalPositions<std::nano> = 9;
172constexpr std::intmax_t MissingDigits(std::size_t n) {
175 constexpr std::intmax_t powers[]{
192template <
typename Ratio>
193struct HasMinutes : std::false_type {};
195template <intmax_t Num, intmax_t Den>
196struct HasMinutes<std::ratio<Num, Den>>
197 : 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>>
213 : 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>>
229 : std::integral_constant<
bool, (Den > 1)> {};
231template <
typename Rep,
typename Period>
232struct HasSubseconds<std::chrono::duration<Rep, Period>>
233 : HasSubseconds<Period> {};
235template <
typename Duration>
236struct HasSubseconds<TimeOfDay<Duration>> : HasSubseconds<Duration> {};
239constexpr inline bool kHasSubseconds = HasSubseconds<T>{};
241template <
typename Rep,
typename Period>
242class TimeOfDayParser {
244 using DurationType = std::chrono::duration<Rep, Period>;
246 constexpr DurationType operator()(std::string_view str) {
250 if (position_ >= kSeconds)
251 throw std::runtime_error{
252 fmt::format(
"Extra colon in TimeOfDay string `{}`", str)};
253 AssignCurrentPosition(str);
254 position_ =
static_cast<TimePart>(position_ + 1);
257 if (position_ != kSeconds)
258 throw std::runtime_error{fmt::format(
259 "Unexpected decimal point in TimeOfDay string `{}`", str)};
260 AssignCurrentPosition(str);
261 position_ =
static_cast<TimePart>(position_ + 1);
264 if (!std::isdigit(c))
265 throw std::runtime_error{fmt::format(
266 "Unexpected character {} in TimeOfDay string `{}`", c, str)};
267 if (position_ == kOverflow) {
269 }
else if (position_ == kSubseconds) {
270 if (digit_count_ >= kDecimalPositions<Period>) {
271 AssignCurrentPosition(str);
272 position_ = kOverflow;
275 }
else if (digit_count_ >= 2) {
276 throw std::runtime_error{
277 fmt::format(
"Too much digits in TimeOfDay string `{}`", str)};
280 current_number_ = current_number_ * 10 + (c -
'0');
284 if (position_ == kHour)
285 throw std::runtime_error{
286 fmt::format(
"Expected to have at least minutes after hours in "
287 "TimeOfDay string `{}`",
289 AssignCurrentPosition(str);
291 auto sum = hours_ + minutes_ + seconds_ + subseconds_;
292 if (sum > kTwentyFourHours<Rep, Period>) {
293 throw std::runtime_error(fmt::format(
294 "TimeOfDay value {} is out of range [00:00, 24:00)", str));
296 return NormalizeTimeOfDay<Rep, Period>(sum);
300 enum TimePart { kHour, kMinutes, kSeconds, kSubseconds, kOverflow };
302 void AssignCurrentPosition(std::string_view str) {
305 if (digit_count_ < 1)
306 throw std::runtime_error{fmt::format(
307 "Not enough digits for hours in TimeOfDay string `{}`", str)};
308 if (current_number_ > 24)
309 throw std::runtime_error{fmt::format(
310 "Invalid value for hours in TimeOfDay string `{}`", str)};
311 hours_ = std::chrono::hours{current_number_};
315 if (digit_count_ != 2)
316 throw std::runtime_error{fmt::format(
317 "Not enough digits for minutes in TimeOfDay string `{}`", str)};
318 if (current_number_ > 59)
319 throw std::runtime_error{fmt::format(
320 "Invalid value for minutes in TimeOfDay string `{}`", str)};
321 minutes_ = std::chrono::minutes{current_number_};
325 if (digit_count_ != 2)
326 throw std::runtime_error{fmt::format(
327 "Not enough digits for seconds in TimeOfDay string `{}`", str)};
328 if (current_number_ > 59)
329 throw std::runtime_error{fmt::format(
330 "Invalid value for seconds in TimeOfDay string `{}`", str)};
331 seconds_ = std::chrono::seconds{current_number_};
335 if (digit_count_ < 1)
336 throw std::runtime_error{fmt::format(
337 "Not enough digits for subseconds in TimeOfDay string `{}`",
339 if constexpr (kHasSubseconds<Period>) {
341 if (digit_count_ < kDecimalPositions<Period>) {
343 MissingDigits(kDecimalPositions<Period> - digit_count_);
345 subseconds_ = DurationType{current_number_};
357 TimePart position_ = kHour;
358 std::chrono::hours hours_{0};
359 std::chrono::minutes minutes_{0};
360 std::chrono::seconds seconds_{0};
361 DurationType subseconds_{0};
363 std::size_t digit_count_{0};
364 std::size_t current_number_{0};
368inline constexpr std::string_view kLongHourFormat =
"{0:0>#2d}";
370inline constexpr std::string_view kMinutesFormat =
"{1:0>#2d}";
372inline constexpr std::string_view kSecondsFormat =
"{2:0>#2d}";
374inline constexpr std::string_view kSubsecondsFormat =
"{3}";
376template <
typename Ratio>
377constexpr inline std::string_view kSubsecondsPreformat =
".0";
379inline constexpr std::string_view kSubsecondsPreformat<std::milli> =
382inline constexpr std::string_view kSubsecondsPreformat<std::micro> =
385inline constexpr std::string_view kSubsecondsPreformat<std::nano> =
".{:0>#9d}";
388template <
typename Ratio>
389inline constexpr std::array<std::string_view, 5> kDefaultFormat{
390 {kLongHourFormat,
":", kMinutesFormat,
":", kSecondsFormat}};
394inline constexpr std::array<std::string_view, 3>
395 kDefaultFormat<std::ratio<60, 1>>{{kLongHourFormat,
":", kMinutesFormat}};
399inline constexpr std::array<std::string_view, 3>
400 kDefaultFormat<std::ratio<3600, 1>>{{kLongHourFormat,
":", kMinutesFormat}};
404template <
typename Rep,
typename Period>
405constexpr TimeOfDay<std::chrono::duration<Rep, Period>>::TimeOfDay(
406 DurationType d)
noexcept
407 : since_midnight_{detail::NormalizeTimeOfDay<Rep, Period>(d)} {}
410template <
typename ORep,
typename OPeriod>
411constexpr TimeOfDay<std::chrono::duration<Rep, Period>>::TimeOfDay(
412 std::chrono::duration<ORep, OPeriod> d)
noexcept
413 : since_midnight_{detail::NormalizeTimeOfDay<Rep, Period>(d)} {}
415template <
typename Rep,
typename Period>
416constexpr TimeOfDay<std::chrono::duration<Rep, Period>>::TimeOfDay(
417 std::string_view str)
418 : since_midnight_{detail::TimeOfDayParser<Rep, Period>{}(str)} {}
420template <
typename Rep,
typename Period>
421constexpr bool TimeOfDay<std::chrono::duration<Rep, Period>>::operator==(
422 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!=(
428 const TimeOfDay& rhs)
const {
429 return !(*
this == rhs);
432template <
typename Rep,
typename Period>
433constexpr bool TimeOfDay<std::chrono::duration<Rep, Period>>::operator<(
434 const TimeOfDay& rhs)
const {
435 return since_midnight_ < rhs.since_midnight_;
438template <
typename Rep,
typename Period>
439constexpr bool TimeOfDay<std::chrono::duration<Rep, Period>>::operator<=(
440 const TimeOfDay& rhs)
const {
441 return since_midnight_ <= rhs.since_midnight_;
444template <
typename Rep,
typename Period>
445constexpr bool TimeOfDay<std::chrono::duration<Rep, Period>>::operator>(
446 const TimeOfDay& rhs)
const {
447 return since_midnight_ > rhs.since_midnight_;
450template <
typename Rep,
typename Period>
451constexpr bool TimeOfDay<std::chrono::duration<Rep, Period>>::operator>=(
452 const TimeOfDay& rhs)
const {
453 return since_midnight_ >= rhs.since_midnight_;
456template <
typename Rep,
typename Period>
458TimeOfDay<std::chrono::duration<Rep, Period>>::
Hours()
const noexcept {
459 return std::chrono::duration_cast<std::chrono::hours>(since_midnight_);
462template <
typename Rep,
typename Period>
464TimeOfDay<std::chrono::duration<Rep, Period>>::
Minutes()
const noexcept {
465 if constexpr (detail::kHasMinutes<Period>) {
466 return std::chrono::duration_cast<std::chrono::minutes>(since_midnight_) -
467 std::chrono::duration_cast<std::chrono::minutes>(Hours());
469 return std::chrono::minutes{0};
473template <
typename Rep,
typename Period>
475TimeOfDay<std::chrono::duration<Rep, Period>>::
Seconds()
const noexcept {
476 if constexpr (detail::kHasSeconds<Period>) {
477 return std::chrono::duration_cast<std::chrono::seconds>(since_midnight_) -
478 std::chrono::duration_cast<std::chrono::seconds>(
479 std::chrono::duration_cast<std::chrono::minutes>(
482 return std::chrono::seconds{0};
486template <
typename Rep,
typename Period>
487constexpr typename TimeOfDay<std::chrono::duration<Rep, Period>>::DurationType
488TimeOfDay<std::chrono::duration<Rep, Period>>::
Subseconds()
const noexcept {
489 if constexpr (detail::kHasSubseconds<Period>) {
490 return since_midnight_ -
491 std::chrono::duration_cast<std::chrono::seconds>(since_midnight_);
493 return DurationType{0};
497template <
typename Rep,
typename Period>
498constexpr typename TimeOfDay<std::chrono::duration<Rep, Period>>::DurationType
499TimeOfDay<std::chrono::duration<Rep, Period>>::
SinceMidnight()
const noexcept {
500 return since_midnight_;
503template <
typename Rep,
typename Period>
504constexpr TimeOfDay<std::chrono::duration<Rep, Period>>
505TimeOfDay<std::chrono::duration<Rep, Period>>::
FromHHMMInt(
int hh_mm) {
506 auto mm = hh_mm % 100;
508 throw std::runtime_error{fmt::format(
509 "Invalid value for minutes {} in int representation {}", mm, hh_mm)};
510 return TimeOfDay{std::chrono::minutes{hh_mm / 100 * 60 + mm}};
519template <
typename Duration>
520class formatter<USERVER_NAMESPACE::utils::datetime::TimeOfDay<Duration>> {
522 static constexpr auto kLongHourFormat =
523 USERVER_NAMESPACE::utils::datetime::detail::kLongHourFormat;
525 static constexpr auto kMinutesFormat =
526 USERVER_NAMESPACE::utils::datetime::detail::kMinutesFormat;
528 static constexpr auto kSecondsFormat =
529 USERVER_NAMESPACE::utils::datetime::detail::kSecondsFormat;
532 static constexpr auto kSubsecondsFormat =
533 USERVER_NAMESPACE::utils::datetime::detail::kSubsecondsFormat;
535 static constexpr auto kSubsecondsPreformat =
536 USERVER_NAMESPACE::utils::
datetime::detail::kSubsecondsPreformat<
537 typename Duration::period>;
539 static constexpr std::string_view kLiteralPercent =
"%";
541 static constexpr auto kDefaultFormat =
542 USERVER_NAMESPACE::utils::
datetime::detail::kDefaultFormat<
543 typename Duration::period>;
545 constexpr std::string_view GetFormatForKey(
char key) {
549 return kLongHourFormat;
551 return kMinutesFormat;
553 return kSecondsFormat;
555 throw format_error{fmt::format(
"Unsupported format key {}", key)};
560 constexpr auto parse(format_parse_context& ctx) {
561 enum { kChar, kPercent, kKey } state = kChar;
562 const auto* it = ctx.begin();
563 const auto* end = ctx.end();
564 const auto* begin = it;
566 bool custom_format =
false;
567 std::size_t size = 0;
568 for (; it != end && *it !=
'}'; ++it) {
569 if (!custom_format) {
570 representation_size_ = 0;
571 custom_format =
true;
574 if (state == kPercent) {
575 PushBackFmt(kLiteralPercent);
578 if (state == kChar && size > 0) {
579 PushBackFmt({begin, size});
584 if (state == kPercent) {
585 PushBackFmt(GetFormatForKey(*it));
587 }
else if (state == kKey) {
597 if (!custom_format) {
598 for (
const auto fmt : kDefaultFormat) {
602 if (state == kChar) {
604 PushBackFmt({begin, size});
606 }
else if (state == kPercent) {
607 throw format_error{
"No format key after percent character"};
612 template <
typename FormatContext>
613 constexpr auto format(
614 const USERVER_NAMESPACE::utils::
datetime::TimeOfDay<Duration>& value,
615 FormatContext& ctx)
const {
616 auto hours = value.Hours().count();
617 auto mins = value.Minutes().count();
618 auto secs = value.Seconds().count();
620 auto ss = value.Subseconds().count();
623 constexpr std::size_t buffer_size =
624 std::max(USERVER_NAMESPACE::utils::
datetime::detail::kDecimalPositions<
625 typename Duration::period>,
628 char subseconds[buffer_size];
630 if (ss > 0 || !truncate_trailing_subseconds_) {
631 fmt::format_to(subseconds, kSubsecondsPreformat, ss);
632 subseconds[buffer_size - 1] = 0;
633 if (truncate_trailing_subseconds_) {
635 for (
auto last = buffer_size - 2; last > 0 && subseconds[last] ==
'0';
637 subseconds[last] = 0;
641 auto res = ctx.out();
642 for (std::size_t i = 0; i < representation_size_; ++i) {
643 res = format_to(ctx.out(), fmt::runtime(representation_[i]), hours, mins,
650 constexpr void PushBackFmt(std::string_view fmt) {
651 if (representation_size_ >= kRepresentationCapacity) {
652 throw format_error(
"Format string complexity exceeds TimeOfDay limits");
654 representation_[representation_size_++] = fmt;
658 static constexpr std::size_t kRepresentationCapacity = 10;
660 std::string_view representation_[kRepresentationCapacity]{};
661 std::size_t representation_size_{0};
662 bool truncate_trailing_subseconds_{
true};