1

I'm trying to find the best way to see if the current time is before a specified time. Say I want to see if it's before 14:32. What's the best way to do this in C++? Ideally I'd be able to build some time object that represents 14:32, then compare it with the current time as some object. This is what I'm doing right now. Pretty messy and uses 3 different representations of time.

int hour_ = 14;
int min_ = 32;
std::chrono::system_clock::time_point now = std::chrono::system_clock::now();
std::time_t tt = std::chrono::system_clock::to_time_t(now);
std::tm utc_tm = *gmtime(&tt);
if ((utc_tm.tm_hour < hour_) || (utc_tm.tm_hour == hour_ && utc_tm.tm_min < min_) ) {
    std::cout << "It's before " << hour_ << ":" << min_ << std::endl;
}
  • If you used std::chrono::hours and std::chrono::minutes to store hours and minutes would it make it any easier? – Stephen M. Webb Oct 27 '21 at 20:28
  • 1
    `std::chrono::time_point` has the usual inequality operators defined, so getting everything into that format should make the comparison itself trivial. – Useless Oct 27 '21 at 20:29
  • @Useless that would be better than my current solution because it would narrow it down to me using only two time libraries. but how would I create a time_point at 14:32 anyways? – Joshua Segal Oct 27 '21 at 20:43
  • 1
    Given a local time-of-day `t`, what should happen when `t` occurs twice in the current day? What should happen when `t` doesn't occur at all? I can see a few reasonable answers to these questions. – Howard Hinnant Oct 27 '21 at 20:50
  • @HowardHinnant I was literally just watching your video on chrono and date.h! I really wasn't expecting to hear from you! This is a really good question, I'd say to assume the two times are in the same UTC timezone so each time only occurs once. I was just thinking of a simple comparison to two UTC times, ignoring the days. Maybe I'm not following what you mean by `t` occurring twice in a day – Joshua Segal Oct 27 '21 at 21:04
  • Assuming just one time zone for `t`, but that during the current day, the UTC offset may change. This typically happens twice a year for time zones that observe daylight saving. For example if the UTC offset falls back an hour, `t` might happen twice. And if the UTC moves forward an hour, `t` might be skipped. If you want to assume that these shenanigans never happen to your `t` in your time zone, it might be wise to check for the case anyway, and throw an exception (or return an error code) if it *does* happen (a quite reasonable approach). – Howard Hinnant Oct 27 '21 at 21:21
  • I see, I'll handle that kind of stuff, thanks for pointing those edge cases out. – Joshua Segal Oct 27 '21 at 21:27
  • @HowardHinnant Do you think there's a simpler way to do what I want using C++? I want to be able to construct a time point as a structure like {hour,min,sec} and then just compare it to the current time. Is this possible in any of the C++ standard libraries. I'm open to your date.h library too if it could do it. – Joshua Segal Oct 27 '21 at 21:27
  • 1
    I'll add an answer for C++20, which only VS is shipping as I write this. And I'll point out how to translate that C++20 solution to my lib if C++20 isn't an option. As it uses time zones, some installation is required. My answer is an hour away due to other obligations I have at the moment... – Howard Hinnant Oct 27 '21 at 21:30

2 Answers2

1

Here is how you can do it in C++20. Later I will show how to convert this to use a free, open-source C++20 chrono preview library which works with C++11/14/17.

#include <chrono>

bool
is_now_before(std::chrono::minutes local_config_tod)
{
    using namespace std::chrono;

    auto tz = current_zone();
    auto now = system_clock::now();

    auto local_day = floor<days>(zoned_time{tz, now}.get_local_time());
    auto utc_config = zoned_time{tz, local_day + local_config_tod}.get_sys_time();
    return now < utc_config;
}

The parameter has type minutes which will be interpreted to be the local time of day in minutes. For example 14:32 is represented by minutes{872}. This representation is compact (one integer), and it is trivial to convert {hours, minutes} to just minutes (shown below).

current_zone() gets the computer's current local time zone. This information is needed twice in this function, so it is best to just get it once. Not only does this save the result, but it also sidesteps the problem of the local time zone changing out from under you (between multiple calls) in a mobile device.

Next the current time is obtained (just once) via system_clock. This gives the current time in UTC.

