Home > front end >  How to tell if it's before a configured time in C ?
How to tell if it's before a configured time in C ?

Time:10-28

I'm trying to find the best way to see if the current time is passed 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;
}

CodePudding user response:

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

CodePudding user response:

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

  • Related