Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Convert milliseconds to hours:minutes:seconds:milliseconds in C++

Tags:

c++

I'm looking for a way to print out milliseconds in this format using C++:

cout << hours << " Hours : " << minutes << " Minutes : " << seconds << " Seconds : " << milliseconds << " Milliseconds" << endl;

I know there are a ton of duplicate questions about this. But none of them really handle how to get the remainder in milliseconds. There are a few that do this using Java, but I want a solution in C++.

Edit:

I wanted to clarify the question. I'm looking to take a time value that I get for the time it takes a program to run and print out that time in a legible format for the user. Getting the standard hr:min:sec was straight forward. But including any remaining milliseconds was tripping me up.

like image 252
Berkyjay Avatar asked Nov 29 '22 00:11

Berkyjay


1 Answers

std::string format_duration( std::chrono::milliseconds ms ) {
    using namespace std::chrono;
    auto secs = duration_cast<seconds>(ms);
    ms -= duration_cast<milliseconds>(secs);
    auto mins = duration_cast<minutes>(secs);
    secs -= duration_cast<seconds>(mins);
    auto hour = duration_cast<hours>(mins);
    mins -= duration_cast<minutes>(hour);

    std::stringstream ss;
    ss << hour.count() << " Hours : " << mins.count() << " Minutes : " << secs.count() << " Seconds : " << ms.count() << " Milliseconds";
    return ss.str();
}

live example.

Extending this to days/years/etc should be easy (there isn't a predefined std::chrono duration type for days/years/etc prior to c++20 however).

But I can do better.

template<class Duration>
struct split_duration {
  Duration d;
  std::chrono::milliseconds leftover;

  split_duration( std::chrono::milliseconds ms ):
    d( std::chrono::duration_cast<Duration>(ms) ),
    leftover( ms - std::chrono::duration_cast<std::chrono::milliseconds>(d) )
  {}
};


template<class...Durations>
std::tuple<Durations...> durations( std::chrono::milliseconds ms ) {
  std::tuple<std::optional<split_duration<Durations>>...> tmp;
  ( (void)(
       (void)std::get<std::optional<split_duration<Durations>>>(tmp).emplace( ms ),
       ms = std::get<std::optional<split_duration<Durations>>>(tmp)->leftover
     ), ...
  );
  return std::make_tuple( std::get<std::optional<split_duration<Durations>>>( tmp )->d... );
}

template<class T>
struct tag_t {};
template<class T>
constexpr tag_t<T> tag = {};

inline std::string duration_name( tag_t<std::chrono::milliseconds> ) { return "ms"; }
inline std::string duration_name( tag_t<std::chrono::seconds> ) { return "Seconds"; }
inline std::string duration_name( tag_t<std::chrono::minutes> ) { return "Minutes"; }
inline std::string duration_name( tag_t<std::chrono::hours> ) { return "Hours"; }
// inline std::string duration_name( tag_t<std::chrono::days> ) { return "Days"; }
// inline std::string duration_name( tag_t<std::chrono::years> ) { return "Years"; }

template<class...Durations>
std::string format_duration( std::chrono::milliseconds ms ) {
    auto split = durations<Durations...>(ms);

    std::stringstream ss;

    (
        (void)( ss << duration_name(tag<Durations>) << ": " << std::get<Durations>(split).count() << " " ), ...
    );

    return ss.str();
}

Days/Years requires c++20, everything else is c++17.

You just call format_durations<Durations...>( some_ms ) and out comes a formatted string based off the Durations.... You do have to do it from most-to-least significant.

durations<Durations...> gives you a tuple breakdown of the time that has to be most-to-least; you could then reorder that before formatting if you chose.

Duplicate duration types leads to compile time errors, as std::get dies a horrible ambiguous death.

Live example.

like image 141
Yakk - Adam Nevraumont Avatar answered Mar 31 '23 22:03

Yakk - Adam Nevraumont