Convert localtime in seconds since epoch to UTC - c

In my system I have a PC (Linux, in case it matters) which keeps RTC time in UTC, making my localtime timezone specific. In PC code, I get UTC time as seconds since epoch using
struct timespec tv;
clock_gettime(CLOCK_REALTIME, &tv);
double time = (tv.tv_nsec / 1000000000.0) + tv.tv_sec;
return time;
I also have a 3rd party network device which provides its time also as seconds from epoch, but it does so using localtime instead of UTC time. This is a problem because, when I print the two timestamps in an interleaved log with timestamps from PC and this device, even though the two clocks show the same localtime, the timestamps are off.
Let's assume that the timezone settings (UTC offset and daylight savings specifications) are the same between the PC and this device. How would I take the seconds since epoch provided by the device (in localtime) and convert it to seconds since epoch in UTC? In other words, what the programmatic (in C) way to apply PC timezone settings to a seconds since epoch when that number is in localtime?
Here is my attempt at converting the 3rd party device localtime based seconds since epoch to UTC based seconds since epoch.
#include <stdio.h>
#include <time.h>
int main(void)
{
// The following epoch timestamps were converted to human time via https://www.epochconverter.com/
time_t device_rawtime = 1568133906.065000; // if treated as GMT: Tuesday, September 10, 2019 4:45:06.065 PM
time_t pc_rawtime = 1568151907.454432; // if treated as localtime: Tuesday, September 10, 2019 4:45:07.454 PM GMT-05:00 DST
struct tm ts;
char buf[80];
ts = *gmtime(&device_rawtime);
strftime(buf, sizeof(buf), "%a %Y-%m-%d %H:%M:%S %Z", &ts);
time_t converted = mktime(&ts);
printf("Device rawtime=%ld which is PC localtime %s ==> UTC based rawtime=%ld (pc was %ld)\n", device_rawtime, buf, converted, pc_rawtime);
return 0;
}
The above does not work. It prints
Device rawtime=1568133906 which is PC localtime Tue 2019-09-10 16:45:06 GMT ==> UTC based rawtime=1568155506 (pc was 1568151907)
As you can see, the converted device timestamp does not equal PC timestamp. How should this be done?

I agree that it's likely due to daylight savings. But the question is how should I be accounting for that?
The relevant information is found in man mktime:
The value specified in the tm_isdst field informs mktime() whether
or not daylight saving time (DST) is in effect for the time supplied in the tm structure: a positive value means DST is in effect; zero means that DST
is not in effect; and a negative value means that mktime() should (use timezone information and system databases to) attempt to determine whether DST is
in effect at the specified time.
On return from gmtime(&device_rawtime), ts.tm_isdst is set to zero, since UTC taken by gmtime() is never daylight saving. So, when mktime(&ts) is called, it converts the time structure with the information that DST is not in effect, thus we get a converted time value which is 3600 seconds too high. To correctly account for DST, setting
ts.tm_isdst = -1
before calling mktime(&ts) is sufficient.

Related

How to compare GMT time and local time in C?