Now we have a choice:

  1. We could do the comparison in UTC, or
  2. We could do the comparison in local time.

Doing the comparison in UTC is less error prone in the corner case that the UTC offset is changing in the current local day (such as going on or off of daylight saving).

To convert the local config time-of-day (local_config_tod) to a UTC time_point one first has to find out what the current local day is. In general this can be different than the current UTC day. So the current UTC now has to be converted to local time, and then truncated to days-precision:

auto local_day = floor<days>(zoned_time{tz, now}.get_local_time());

Now a local time_point can be created simply by summing local_day and local_config_tod. This local time_point can then be converted back into UTC (a time_point based on system_clock but with seconds precision):

auto utc_config = zoned_time{tz, local_day + local_config_tod}.get_sys_time();

The line of code above handles the corner cases for you. If there is not a unique (one-to-one) mapping from local time to UTC, then an exception is thrown. The .what() of the exception type will have a detailed description about how this mapping is either ambiguous, or non-existent.

Assuming the above mapping does not throw an exception, you can simply compare these two UTC time_points:

return now < utc_config;

The precision of this comparison is with whatever precision your system_clock has (typically microseconds to nanoseconds).

This can be exercised like so:

int hour_ = 14;
int min_ = 32;
using namespace std::chrono;
auto b = is_now_before(hours{hour_} + minutes{min_});

If 14 and 32 are literals (and you're in C++14 or later), it can be shortened to:

auto b = is_now_before(14h + 32min);

If you are using a standard prior to C++17, the zoned_time constructions will require an explicit template parameter:

auto local_day = floor<days>(zoned_time<system_clock::duration>{tz, now}.get_local_time());
auto utc_config = zoned_time<minutes>{tz, local_day + local_config_tod}.get_sys_time();

If you would like to use the free, open-source C++20 chrono preview library, add #include "date/tz.h" and using namespace date;. Some installation is required.

If you would like to avoid an exception in the case that local_day + local_config_tod does not have a unique mapping to UTC, that is also possible with minor changes to is_now_before. But you will have to decide things such as: Do I want to compare against the first or second local_config_tod of the local_day (in case the UTC offset has been decreased).

Oops! Is the config time already UTC?

On re-reading your question it occurred to me that I may have misread your question. If 14:32 is UTC, then things get much, much simpler! And rather than removing my answer showing the local 14:32 interpretation, I thought it would be better to add this, so future readers could pick either solution.

Assuming the config is a UTC time, then time zones play no role at all:

#include <chrono>

bool
is_now_before(std::chrono::minutes utc_config_tod)
{
    using namespace std::chrono;

    auto now = system_clock::now();

    auto utc_day = floor<days>(now);
    return now < utc_day + utc_config_tod;
}

The current day in UTC is simply:

auto utc_day = floor<days>(now);

And now the config date-time is simply utc_day + utc_config_tod. This is just drop-dead simple.

If you can't use C++20, the free, open-source C++20 chrono preview library is also much simpler now as it is header-only, requiring no installation at all. Just #include "date/date.h" and add using namespace date;.

Howard Hinnant
  • 206,506
  • 52
  • 449
  • 577
0

In C++ we can use the mt_structure from the date/time functions (documentation here: https://en.cppreference.com/w/cpp/chrono/c/tm) Here is how I would print the date, and check to see if it's past a certain time

#include <iostream>
#include <ctime>
#include <chrono>
using namespace std;

int main()
{
  time_t t = time(0);   // get time now
    tm* now = localtime(&t);
    cout << (now->tm_year + 1900) << '-' 
        << (now->tm_mon + 1) << '-'
        <<  now->tm_mday << ", " 
        << now->tm_hour << ":" << now->tm_min
        << "\n";

  int hour = 7, minute = 30;
  if((now->tm_hour > hour) || (now->tm_hour == hour && now->tm_min >= minute))
    cout << "it's past 7:30\n";
  else 
    cout << "it's not past 7:30";
}
  

prints:

2021-10-27, 20:40
it's past 7:30
Jacob Glik
  • 223
  • 3
  • 10
  • This is basically the same code I have. Hopefully, there's a more elegant way to do this in c++. Thank you for the input :) – Joshua Segal Oct 27 '21 at 20:46