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};