my server uses local time of Prague (+ 2 hours) and visitor's request uses GMT time.
In code I want to compare these times but for that I need to convert them to the same time zone. How to do it? When I try to use gmtime() and localtime() they returns same result.
struct tm time;
struct stat data;
time_t userTime, serverTime;
// this time will send me user in GMT
strptime("Thu, 15 Apr 2021 17:20:21 GMT", "%a, %d %b %Y %X GMT", &time)
userTime = mktime(&time); // in GMT
// this time I will find in my server in another time zone
stat("test.txt", &data);
serverTime = data.st_mtimespec.tv_sec; // +2 hours (Prague)
// it's not possible to compare them (2 diferrent time zones)
if(serverTime < userTime) {
// to do
}
Thank you for answer.
On linux with glibc you can just use %Z with strptime to read GMT.
#define _XOPEN_SOURCE
#define _DEFAULT_SOURCE
#include <time.h>
#include <assert.h>
#include <string.h>
#include <sys/stat.h>
#include <stdio.h>
int main() {
// this time will send me user in GMT
struct tm tm;
char *buf = "Thu, 15 Apr 2021 17:20:21 GMT";
char *r = strptime(buf, "%a, %d %b %Y %X %Z", &tm);
assert(r == buf + strlen(buf));
time_t userTime = timegm(&tm);
// this time represents time that has passed since epochzone
struct stat data;
stat("test.txt", &data);
// be portable, you need only seconds
// see https://pubs.opengroup.org/onlinepubs/007904875/basedefs/sys/stat.h.html
time_t serverTime = data.st_mtime;
// it's surely is possible to compare them
if (serverTime < userTime) {
// ok
}
}
// it's not possible to compare them (2 diferrent time zones)
But it is!
The time that has passed since an event can't be in a timezone. Count of seconds since epoch is how many seconds have passed since that event, it's relative time that has passed, it's distance in time. No matter in which timezone you are, no matter if daylight saving time or not, the time that has passed since an event is the same in every location (well, excluding relativistic effects, which we don't care about). Timezone is irrelevant. mktime returns the number of seconds since epoch. stat returns timespec which represents time that have passed since epoch. Timezone has nothing to do here. Once you represent time as relative to some event (ie. since epoch), then just compare them.

Getting UTC time as time_t

I am trying to get UTC time as time_t.
Code below seems giving it correct but surprisingly prints local time only:
time_t mytime;
struct tm * ptm;
time ( &mytime ); // Get local time in time_t
ptm = gmtime ( &mytime ); // Find out UTC time
time_t utctime = mktime(ptm); // Get UTC time as time_t
printf("\nLocal time %X (%s) and UTC Time %X (%s)", mytime, ctime(&mytime), utctime, ctime(&utctime));
As we can see values of mytime and utctime we get are different.
However, when passed as parameters to ctime it converts them to same string.
Local time 55D0CB59 (Sun Aug 16 23:11:45 2015) and UTC Time 55D07E01
(Sun Aug 16 23:11:45 2015)
By definition the C time() function returns a time_t epoch time in UTC. So the code commented "//get the local time in time_t" is really getting UTC already, and the comment is incorrect.
From the Linux man-page:
time_t time(time_t *tloc);
time() returns the time as the number of seconds since the Epoch, 1970-01-01 00:00:00 +0000 (UTC).
Giving the code:
#include <time.h>
...
time_t utc_now = time( NULL );
The ctime result is a static variable. Do not use ctime twice in the same print statement. Do it in two separate print statements.
If ctime is replaced with asctime, the same problem will arise as asctime also returns the result as a static variable.
That's exactly what the documented behavior is supposed to do:
Interprets the value pointed by timer as a calendar time and converts it to a C-string containing a human-readable version of the corresponding time and date, in terms of local time.
You probably want to use asctime instead.
ctime function returns a C string containing the date and time information in a human-readable format.
To get time in UTC you can use gettimeofday() (for Linux)-
struct timeval ptm;
gettimeofday(&ptm,NULL);
long int ms = ptm.tv_sec * 1000 + ptm.tv_usec / 1000;
And you can see function GetSystemTime in for windows.

Get the time zone GMT offset in C

