getting utc timestamp using strftime()

9.1k Views Asked by At

I am trying to encode current UTC time into string using the strftime function:

time_t now;
struct tm nowLocal;
struct tm nowUtc;

now = time(NULL);
localtime_r(&now, &nowLocal);
gmtime_r(&now, &nowUtc);

So far so good: nowLocal contains current time in my timezone (CET), nowUtc contains UTC time, the difference is exactly according to the tm_gmtoff value:

nowLocal: {tm_sec = 28, tm_min = 27, tm_hour = 13, tm_mday = 23,
   tm_mon = 4, tm_year = 112, tm_wday = 3, tm_yday = 143,
   tm_isdst = 1, tm_gmtoff = 7200, tm_zone = 0x8127a38 "CEST"}

nowUtc:   {tm_sec = 28, tm_min = 27, tm_hour = 11, tm_mday = 23,
   tm_mon = 4, tm_year = 112, tm_wday = 3, tm_yday = 143,
   tm_isdst = 0, tm_gmtoff = 0, tm_zone = 0x3e9907 "GMT"}

Then I call strftime() with "%s" format to get the seconds since epoch:

char tsFromLocal[32];
char tsFromUtc[32];

strftime(tsFromLocal, sizeof(tsFromLocal), "%s", &nowLocal);
strftime(tsFromUtc, sizeof(tsFromUtc), "%s", &nowUtc);

The result seems strange to me. I expected to get exactly the same string from both strftime() calls as %s format is described as:

The number of seconds since the epoch, i.e., since 1970-01-01 00:00:00 UTC. Leap seconds are not counted unless leap second support is available.

But I got two different values:

tsFromLocal: "1337772448"

tsFromUtc:   "1337768848"

and moreover the difference is not 7200 (tm_gmtoff) but 3600. Can anyone explain such behavior? Or is it a bug?

The reason why I am doing this is that I need to transfer the time value over the network and compare it to the current time on the target machine that can be in a different time zone. On target machine, I wanted to:

struct tm restoredUtc;
time_t restored;

strptime(tsFromUtc, "%s", &restoredUtc);
restored = timegm(&restoredUtc);

But I got:

restoredUtc:{tm_sec = 28, tm_min = 27, tm_hour = 12, tm_mday = 23,
   tm_mon = 4, tm_year = 112, tm_wday = 3, tm_yday = 143,
   tm_isdst = 1, tm_gmtoff = 7200, tm_zone = 0x8127a38 "CEST"}

So strptime() sets the tm_zone according to the current time zone anyway. But even if I would use timelocal() instead of timegm() I will not get the correct value as it should be 11:27:28 CEST and not 12:27:28 CEST. Is this error related to different results of strftime()?

Any comment to this later part?

4

There are 4 best solutions below

0
On

Q1: Can anyone explain such behavior? Or is it a bug?

Yes, it is a bug in that tsFromLocal:"1337772448" != tsFromUtc: "1337768848". But the bug is in the tsFromUtc. They should be the same and they both should be 1337772448.

1337772448%(246060) --> 41248 is the UTC seconds of the day or 11:27:28 which matches your nowUtc structure and is the time you think it is (in UTC)

Q2: ... Is this error related to different results of strftime()?

I do agree with the general idea of using time_t as an integer for network communication.

Yes, it appears so.

If you want to go further on this post: Consider posting the numeric value of now right after time(&now) and clearly stated what time you expected it was then.

1
On

You're probably best off just using GMT throughout given there's gmtime_r that'll always give you the same answer everywhere. If an individual machine wants to display in local time, that can be done later, but sticking to one timezone for storage and network transfer is a good idea, and GMT values are easy to get to.

0
On

As others mentioned, the functions you are trying to use give you results that depend on the current settings of each computer.

However, Linux systems are very likely using UTC for their clock and convert that to local time as required to display the date. So the best bet is to share the UTC value between your computers to make sure they have the same time. If not, use NTP or alike to correct the time on each computer (most VPS use the time of their host which will already have an NTP server running so you rarely have to do so on such machines).

Now, if you want to know the timezone of each computer, I would suggest you share that information as a separate variable. This means you need to retrieve said timezone information. There are several ways to do so.

Determining an offset: the C functions that transform a time_t in a struct tm can be used to define an offset. If the struct tm does not already include an offset, you can use the localtime() and gmtime() functions and then compute the offset from the results (not exactly trivial, but not that hard) or better yet, use the gettimeofday() and it gives you a struct timezone with the offset in minutes and whether a DST (Day Standard Time) applies (i.e. summer vs. winter time).

Determining a name: it is always possible to determine the name of timezone in use; what may be difficult is finding all the functions to call to get that information.

Newer version of C++ (C++20 and above) have a ready made function for the purpose: std::chrono::current_zone()->name(), which returns a string such as "America/Los_Angeles". A name found under the /usr/share/zoneinfo directory.

In C, you could use the strftime() with the "%z" or "%Z" format. This gives you the offset (+hhmm or -hhmm) or the name ("PST" or "America/Los_Angeles") respectively. There isn't much control on which name will be returned, though.

There are also libraries that implement such: C++, boost, Unicode... all will make use of lower level functions and you can always download their code and look for the information on how they do it to find out how to implement it yourself if you don't want to use those libraries.

On Linux with systemd, you can also run the following command:

timedatectl show --va -p Timezone

So in C you could use a popen() and read the output of that command.

See also: How do I find the current system timezone?

0
On

I suspect the comment is correct: strftime() is interpreting the time as a local time. Note that tm_gmtoff is not a standardized field; I wonder if strftime() is even looking at it. But I can't find anything specific to confirm this.

However, answering the second part of your question, why not just transfer the results of time(NULL) over the network directly? Or, if you already have the time in the form of a struct tm, use mktime() to convert to time_t and then transfer that? printf("%lu", (unsigned long) time) is a lot simpler than trying to use strftime("%s"), which is not standardized by C99 or POSIX.