3

I have a string representing seconds since epoch, and I need to convert it to a human readable string. I've seen a few posts online that suggest simply casting an integer to time_t as so:

time_t time = (time_t)(atoi(secs_since_epoch_str));

But, if I look up the definition of time_t:

typedef /* unspecified */ time_t;

Although not defined by the C standard, this is almost always an integral 
value holding the number of seconds (not counting leap seconds) since 00:00,
Jan 1 1970 UTC, corresponding to POSIX time.

So, this is not guaranteed to work. I'm wondering if there's a proper way of doing this?

HardcoreHenry
  • 5,909
  • 2
  • 19
  • 44
  • 3
    It's no use casting *after* the `int` conversion is made with `atoi`. Better to extract to `unsigned long long` instead, with `strtoull` and then convert to `time_t`. – Weather Vane Oct 26 '22 at 12:59
  • @WeatherVane I'd agree with you, but OP said that these are the seconds since an epoch. Maybe in their use case the time passed is never larger than 2^32 seconds. – Fra93 Oct 26 '22 at 13:01
  • 2
    @Fra93 good point, but we should have learned from the Millenium Bug that code should be future proof. This will overflow `int` in year 2038, which isn't so far away. – Weather Vane Oct 26 '22 at 13:11
  • The 32-bit integer range is not adequate for times from 2038-01-18 onwards. On most systems, it is now a 64-bit signed integer type. Standard C does not mandate an integer type; POSIX does. Using `atoi()` is not sensible; using `strtoll()` should be safe enough. – Jonathan Leffler Oct 26 '22 at 13:23
  • It's a good point (I only used `atoi` as it was in the example I found online) -- but the real question is is there a POSIX compliant way to convert from seconds since epoch to a time value? While casting long-long to time_t will work on most systems, it's not specified that this is how things work in the standard, and thus anything I implement using that assumption is not guaranteed to work (or not to break in the future...) – HardcoreHenry Oct 26 '22 at 13:33
  • 1
    Well, a `time_t` value is 'seconds since the epoch' — so your question is more "is there a way to convert a string representation of seconds since the epoch to a `time_t` value". And the answer to that is "No — there is no function which explicitly takes a string representation of the number of seconds since the epoch and returns a `time_t` value". – Jonathan Leffler Oct 26 '22 at 13:58
  • I'm concerned over the words `Although not defined by the C standard, this is almost always an integral value holding the number of seconds`, which means if I write code that assumes that it holds seconds since epoch, that assumption is not guaranteed, and down the road the code I'm writing may break. – HardcoreHenry Oct 26 '22 at 14:12
  • If you are concerned that your conversion from a string representation of the number of seconds since the epoch to a `time_t` may break in future, you should also check how the software that produces this string representation is implemented. – Bodo Oct 26 '22 at 14:33
  • https://man7.org/linux/man-pages/man1/date.1.html in section EXAMPLES is reported a way to convert a string representation of seconds since epoch to the corresponding date. How could this be done without first converting seconds to time_t ? – ulix Oct 26 '22 at 15:51
  • @ulix The UNIX/Linux `date` command most probably relies on POSIX interfaces. So it can safely assume that `time_t` is an integer type and does not need to care about possible other implementations where `time_t` might be something else. – Bodo Oct 26 '22 at 16:48
  • see also [Why shouldn't I use atoi()?](https://stackoverflow.com/q/17710018/995714). Use `strtol` instead – phuclv Oct 28 '22 at 16:04

2 Answers2

2

Converting seconds since epoch to time_t
I have a string representing seconds since epoch, and I need to convert it to a human readable string.

Add the seconds offset to a .tm_sec member of an epoch struct tm and form the time_t.


  1. Convert the string to an wide integer. (Here I assume the string represents a whole number of seconds.) It may be wider than long. Error handling omitted for brevity.
    long long offset = strtoll(string, NULL, 10);
    // or pedantically
    intmax_t offset = strtoimax(string, NULL, 10);
  1. Consider cases where time_t is not certainly a count of seconds (e.g. maybe nanoseconds) and maybe the epoch is not Jan 1, 1970 - it is more interesting. Form a timestamp for the epoch.
    time_t epoch = 0;
    struct tm *tm_ptr = localtime(&epoch);
    if (tm_ptr == NULL) Handle_Error();
    struct tm tm = *tm_ptr;
  1. Add to the struct tm members. Perhaps segmented the offset addition into parts to avoid int overflow. More code needed when int is 16-bit and offset is large (e. g. 88+ years).
    tm.tm_sec += offset%60;
    offset /= 60;
    tm.tm_min += offset%60;
    offset /= 60;
    tm.tm_hour += offset%24;
    offset /= 24;
    tm.tm_mday += offset;
  1. Call mktime() to bring the timestamp into the primary range and print. Note than since time_t might be some integer type or some floating point type, covert to a FP type to print.
    time_t target = mktime(&tm);
    printf("%Lg\n", (long double) target);
    if (target == -1) Handle_Error();
    printf("%s", asctime(&tm));
chux - Reinstate Monica
  • 143,097
  • 13
  • 135
  • 256
1

So, this is not guaranteed to work. I'm wondering if there's a proper way of doing this?

Hm.... I don't exactly know what you mean with the above conclussion. time_t is a unix specific type, that has evolved into POSIX standard. If you receive it as a string, then you must deal with the possibility of it being a 64bit number (E.g. FreeBSD uses a 64bit time_t)

About the epoch time, it wasn't always that epoch (first versions of unix used 01.01.1972, but I think none of us has seen one of such systems ---I run a unix v7 for pdp-11 at home, and it has a 32bit time ---no time_t type at that time, it's a long int--- and it is based on actual epoch time)

You must be careful, as since its conception, unix time has been a signed value, and so it starts at 1970-01-01, but you can specify dates before that, just by using negative values, as in:

$ LANG=C date -u -r -2147483648
Fri Dec 13 20:45:52 UTC 1901
$ _

This is why it will run out in

$ LANG=C date -u -r 2147483647
Tue Jan 19 03:14:07 UTC 2038
$ _

On other side, your problem is ambiguous, because you don't state the final string data in which locale and timezone (or format) has to be converted to. The best is that you use the standard unix time functions (look for gmtime(3), localtime(3) and the struct tm associated type)

A common way to do it is:

    time_t now = time(NULL);
    struct tm *now_gmt = gmtime(&now);

and then, if you are not interested in a specific format for the time, to use:

    printf("%s", asctime(now_gmt));

Or you can operate on the struct tm fields (tm_year, tm_mon, tm_mday, tm_hour, tm_min, tm_sec, tm_wday, tm_yday or tm_isdst ---this last to know if you are in winter or summer time)

So everything is solved in POSIX.

And last, if you have a huge number (for a 64bit value) to convert into time_t, the best to do is to convert it with:

#include <stdint.h>

...

    int64_t val;

    sscanf(the_string_value, "%lld", &val); /* use a 64bit type */
    time_t my_time_t = (time_t) val; /* then cast it to time_t */

(I used a cast because it can be that time_t be smaller than int64_t, and this avoids a warning from the compiler)

that will work for 64bit and 32bit integers. Once you have converted it into a time_t, all the above things remain valid.

Luis Colorado
  • 10,974
  • 1
  • 16
  • 31