15

I'm doing a lot of calculations with times, building time objects relative to other time objects by adding seconds. The code is supposed to run on embedded devices and servers. Most documentations say about time_t that it's some arithmetic type, storing usually the time since the epoch. How safe is it to assume that time_t store a number of seconds since something? If we can assume that, then we can just use addition and subtraction rather than localtime, mktime and difftime.

So far I've solved the problem by using a constexpr bool time_tUsesSeconds, denoting whether it is safe to assume that time_t uses seconds. If it's non-portable to assume time_t is in seconds, is there a way to initialize that constant automatically?

time_t timeByAddingSeconds(time_t theTime, int timeIntervalSeconds) {
    if (Time_tUsesSeconds){
        return theTime + timeIntervalSeconds;
    } else {
        tm timeComponents = *localtime(&theTime);
        timeComponents.tm_sec += timeIntervalSeconds;
        return mktime(&timeComponents);
    }
}
lurker
  • 56,987
  • 9
  • 69
  • 103
Ant6n
  • 1,887
  • 1
  • 20
  • 26
  • The units of `time_t` are determined by the function that sets it, not by the type itself. So if the function you use says it gives "seconds" then you can be assured that's what you get. – lurker Oct 19 '13 at 13:33

6 Answers6

16

The fact that it is in seconds is stated by the POSIX specification, so, if you're coding for POSIX-compliant environments, you can rely on that.

The C++ standard also states that time_t must be an arithmetic type.

Anyway, the Unix timing system (second since the Epoch) is going to overflow in 2038. So, it's very likely that, before this date, C++ implementations will switch to other non-int data types (either a 64-bit int or a more complex datatype). Anyway, switching to a 64-bit int would break binary compatibility with previous code (since it requires bigger variables), and everything should be recompiled. Using 32-bit opaque handles would not break binary compatibility, you can change the underlying library, and everything still works, but time_t would not a time in seconds anymore, it'd be an index for an array of times in seconds. For this reason, it's suggested that you use the functions you mentioned to manipulate time_t values, and do not assume anything on time_t.

phuclv
  • 37,963
  • 15
  • 156
  • 475