I'm using the standard mktime function to turn a struct tm into an epoch time value. The tm fields are populated locally, and I need to get the epoch time as GMT. tm has a gmtoff field to allow you to set the local GMT offset in seconds for just this purpose.
But I can't figure out how to get that information. Surely there must be a standard function somewhere that will return the offset? How does localtime do it?
Just do the following:
#define _GNU_SOURCE /* for tm_gmtoff and tm_zone */
#include <stdio.h>
#include <time.h>
/* Checking errors returned by system calls was omitted for the sake of readability. */
int main(void)
{
time_t t = time(NULL);
struct tm lt = {0};
localtime_r(&t, &lt);
printf("Offset to GMT is %lds.\n", lt.tm_gmtoff);
printf("The time zone is '%s'.\n", lt.tm_zone);
return 0;
}
Note: The seconds since epoch returned by time() are measured as if in Greenwich.
How does localtime do it?
According to localtime man page
The localtime() function acts as if it called tzset(3) and sets the
external variables tzname with information about the current timezone,
timezone with the difference between Coordinated Universal
Time (UTC) and local standard time in seconds
So you could either call localtime() and you will have the difference in timezone or call tzset():
extern long timezone;
....
tzset();
printf("%ld\n", timezone);
Note: if you choose to go with localtime_r() note that it is not required to set those variables you will need to call tzset() first to set timezone:
According to POSIX.1-2004, localtime() is required to behave as though
tzset() was called, while localtime_r() does not have this
requirement. For portable code tzset() should be called before
localtime_r()
The universal version of obtaining local time offset function is here.
I borrowed pieces of code from this answer in stackoverflow.
int time_offset()
{
time_t gmt, rawtime = time(NULL);
struct tm *ptm;
#if !defined(WIN32)
struct tm gbuf;
ptm = gmtime_r(&rawtime, &gbuf);
#else
ptm = gmtime(&rawtime);
#endif
// Request that mktime() looksup dst in timezone database
ptm->tm_isdst = -1;
gmt = mktime(ptm);
return (int)difftime(rawtime, gmt);
}
I guess I should have done a bit more searching before asking. It turns out there's a little known timegm function which does the opposite of gmtime. It's supported on GNU and BSD which is good enough for my purposes. A more portable solution is to temporarily set the value of the TZ environment variable to "UTC" and then use mktime, then set TZ back.
But timegm works for me.
This is the portable solution that should work on all standard C (and C++) platforms:
const std::time_t epoch_plus_11h = 60 * 60 * 11;
const int local_time = localtime(&epoch_plus_11h)->tm_hour;
const int gm_time = gmtime(&epoch_plus_11h)->tm_hour;
const int tz_diff = local_time - gm_time;
Add std:: namespace when using C++. The result is in hours in the range [-11, 12];
Explanation:
We just convert the date-time "1970-01-01 11:00:00" to tm structure twice - with the local timezone and with the GMT. The result is the difference between hours part.
The "11:00::00" has been chosen because this is the only time point (considering GMT) when we have the same date in the whole globe. Because of that fact, we don't have to consider the additional magic with date changing in the calculations.
WARNING
Previous version of my answer worked only on linux:
// DO NOT DO THAT!!
int timezonez_diff = localtime(&epoch_plus_11h)->tm_hour -
gmtime(&epoch_plus_11h)->tm_hour;
This may not work because the storage for result tm object returned as a pointer from localtime and gmtime may be shared (and it is on windows/msvc). That's whe I've introduced temporaries for calculation.
I believe the following is true in linux at least: timezone info comes from /usr/share/zoneinfo/. localtime reads /etc/localtime which should be a copy of the appropriate file from zoneinfo. You can see whats inside by doing zdump -v on the timezone file (zdump may be in sbin but you don't need elevated permissions to read timezone files with it). Here is a snipped of one:
/usr/share/zoneinfo/EST5EDT Sun Nov 6 05:59:59 2033 UTC = Sun Nov 6 01:59:59 2033 EDT isdst=1 gmtoff=-14400
/usr/share/zoneinfo/EST5EDT Sun Nov 6 06:00:00 2033 UTC = Sun Nov 6 01:00:00 2033 EST isdst=0 gmtoff=-18000
/usr/share/zoneinfo/EST5EDT Sun Mar 12 06:59:59 2034 UTC = Sun Mar 12 01:59:59 2034 EST isdst=0 gmtoff=-18000
/usr/share/zoneinfo/EST5EDT Sun Mar 12 07:00:00 2034 UTC = Sun Mar 12 03:00:00 2034 EDT isdst=1 gmtoff=-14400
/usr/share/zoneinfo/EST5EDT Sun Nov 5 05:59:59 2034 UTC = Sun Nov 5 01:59:59 2034 EDT
I guess you could parse this yourself if you want. I'm not sure if there is a stdlib function that just returns the gmtoff (there may well be but I don't know...)
edit: man tzfile describes the format of the zoneinfo file. You should be able to simply mmap into a structure of the appropriate type. It appears to be what zdump is doing based on an strace of it.
Here's a two-liner inspired by #Hill's and #friedo's answers:
#include <time.h>
...
time_t rawtime = time(0);
timeofs = timegm(localtime(&rawtime)) - rawtime;
Returns offset from UTC in seconds.
Doesn't need _GNU_SOURCE defined, but note that timegm is not a POSIX standard and may not be available outside of GNU and BSD.
Ended up with this. Sure tm_secs is redundant, just for a sake of consistency.
int timezone_offset() {
time_t zero = 0;
const tm* lt = localtime( &zero );
int unaligned = lt->tm_sec + ( lt->tm_min + ( lt->tm_hour * 60 ) ) * 60;
return lt->tm_mon ? unaligned - 24*60*60 : unaligned;
}
Here is my way:
time_t z = 0;
struct tm * pdt = gmtime(&z);
time_t tzlag = mktime(pdt);
Alternative with automatic, local storage of struct tm:
struct tm dt;
memset(&dt, 0, sizeof(struct tm));
dt.tm_mday=1; dt.tm_year=70;
time_t tzlag = mktime(&dt);
tzlag, in seconds, will be the negative of the UTC offset; lag of your timezone Standard Time compared to UTC:
LocalST + tzlag = UTC
If you want to also account for "Daylight savings", subtract tm_isdst from tzlag, where tm_isdst is for a particular local time struct tm, after applying mktime to it (or after obtaining it with localtime ).
Why it works:
The set struct tm is for "epoch" moment, Jan 1 1970, which corresponds to a time_t of 0.
Calling mktime() on that date converts it to time_t as if it were UTC (thus getting 0), then subtracts the UTC offset from it in order to produce the output time_t. Thus it produces negative of UTC_offset.
Here is one threadsafe way taken from my answer to this post:
What is the correct way to get beginning of the day in UTC / GMT?
::time_t GetTimeZoneOffset ()
{ // This method is to be called only once per execution
static const seconds = 0; // any arbitrary value works!
::tm tmGMT = {}, tmLocal = {};
::gmtime_r(&seconds, &tmGMT); // ::gmtime_s() for WINDOWS
::localtime_r(&seconds, &tmLocal); // ::localtime_s() for WINDOWS
return ::mktime(&tmGMT) - ::mktime(&tmLocal);
};

