14#include <fmt/format.h>
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);
89 constexpr auto operator<=>(
const TimeOfDay&)
const =
default;
95 constexpr std::chrono::hours Hours()
const noexcept {
96 return std::chrono::duration_cast<std::chrono::hours>(since_midnight_);
100 constexpr std::chrono::minutes Minutes()
const noexcept;
102 constexpr std::chrono::seconds Seconds()
const noexcept;
105 constexpr DurationType Subseconds()
const noexcept;
108 constexpr DurationType SinceMidnight()
const noexcept;
112 constexpr static TimeOfDay FromHHMMInt(
int);
115 DurationType since_midnight_{};
121template <
typename LDuration,
typename RDuration>
122auto operator-(TimeOfDay<LDuration> lhs, TimeOfDay<RDuration> rhs) {
123 return lhs.SinceMidnight() - rhs.SinceMidnight();
126template <
typename Duration,
typename Rep,
typename Period>
127TimeOfDay<Duration> operator+(TimeOfDay<Duration> lhs, std::chrono::duration<Rep, Period> rhs) {
128 return TimeOfDay<Duration>{lhs.SinceMidnight() + rhs};
131template <
typename Duration,
typename Rep,
typename Period>
132TimeOfDay<Duration> operator-(TimeOfDay<Duration> lhs, std::chrono::duration<Rep, Period> rhs) {
133 return TimeOfDay<Duration>{lhs.SinceMidnight() - rhs};
137template <
typename Duration>
138logging::LogHelper& operator<<(
logging::LogHelper& lh, TimeOfDay<Duration> value) {
139 lh <<
fmt::to_string(value);
144template <
typename Rep,
typename Period>
145inline constexpr std::chrono::duration<Rep, Period>
146 kTwentyFourHours = std::chrono::duration_cast<std::chrono::duration<Rep, Period>>(std::chrono::hours{24});
148template <
typename Rep,
typename Period,
typename ORep = Rep,
typename OPeriod = Period>
149constexpr std::chrono::duration<Rep, Period> NormalizeTimeOfDay(std::chrono::duration<ORep, OPeriod> d) {
150 auto res = std::chrono::duration_cast<std::chrono::duration<Rep, Period>>(d) % kTwentyFourHours<Rep, Period>;
151 return res.count() < 0 ? res + kTwentyFourHours<Rep, Period> : res;
154template <
typename Ratio>
155inline constexpr std::size_t kDecimalPositions = 0;
157inline constexpr std::size_t kDecimalPositions<std::milli> = 3;
159inline constexpr std::size_t kDecimalPositions<std::micro> = 6;
161inline constexpr const std::size_t kDecimalPositions<std::nano> = 9;
163constexpr std::intmax_t MissingDigits(std::size_t n) {
166 constexpr std::intmax_t powers[]{
183template <
typename Ratio>
184struct HasMinutes : std::false_type {};
186template <intmax_t Num, intmax_t Den>
187struct HasMinutes<std::ratio<Num, Den>> : std::integral_constant<
bool, (Num <= 60LL)> {};
189template <
typename Rep,
typename Period>
190struct HasMinutes<std::chrono::duration<Rep, Period>> : HasMinutes<Period> {};
192template <
typename Duration>
193struct HasMinutes<TimeOfDay<Duration>> : HasMinutes<Duration> {};
196constexpr inline bool kHasMinutes = HasMinutes<T>{};
198template <
typename Ratio>
199struct HasSeconds : std::false_type {};
201template <intmax_t Num, intmax_t Den>
202struct HasSeconds<std::ratio<Num, Den>> : std::integral_constant<
bool, (Num == 1)> {};
204template <
typename Rep,
typename Period>
205struct HasSeconds<std::chrono::duration<Rep, Period>> : HasSeconds<Period> {};
207template <
typename Duration>
208struct HasSeconds<TimeOfDay<Duration>> : HasSeconds<Duration> {};
211constexpr inline bool kHasSeconds = HasSeconds<T>{};
213template <
typename Ratio>
214struct HasSubseconds : std::false_type {};
216template <intmax_t Num, intmax_t Den>
217struct HasSubseconds<std::ratio<Num, Den>> : std::integral_constant<
bool, (Den > 1)> {};
219template <
typename Rep,
typename Period>
220struct HasSubseconds<std::chrono::duration<Rep, Period>> : HasSubseconds<Period> {};
222template <
typename Duration>
223struct HasSubseconds<TimeOfDay<Duration>> : HasSubseconds<Duration> {};
226constexpr inline bool kHasSubseconds = HasSubseconds<T>{};
228template <
typename Rep,
typename Period>
229class TimeOfDayParser {
231 using DurationType = std::chrono::duration<Rep, Period>;
233 constexpr DurationType operator()(std::string_view str) {
237 if (position_ >= kSeconds) {
238 throw std::runtime_error{
fmt::format(
"Extra colon in TimeOfDay string `{}`", str)};
240 AssignCurrentPosition(str);
241 position_ =
static_cast<TimePart>(position_ + 1);
244 if (position_ != kSeconds) {
245 throw std::runtime_error{
fmt::format(
"Unexpected decimal point in TimeOfDay string `{}`", str)};
247 AssignCurrentPosition(str);
248 position_ =
static_cast<TimePart>(position_ + 1);
251 if (!std::isdigit(c)) {
252 throw std::runtime_error{
fmt::format(
"Unexpected character {} in TimeOfDay string `{}`", c, str)
255 if (position_ == kOverflow) {
257 }
else if (position_ == kSubseconds) {
258 if (digit_count_ >= kDecimalPositions<Period>) {
259 AssignCurrentPosition(str);
260 position_ = kOverflow;
263 }
else if (digit_count_ >= 2) {
264 throw std::runtime_error{
fmt::format(
"Too much digits in TimeOfDay string `{}`", str)};
267 current_number_ = current_number_ * 10 + (c -
'0');
271 if (position_ == kHour) {
272 throw std::runtime_error{
fmt::format(
273 "Expected to have at least minutes after hours in "
274 "TimeOfDay string `{}`",
278 AssignCurrentPosition(str);
280 auto sum = hours_ + minutes_ + seconds_ + subseconds_;
281 if (sum > kTwentyFourHours<Rep, Period>) {
282 throw std::runtime_error(
fmt::format(
"TimeOfDay value {} is out of range [00:00, 24:00)", str));
284 return NormalizeTimeOfDay<Rep, Period>(sum);
296 void AssignCurrentPosition(std::string_view str) {
299 if (digit_count_ < 1) {
300 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)};
305 hours_ = std::chrono::hours{current_number_};
309 if (digit_count_ != 2) {
310 throw std::runtime_error{
fmt::format(
"Not enough digits for minutes in TimeOfDay string `{}`", str)
313 if (current_number_ > 59) {
314 throw std::runtime_error{
fmt::format(
"Invalid value for minutes in TimeOfDay string `{}`", str)};
316 minutes_ = std::chrono::minutes{current_number_};
320 if (digit_count_ != 2) {
321 throw std::runtime_error{
fmt::format(
"Not enough digits for seconds in TimeOfDay string `{}`", str)
324 if (current_number_ > 59) {
325 throw std::runtime_error{
fmt::format(
"Invalid value for seconds in TimeOfDay string `{}`", str)};
327 seconds_ = std::chrono::seconds{current_number_};
331 if (digit_count_ < 1) {
332 throw std::runtime_error{
333 fmt::format(
"Not enough digits for subseconds in TimeOfDay string `{}`", str)
336 if constexpr (kHasSubseconds<Period>) {
338 if (digit_count_ < kDecimalPositions<Period>) {
339 current_number_ *= MissingDigits(kDecimalPositions<Period> - digit_count_);
341 subseconds_ = DurationType{current_number_};
353 TimePart position_ = kHour;
354 std::chrono::hours hours_{0};
355 std::chrono::minutes minutes_{0};
356 std::chrono::seconds seconds_{0};
357 DurationType subseconds_{0};
359 std::size_t digit_count_{0};
360 std::size_t current_number_{0};
364inline constexpr std::string_view kLongHourFormat =
"{0:0>#2d}";
366inline constexpr std::string_view kMinutesFormat =
"{1:0>#2d}";
368inline constexpr std::string_view kSecondsFormat =
"{2:0>#2d}";
370inline constexpr std::string_view kSubsecondsFormat =
"{3}";
372template <
typename Ratio>
373constexpr inline std::string_view kSubsecondsPreformat =
".0";
375inline constexpr std::string_view kSubsecondsPreformat<std::milli> =
".{:0>#3d}";
377inline constexpr std::string_view kSubsecondsPreformat<std::micro> =
".{:0>#6d}";
379inline constexpr std::string_view kSubsecondsPreformat<std::nano> =
".{:0>#9d}";
382template <
typename Ratio>
383inline constexpr std::array<std::string_view, 5> kDefaultFormat{
384 {kLongHourFormat,
":", kMinutesFormat,
":", kSecondsFormat}
389inline constexpr std::array<std::string_view, 3> kDefaultFormat<std::ratio<60, 1>>{
390 {kLongHourFormat,
":", kMinutesFormat}
395inline constexpr std::array<std::string_view, 3> kDefaultFormat<std::ratio<3600, 1>>{
396 {kLongHourFormat,
":", kMinutesFormat}
401template <
typename Rep,
typename Period>
402constexpr TimeOfDay<std::chrono::duration<Rep, Period>>::TimeOfDay(DurationType d)
noexcept
403 : since_midnight_{detail::NormalizeTimeOfDay<Rep, Period>(d)} {}
406template <
typename ORep,
typename OPeriod>
407constexpr TimeOfDay<std::chrono::duration<Rep, Period>>::TimeOfDay(std::chrono::duration<ORep, OPeriod> d)
noexcept
408 : since_midnight_{detail::NormalizeTimeOfDay<Rep, Period>(d)} {}
410template <
typename Rep,
typename Period>
411constexpr TimeOfDay<std::chrono::duration<Rep, Period>>::TimeOfDay(std::string_view str)
412 : since_midnight_{detail::TimeOfDayParser<Rep, Period>{}(str)}
416template <
typename Rep,
typename Period>
417constexpr std::chrono::minutes TimeOfDay<std::chrono::duration<Rep, Period>>::Minutes()
const noexcept {
418 if constexpr (detail::kHasMinutes<Period>) {
419 return std::chrono::duration_cast<std::chrono::minutes>(since_midnight_) -
420 std::chrono::duration_cast<std::chrono::minutes>(Hours());
422 return std::chrono::minutes{0};
426template <
typename Rep,
typename Period>
427constexpr std::chrono::seconds TimeOfDay<std::chrono::duration<Rep, Period>>::Seconds()
const noexcept {
428 if constexpr (detail::kHasSeconds<Period>) {
429 return std::chrono::duration_cast<std::chrono::seconds>(since_midnight_) -
430 std::chrono::duration_cast<
431 std::chrono::seconds>(std::chrono::duration_cast<std::chrono::minutes>(since_midnight_));
433 return std::chrono::seconds{0};
437template <
typename Rep,
typename Period>
438constexpr typename TimeOfDay<std::chrono::duration<Rep, Period>>::DurationType TimeOfDay<
439 std::chrono::duration<Rep, Period>>::Subseconds()
const noexcept {
440 if constexpr (detail::kHasSubseconds<Period>) {
441 return since_midnight_ - std::chrono::duration_cast<std::chrono::seconds>(since_midnight_);
443 return DurationType{0};
447template <
typename Rep,
typename Period>
448constexpr typename TimeOfDay<std::chrono::duration<Rep, Period>>::DurationType TimeOfDay<
449 std::chrono::duration<Rep, Period>>::SinceMidnight()
const noexcept {
450 return since_midnight_;
453template <
typename Rep,
typename Period>
454constexpr TimeOfDay<std::chrono::duration<Rep, Period>> TimeOfDay<
455 std::chrono::duration<Rep, Period>>::FromHHMMInt(
int hh_mm) {
456 auto mm = hh_mm % 100;
458 throw std::runtime_error{
fmt::format(
"Invalid value for minutes {} in int representation {}", mm, hh_mm)};
460 return TimeOfDay{std::chrono::minutes{hh_mm / 100 * 60 + mm}};
470template <
typename Duration>
473 static constexpr auto kLongHourFormat = USERVER_NAMESPACE::
utils::
datetime::detail::kLongHourFormat;
475 static constexpr auto kMinutesFormat = USERVER_NAMESPACE::
utils::
datetime::detail::kMinutesFormat;
477 static constexpr auto kSecondsFormat = USERVER_NAMESPACE::
utils::
datetime::detail::kSecondsFormat;
480 static constexpr auto kSubsecondsFormat = USERVER_NAMESPACE::
utils::
datetime::detail::kSubsecondsFormat;
482 static constexpr auto kSubsecondsPreformat = USERVER_NAMESPACE::
utils::
datetime::detail::kSubsecondsPreformat<
483 typename Duration::period>;
485 static constexpr std::string_view kLiteralPercent =
"%";
487 static constexpr auto
488 kDefaultFormat = USERVER_NAMESPACE::
utils::
datetime::detail::kDefaultFormat<
typename Duration::period>;
490 constexpr std::string_view GetFormatForKey(
char key) {
494 return kLongHourFormat;
496 return kMinutesFormat;
498 return kSecondsFormat;
500 throw format_error{
fmt::format(
"Unsupported format key {}", key)};
505 constexpr auto parse(format_parse_context& ctx) {
506 enum { kChar, kPercent, kKey } state = kChar;
507 const auto* it = ctx.begin();
508 const auto* end = ctx.end();
509 const auto* begin = it;
511 bool custom_format =
false;
512 std::size_t size = 0;
513 for (; it != end && *it !=
'}'; ++it) {
514 if (!custom_format) {
515 representation_size_ = 0;
516 custom_format =
true;
519 if (state == kPercent) {
520 PushBackFmt(kLiteralPercent);
523 if (state == kChar && size > 0) {
524 PushBackFmt({begin, size});
529 if (state == kPercent) {
530 PushBackFmt(GetFormatForKey(*it));
532 }
else if (state == kKey) {
542 if (!custom_format) {
543 for (
const auto fmt : kDefaultFormat) {
547 if (state == kChar) {
549 PushBackFmt({begin, size});
551 }
else if (state == kPercent) {
552 throw format_error{
"No format key after percent character"};
557 template <
typename FormatContext>
558 constexpr auto format(
const USERVER_NAMESPACE::
utils::
datetime::TimeOfDay<Duration>& value, FormatContext& ctx)
560 auto hours = value.Hours().count();
561 auto mins = value.Minutes().count();
562 auto secs = value.Seconds().count();
564 auto ss = value.Subseconds().count();
567 constexpr std::size_t buffer_size =
569 USERVER_NAMESPACE::
utils::
datetime::detail::kDecimalPositions<
typename Duration::period>,
573 char subseconds[buffer_size];
575 if (ss > 0 || !truncate_trailing_subseconds_) {
576 fmt::format_to(subseconds, kSubsecondsPreformat, ss);
577 subseconds[buffer_size - 1] = 0;
578 if (truncate_trailing_subseconds_) {
580 for (
auto last = buffer_size - 2; last > 0 && subseconds[last] ==
'0'; --last) {
581 subseconds[last] = 0;
586 auto res = ctx.out();
587 for (std::size_t i = 0; i < representation_size_; ++i) {
588 res = format_to(ctx.out(),
fmt::runtime(representation_[i]), hours, mins, secs, subseconds);
594 constexpr void PushBackFmt(std::string_view fmt) {
595 if (representation_size_ >= kRepresentationCapacity) {
596 throw format_error(
"Format string complexity exceeds TimeOfDay limits");
598 representation_[representation_size_++] = fmt;
602 static constexpr std::size_t kRepresentationCapacity = 10;
604 std::string_view representation_[kRepresentationCapacity]{};
605 std::size_t representation_size_{0};
606 bool truncate_trailing_subseconds_{
true};