Giulio Franco
  • 3,170
  • 15
  • 18
  • 3
    Realistically, nobody will ever implement `time_t` as anything other than an arithmetic type (which is mandatory on POSIX systems). Current 64 bit UNIX-like systems already implement it as a 64 bit integer (since the ABI change for 64 bit architectures already forced the recompilation), and [the same holds on Windows](http://msdn.microsoft.com/en-us/library/3b2e7499.aspx). Any other solution is bound to be even more traumatic, since it's commonplace even on non-POSIX systems to treat `time_t` as "number of seconds since epoch", and changing its semantics would silently break a lot of stuff. – Matteo Italia Oct 19 '13 at 13:50
  • 5
    @MatteoItalia - the C and C++ standards both require `time_t` to be an arithmetic type. – Pete Becker Oct 19 '13 at 14:25
  • The actual implementation to fix y2038 problem is the addition of `*time64` syscalls and the change of `time_t` to 64-bit in 32-bit Linux and glibc. The count is also an integral time exactly like before and similar to 64-bit Linux, just a wider `time_t` value. See [64-bit time_t in Linux Kernel](https://stackoverflow.com/a/64747936/995714) – phuclv Nov 28 '20 at 00:06
4

If C++11 is available, you can use std::chrono::system_clock's to_time_t and from_time_t to convert to/from std::chrono::time_point, and use chrono's arithmetic operators.

If your calculations involve the Gregorian calendar, you can use the HowardHinnant/date library, or C++20's new calendar facilities in chrono (they have essentially the same API).

Emile Cormier
  • 28,391
  • 15
  • 94
  • 122
2

There is no requirement in standard C or in standard C++ for the units that time_t represents. To work with seconds portably you need to use struct tm. You can convert between time_t and struct tm with mktime and localtime.

Pete Becker
  • 74,985
  • 8
  • 76
  • 165
  • This isn't correct. As Giulio points out below POSIX defines time() (and thus its return type) as returning seconds since the epoch. Obviously it would be possible to have a non-POSIX system with a typedef of the same name interpreted differently, but I don't think that's what the question was about (and no such systems exist anyway). – Andy Ross Oct 19 '13 at 13:45
  • 5
    Um, POSIX does not define the whole world. In order to be sure that `time_t` represents seconds you **have** to be on a system that promises to meet POSIX requirements. Neither the C nor the C++ standard requires that. I've amended my answer to refer specifically to those standards. – Pete Becker Oct 19 '13 at 14:20
  • Are you aware of a real system on which time_t (defined originally in Unix of course) has a unit of other than seconds? I'm not. It's compatible everywhere, and for obvious reasons. If you don't either, I don't see the value to having that discussion. You might as well caution people not to use `printf()` because some fictitious system defines it as a synonym for `abort()`. – Andy Ross Oct 20 '13 at 03:11
  • 2
    @AndyRoss - a system that defined `printf` as a synonym for `abort` would not satisfy the C standard. A system that did not use seconds for `time_t` would. I really don't understand this insistence on using something that is not required to be portable instead of something that is. – Pete Becker Oct 20 '13 at 22:51
2

Rather than determine whether time_t is in seconds, since time_t is an arithmetic type, you can instead calculate a time_t value that represents one second, and work with that. This answer I wrote before explains the method and has some caveats, here's some example code (bad_time() is a custom exception class, here):

time_t get_sec_diff() {
    std::tm datum_day;
    datum_day.tm_sec = 0;
    datum_day.tm_min = 0;
    datum_day.tm_hour = 12;
    datum_day.tm_mday = 2;
    datum_day.tm_mon = 0;
    datum_day.tm_year = 30;
    datum_day.tm_isdst = -1;

    const time_t datum_time = mktime(&datum_day);
    if ( datum_time == -1 ) {
        throw bad_time();
    }

    datum_day.tm_sec += 1;
    const time_t next_sec_time = mktime(&datum_day);
    if ( next_sec_time == -1 ) {
        throw bad_time();
    }

    return (next_sec_time - datum_time);
}

You can call the function once and store the value in a const, and then just use it whenever you need a time_t second. I don't think it'll work in a constexpr though.

Community
  • 1
  • 1
Crowman
  • 25,242
  • 5
  • 48
  • 56
  • I kind of like it, but it does assume that time_t is in seconds, up to some constant. I.e. between any two seconds, there's an equal difference. – Ant6n Oct 21 '13 at 02:16
  • 1
    Well, it implies that `time_t` can accurately represent whole seconds in some way, yes. If it can't, then you're just out of luck trying to perform this kind of per-second arithmetic on a `time_t`, so you're not really losing anything if that's the case. If `time_t` happens to be implemented as a `double` (I'm not aware of any modern system that does) then you're subject to the normal floating point precision issues, too, i.e. trying to add 20 million seconds may give odd results. You can always check your results against a `struct tm`, except that's what you're trying to avoid. – Crowman Oct 21 '13 at 02:25
  • As a high level check, you can set up two `struct tm`s sufficiently far apart. For instance, take two `struct tm`s exactly one year apart (not spanning a leap year), then turn the earlier one into a `time_t`, add `get_sec_diff() * 60 * 60 * 24 * 365` to it, and check with `localtime()` to see if you get a `struct tm` that matches the later. If you do, then you should be good, since if the return from `get_sec_diff()` wasn't exactly one second, you ought to be miles off otherwise. – Crowman Oct 21 '13 at 02:41
  • That being said, of course, adding seconds to `struct tm`s really isn't that difficult, so that'll usually be a better solution. Note that deliberately overflowing the members of a `struct tm` isn't guaranteed to work - `mktime()` will make any out-of-range values "forced to the ranges indicated", but there's nothing in the standard that says it can't just truncate them without changing the other members (i.e. setting `tm_sec` to 70 isn't required to advance `tm_min` by 1, for instance). – Crowman Oct 21 '13 at 02:48
  • It may be difficult to find a year that is going to be 365*60*60*24 seconds long in every implementation due to leap seconds. Maybe a year far in the past? – Ant6n Oct 24 '13 at 13:16
  • It's true that leap seconds can present difficulties for dealing with times in generally, but the years in which they have occurred are known, so you can just pick a year without one. There were no leap seconds from 1999 through 2004 inclusive, for instance. In reality, most systems will not actually record leap seconds, they'll just update the current time when one occurs. I believe that POSIX requires it to work this way, for instance. – Crowman Oct 24 '13 at 14:06
0

My two cents: on Windows it is in seconds over time but the time it takes for one second to increment to the next is usually 18*54.925 ms and sometimes 19*54.925. The reason for this is explained in this post.

Community
  • 1
  • 1
Olof Forshell
  • 3,169
  • 22
  • 28
  • 1
    55 msec was the old DOS clock rate. Windows versions since 1993 in the NT branch use a default clock interrupt rate of 64 ticks/sec, 15.625 msec. – Hans Passant Oct 19 '13 at 14:21
  • My four yo HP laptop w W7 has a stated clock frequency of 2GHz. When I measure it using my method I get a result of 1995-1997 MHz which open source products also get. Would I get that with a 15.625 period? – Olof Forshell Oct 20 '13 at 19:28
0

(Answering own question)

One answer suggests that as long as one is using posix, time_t is in seconds and arithmetic on time_t should work.

A second answer calculates the time_t per second, and uses that as a factor when doing arithmetic. But there are still some assumptions about time_t made.

In the end I decided portability is more important, I don't want my code to fail silently on some embedded device. So I used a third way. It involves storing an integer denoting the time since the program starts. I.e. I define

 const static time_t time0 = time(nullptr);
 static tm time0Components = *localtime(&time0);

All time values used throughout the program are just integers, denoting the time difference in seconds since time0. To go from time_t to this delta seconds, I use difftime. To go back to time_t, I use something like this:

time_t getTime_t(int timeDeltaSeconds) {
    tm components = time0Components;
    components.tm_sec += timeDeltaSeconds;
    return mktime(&components);
}

This approach allows making operations like +,- cheap, but going back to time_t is expensive. Note that the time delta values are only meaningful for the current run of the program. Note also that time0Components has to be updated when there's a time zone change.

Ant6n
  • 1,887
  • 1
  • 20
  • 26