time(NULL) returning different time

I am trying to get current time in C using time_t current_time = time(NULL).
As I understand, it would return me the current time of system.
I am later trying to convert it into GMT time using struct tm* gmt = gmtime(&current_time).
I print both times using ctime() and asctime() functions.
The current time on my system is GMT + 1. But gmtime() returns me the same time as current_time is. I could not understand why gmtime() is returning me same time. Any help will be appreciated.
Ok here is the code and the output: Current time that windows is showing is 17:54 (Stockholm zone; GMT+1). I want something to return me 15:54. Or perhaps my understanding is wrong ...
time_t current_time = time(NULL);
struct tm* gmt = gmtime(&current_time);
struct tm* loc = localtime(&current_time);
printf("current time: %s\n", ctime(&current_time));
printf("gmt time %s\n", asctime(gmt));
printf("local time %s\n", asctime(loc));
Output:
current time: Mon Oct 8 17:54:06 2012
gmt time Mon Oct 8 17:54:06 2012
local time Mon Oct 8 17:54:06 2012
Accepted Solution: From Simes
That's probably your problem. Check the value of your TZ environment variable; if not present, it will default to GMT. Cygwin doesn't automatically pick up the time zone setting from Windows. See also localtime returns GMT for windows programs running on cygwin shells
A time_t type holds a value representing the number of seconds since the UNIX epoch.
A tm type holds a calendar value.
gmtime() just converts system time (which is always UTC) from time_t to tm. That's why the values are the same. If you want a representation of your local time (GMT+1), that's what localtime() is for.
time() returns the number of seconds since epoch. Which is equal to UTC (aka GMT)
Epoch was 1.1.1970, 00:00:00 in Greenwich, UK.
So in fact time() does not return a time, but a time difference.
Run the debugger over these two lines:
struct tm* gmt = gmtime(&current_time);
struct tm* loc = localtime(&current_time);
Break on the second line, watch members of gmt and when you execute the second line - you will see that some gmt members change value. Apparently some static memory is used by the library. So save the results of the first statement before running the second

