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 astd::chrono::time_point
, so my ideal type should probably be something that can be compared to atime_point
, or easily constructed from atime_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))
CodePudding user response:
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
).
CodePudding user response:
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();
}
CodePudding user response:
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;