# How do I represent a 24-hour clock with the STL?

## How do I represent a 24-hour clock with the STL?

Contents

Problem Description:

I’m looking for a solution from the STL, for dealing with "time of day". I am working on a simple unit test exercise, with behavior depending on whether current time is in the morning, evening or night.

For a first iteration I used a humble integer as a stand-in for some "time object":

``````using TimeOfDay = int;
constexpr bool isBetween(TimeOfDay in, TimeOfDay min, TimeOfDay max) noexcept {
return in >= min && in <= max;
}
constexpr bool isMorning(TimeOfDay in) noexcept {
return isBetween(in, 6, 12); }
constexpr bool isEvening(TimeOfDay in) noexcept {
return isBetween(in, 18, 22);
}
constexpr bool isNight(TimeOfDay in) noexcept {
return isBetween(in, 22, 24) || isBetween(in, 0, 6);
}

constexpr string_view getGreetingFor(TimeOfDay time) noexcept {
if (isMorning(time)) {
return "Good morning "sv;
}
if (isEvening(time)) {
return "Good evening "sv;
}
if (isNight(time)) {
return "Good night "sv;
}
return "Hello "sv;
}
``````

This works but has a couple of smells:

• the `int` simply isn’t the right type to represent a 24 hour clock
• `isNight()` requires an unnecessarily complicated comparison, due to wrapping (22-06)
• ideally I would like to be able actually use the system clock for some of my tests.
• `std::chrono::system_clock::now()` returns a `std::chrono::time_point`, so my ideal type should probably be something that can be compared to a `time_point`, or easily constructed from a `time_point`.

Any pointers would be very appreciated!

(I am working in Visual Studio with C++Latest (preview of the C++ working draft, so roughly C++23))

## Solution – 1

If you are talking about time within a day, detached from a date, use a `std::chrono::duration`. For integer hours, there is the alias `std::chrono::hours`.

See also `std::chrono::hh_mm_ss`

Something like this:

``````#include <iostream>
#include <chrono>

using namespace std::chrono;

int main()
{
auto now = system_clock::now().time_since_epoch();
auto today = duration_cast<days>(now);
hh_mm_ss time { now - today };
std::cout << time.hours() << time.minutes() << time.seconds();
}
``````

See it on coliru

## Solution – 2

The "time of day" should be stored in a `std::chrono::duration`. To detach only the h/m/s information of any `duration`, you can do:

``````auto time_info = my_duration - std::chrono::round<std::chrono::days>(my_duration);
``````

Similarly, you can detach the h/m/s information of a `time_point` with the same interface:

``````auto now = std::chrono::system_clock::now();
auto time_info = now - std::chrono::round<std::chrono::days>(now);
``````

Then you can use the helper class `hh_mm_ss` to examine the information:

``````auto hms = std::chrono::hh_mm_ss{ time_info };
std::cout << hms.hours() << hms.minutes() << hms.seconds();
``````

To check if a `duration` is between two other time, you can do:

``````using TimeOfDay = std::chrono::seconds;

constexpr bool isBetween(TimeOfDay in, TimeOfDay min, TimeOfDay max) noexcept {
return in >= min && in <= max;
}

constexpr bool isMorning(TimeOfDay in) noexcept {
return isBetween(in, std::chrono::hours{6}, std::chrono::hours{12});
}
``````

You can also enable the literals by `using namespace std::chrono_literals` or `using namespace std::literals`:

``````constexpr bool isMorning(TimeOfDay in) noexcept {
return isBetween(in, 6h, 12h);
}
``````

For the wrapping in `isEvening`, you can potentially do something like: `isBetween(in + 12h - days{1}, 10h, 18h)`, but I think what you have right now have a much cleaner and easier to follow logic.

And this can be easily turned back to a `time_point` if needed:

``````auto today = std::chrono::round<std::chrono::days>(std::chrono::system_clock::now());
auto now = today + time_info;
``````

## Solution – 3

I’m going to start with the assumption that you’re looking for the local time of day. `chrono::system_clock` represents UTC. So I recommend a helper function that takes `std::chrono::system_clock::time_point` and returns a `std::chrono::system_clock::duration` which represents the time elapsed since the most recent local midnight:

``````using TimeOfDay = std::chrono::system_clock::duration;

TimeOfDay
get_local_time_of_day(std::chrono::system_clock::time_point tp) noexcept
{
using namespace std::chrono;
auto time = current_zone()->to_local(tp);
return time - floor<days>(time);
}
``````

`chrono::current_zone()` returns a pointer to the `chrono::time_zone` that your computer is currently set to. This is used to convert the `system_clock::time_point` to a local `time_point`. This local `time_point` is a `chrono::time_point` but with a different clock that means "local time".

The expression `floor<days>(time)` truncates the `time_point` to precision `days`, which effectively gives you a pointer to the most recently passed local midnight. Subtracting that from `time` gives you a `chrono::duration` since the local midnight. This `duration` will have the same precision as `tp`.

This function can not be made `constexpr` because the rules for transforming UTC to local time is in the hands of your politicians and is subject to change (in many countries twice a year!).

Now you can write `isMorning` (et al) like this:

``````bool isMorning(std::chrono::system_clock::time_point in) noexcept {
using namespace std::literals;
return isBetween(get_local_time_of_day(in), 6h, 12h); }
``````

And call it like this:

``````if (isMorning(system_clock::now())) ...
``````

Your logic for `isNight` looks fine to me. And your code for `isBetween` can stay like it is (picking up the new definition of `TimeOfDay`).

Rate this post
We use cookies in order to give you the best possible experience on our website. By continuing to use this site, you agree to our use of cookies.