UTC time from a timestamp

I'm trying to get the UTC time from a localtime. Vice versa it works OK. I only have problems when I want to convert a localtime to a UTC one in order to update an RTC clock. This is what I'm doing:
Say that t is a local timestamp.
char *tz = "GMT-2GMT,M3.5.0/3,M10.5.0/4";
t = 1311444000; // 23/07/11 18:00:00
set_TZ(tz);
gmt_time = gmtime(&t);
mktime(gmt_time);
printf("GMT Time: %s\r\n",asctime (gmt_time));
This gives me 18:00:00, when it should be minus the timezone.
How do I fix this problem?
The problem here is that time_t is supposed to represent the number of seconds (simplification, see footnote) since the epoch. This is not affected by time zones. If you add a time zone offset to a time_t, none of the functions will work as expected. Part of the problem is that it is sometimes impossible to know how to correctly convert such a value to a POSIX timestamp -- when the clocks roll back in the fall, where I live the clocks will read 1:30 AM twice during the same day, and without more information you can't figure out what the UTC time is.
You'll need to convert to local calendar time before you can convert to a POSIX timestamp, at which point you can convert to UTC calendar time.
// NOTE: not reentrant
time_t local_to_posix(time_t t) {
struct tm *tm;
time_t tt;
tm = gmtime(&t); // No timezone compensation
tm->tm_isdst = -1; // Let mktime figure out daylight savings
// NOTE: this WILL be wrong for one hour each year
tt = mktime(tm); // Converts local time to POSIX timestamp
return tt;
}
You can then pass the result to gmtime, which will give you the UTC calendar time.
Footnote: Strictly speaking, these timestamps are not UTC and do not measure the number of seconds since epoch. They are POSIX timestamps, which count seconds since epoch as if there were no leap seconds. This is probably irrelevant unless your realtime clock is an atomic clock.
After further tests the following code this seems to work. Im in UTC+2 and currently in DST. The returned time for UTC is correct. I will to further test without DST and see what is returned.
struct tm tm;
struct tm *local_time;
char *tz = "GMT-2GMT,M3.5.0/3,M10.5.0/4";
time_t t;
tm.tm_hour = 18;
tm.tm_min = 0;
tm.tm_sec = 0;
tm.tm_wday = 0;
tm.tm_yday = 0;
tm.tm_year = (2011) - 1900;
tm.tm_mday = 22;
tm.tm_mon = 7 - 1;
tm.tm_isdst = -1;
set_TZ(tz);
t = mktime(&tm);
local_time = localtime(&t);
printf("Local Time: %s\r\n",asctime (local_time ));
local_time = gmtime(&t);
printf("UTC Time: %s\r\n",asctime (local_time ));
Output:
Local Time: Fri Jul 22 18:00:00 2011
UTC Time: Fri Jul 22 15:00:00 2011

Resources