Subtract two timespec objects; find difference in time or duration
Asked Answered
T

3

6

The clock_gettime function requires a struct timespec object as an argument.

https://linux.die.net/man/3/clock_gettime

In the date and time utilities section of the C standard library, there is a function difftime which calculates the difference in two time_t objects.

https://en.cppreference.com/w/c/chrono

However there does not appear to be an equivalent function for struct timespec data.

I guess it is trivial to write one given that there are just two fields inside of a struct timespec (one for the number of seconds and one for the number of nanoseconds). However, it seems surprising that the standard library does not include this function for convenience.

Here is an example of how to do it:

(time_stop.tv_sec - time_start.tv_sec)
    + 1.0e-9 * (time_stop.tv_nsec - time_start.tv_nsec);

Does a function to find the difference in time between two struct timespec objects exist? Or is it perhaps not included because clock_gettime can be called with different arguments for different types of clock?

Terzas answered 16/8, 2021 at 14:22 Comment(4)
Good, convenient utility functions for manipulating struct timespec (and the older struct timeval — it'd be nice to have a set of those, too) are not always so trivial to write. I wish there were a standard set, too. I suspect the reason is that C has never sought to be voluminous or complete in that sort of way.Doubleganger
There is at least one theoretical case where a hypothetical timespec_diff function would not be sufficient, or would need an extra flags argument or something. If you implemented the CLOCK_UTC clock described at cl.cam.ac.uk/~mgk25/posix-clocks.html, you'd want a way to take differences between two of those values that either did or didn't honor leap seconds, or that returned an error if the computation were impossible due to leapsecond ambiguity or lack of sufficient leapsecond information.Doubleganger
@SteveSummit Many systems have macros for working with timeval structs, but not timespec for some reason. man7.org/linux/man-pages/man3/timeradd.3.htmlCuster
@FreelanceConsultant, Anything more on this question that I may help you with?Sack
S
7

However, it seems surprising that the standard library does not include this function for convenience.

Does a function to find the difference in time between two struct timespec objects exist?

double difftime(time_t time1, time_t time0) returns seconds, regardless of the time units and type used for time_t.

time_t is some real type, often in seconds, but not specified as such.

difftime() performs the subtraction and returns seconds. User code cannot as there is no seconds per time_t constant,

struct timespec is specified to use seconds and nanoseconds, so a time difference in units and desired type is readily code-able.


Perhaps as:

#include <assert.h>
#include <time.h>

struct timespec diff_timespec(const struct timespec *time1,
    const struct timespec *time0) {
  assert(time1);
  assert(time0);
  struct timespec diff = {.tv_sec = time1->tv_sec - time0->tv_sec, //
      .tv_nsec = time1->tv_nsec - time0->tv_nsec};
  if (diff.tv_nsec < 0) {
    diff.tv_nsec += 1000000000; // nsec/sec
    diff.tv_sec--;
  }
  return diff;
}

OR

double diff_timespec(const struct timespec *time1, const struct timespec *time0) {
  return (time1->tv_sec - time0->tv_sec)
      + (time1->tv_nsec - time0->tv_nsec) / 1000000000.0;
}

time.h is missing much: mktime_utc(), robust timezone support, etc.

Sack answered 16/8, 2021 at 14:32 Comment(6)
@FreelanceConsultant * 1e9 and / 1e9 make for slightly different results. / 1e9 being the better one. Consider posting an alternative if you like.Sack
I'd say that tantalizing comment is worth at least a pointer to a reference! (And did you mean * 1e-9?)Doubleganger
Sorry did I miss a minus sign from the edit?Terzas
@FreelanceConsultant, Yes I meant 1.0e-9. Consider if * 1e-9 and / 1e9 did not differ, the compiler could encode either way and so use the most efficient. So if the same, it makes no difference, but if different, which is better? 1.0e-9, as a double is more like 0.000000001000000000000000062... as the double constant itself is not 1.0e-9. OTOH / 1.0e9 is division with an exact desired constant. If more needed, consider posting a question. Given the exactitudes of time, generating the best answer is called for, even if it costs a division vs. multiplication.Sack
If you take a casual stroll through the park the uncertainty caused by time dilation due to your movement will be many orders of magnitude more than the difference between /1e9 and *1e-9. There's no human-made clock that could measure time that accurately.Upbeat
@Upbeat Although the practicality of your comment is about time is truthful, the goal of repeatability of code comes into play. With variant implementations that differ by as small as a ULP, do we accept both, or the better one? Much analysis of FP code is triggered by such off-by-1 cases. There are less discrepancies when each implementation forms the best answer.Sack
W
1

Here are some conversion/arithmetic functions that always normalize timespec structures such that tv_nsec is in the range [0, 999999999]. and is always added to tv_sec, but allow positive or negative tv_sec. This is consistent with the best interpretation of the spec as others have described above.

#include <cmath>
#include <time.h>

/**
 * @brief Normalize a tv_sec, tv_nsec pair into a timespec such that tv_nsec is in the range [0, 999999999]
 * 
 * Per the timespec specification, tv_nsec should be positive, in the range 0 to 999,999,999. It is always
 * added to tv_sec (even when tv_sec is negative).
 * For this reason, if t is the time in secs, tv_sec is actually floor(t), and nsec is (t - floor(t)) * 1.0e9.
 * 
 * @param tv_sec   (long) time in seconds. Msy be negative.
 * @param tv_nsec  (long) time in nanoseconds to be added to tv_sec. May be negative or >= 1000000000.
 * 
 * @return timespec  Normalized timespec value with tv_nsec in the range [0, 999999999]
 */
inline static struct timespec normalize_timespec(long tv_sec, long tv_nsec) {
    long const billion = 1000000000;
    struct timespec t;
    t.tv_sec = (tv_nsec >= 0 ? tv_nsec : tv_nsec - (billion-1)) / billion;
    t.tv_nsec = tv_nsec - t.tv_sec * billion;
    t.tv_sec += tv_sec;
    return t;
}

/**
 * @brief Subtract two timespec values
 * 
 * @param time1 Ending time
 * @param time0 Starting time
 * @return timespec  (time1 - time0)
 */
inline static struct timespec timespec_subtract(const struct timespec& time1, const struct timespec& time0) {
    return normalize_timespec(time1.tv_sec - time0.tv_sec, time1.tv_nsec - time0.tv_nsec);
}

/**
 * @brief Add two timespec values
 * 
 * @param time1 Ending time
 * @param time0 Starting time
 * @return timespec  (time1 + time0)
 */
inline static struct timespec timespec_add(const struct timespec& time1, const struct timespec& time2) {
    return normalize_timespec(time1.tv_sec + time2.tv_sec, time1.tv_nsec + time2.tv_nsec);
}

/**
 * @brief Convert a timespec to a double in seconds
 * 
 * @param ts 
 * @return double 
 */
inline double timespec_to_secs(const struct timespec& ts) {
    return (double)ts.tv_sec + (double)ts.tv_nsec / 1.0e9;
}

/**
 * @brief Convert a double in seconds to a timespec
 */
inline struct timespec secs_to_timespec(double secs) {
    auto sec = (long)floor(secs);
    auto nsec = (long)round((secs - (double)sec) * 1.0e9);
    return normalize_timespec(sec, nsec);
}
Wittgenstein answered 5/4, 2024 at 19:14 Comment(4)
I agree your extension to the spec for negative timespec makes sense. Consider this alternative implementation of normalize_timespec: gcc.godbolt.org/z/TheKePPT3Weiler
@HowardHinnant Yes, your implementation will be more efficient...Wittgenstein
@HowardHinnant edited my solution to use your implementation. Thank you.Wittgenstein
It would be much better to use either long long or int64_t instead of long. as long is quite often 32 bits - see 64-bit WindowsViyella
M
0

I came up with this concise and simple approach for time differences where the nanoseconds fit within a long. I'm curious if it is correct. It's basically the same as the question asker's, but nobody has commented on the correctness of that.

The intention was to maximize speed, accuracy, and compatibility, when the difference is under 2 seconds for a 4-byte long. This would extend to 4.2 seconds if unsigned were used.

long time_getdiff_nanos(struct timespec tp_old, struct timespec tp_new)
{
    return (tp_new.tv_sec - tp_old.tv_sec) * (long)(1000000000) +
        tp_new.tv_nsec - tp_old.tv_nsec;
}
double time_getdiff_secs(struct timespec tp_old, struct timespec tp_new)
{
    return time_getdiff_nanos(tp_old, tp_new) / (double)(1000000000);
}
Mincey answered 13/9, 2024 at 22:28 Comment(3)
I removed my critical comment once I figured out the casting and added parentheses. Thank you. The functional-style-cast is not actually recommended in C++, I used to think it was the same as the C-style and have now learned it is not. EDIT: sorry for removing my post while you were replying !Mincey
Re “I'm curious if it is correct”: Suppose long is 32 bits, so the maximum value representable in long is 2,147,483,647, and that tp_new.tv_sec is 2, tp_new_tv.nsec is 500,000,000, tp_old.tv_sec is 0, and tp_old.tv_nsec is 600,000.000. Then the net difference in nanoseconds is 1,900,000,000, which fits in long and is under the 2 seconds you specified. However, (tp_new.tv_sec - tp_old.tv_sec) * (long)(1000000000) is 2,000,000,000, and adding tp_new.tv_nsec would produce 2,500,000,000, which overflows the 2,147,483,647 limit, so the behavior is not defined by the C standard.Julesjuley
Changing + tp_new.tv_nsec - tp_old.tv_nsec to + (tp_new.tv_nsec - tp_old.tv_nsec) would fix that issue, as the latter never goes outside (−1,000,000,000, +1,000,000,000), and the total sum is less than 2,000,000,000 by premise that the time difference is under 2 seconds.Julesjuley

© 2022 - 2025 — McMap. All rights reserved.