mktime() for non-local timezone - c

In C the function mktime() returns the epoch time according to the local timezone (the input struct is locally formatted).
The function timegm() returns the epoch time according to the UTC time (the input struct is formatted based off of UTC time).
The function localtime_r () takes in an epoch time and returns a local timezone formatted struct.
The function gmtime_r () takes in an epoch time and returns a UTC formatted struct.
I need to find out if a non-local timezone is currently daylight savings time or not, which would work with the localtime_r() function if it were local, but what if it were not local?
The gmtime_r() function always sets the tm_isdst field to zero, which won't work here.
Maybe there's some other function I am not aware of. Not sure.

If you (a) don't want to muck around with a global environment variable and (b) have the "NetBSD inspired" time functions available to you, there's an additional possibility: mktime_z() and localtime_rz(), which let you explicitly specify the zone you want to use. So you're not limited to your default local zone, or UTC.
Here's an example:
int main(int argc, char **argv)
{
timezone_t tzp = tzalloc(argv[1]);
if(tzp == NULL) return 1;
time_t now = time(NULL);
struct tm tm;
struct tm *tmp = localtime_rz(tzp, &now, &tm);
char tmpbuf[20];
strftime(tmpbuf, sizeof(tmpbuf), "%H:%M:%S", tmp);
printf("right now in zone %s is %s\n", argv[1], tmpbuf);
tm.tm_year = 1976 - 1900;
tm.tm_mon = 7 - 1;
tm.tm_mday = 4;
tm.tm_hour = 12;
tm.tm_min = tm.tm_sec = 0;
tm.tm_isdst = -1;
time_t t = mktime_z(tzp, &tm);
printf("in zone %s, %d-%02d-%02d %d:%02d was %ld\n", argv[1],
1900+tm.tm_year, tm.tm_mon+1, tm.tm_mday, tm.tm_hour, tm.tm_min, t);
}
When I invoke tzt America/New_York I see
right now in zone America/New_York is 11:58:23
in zone America/New_York, 1976-07-04 12:00 was 205344000
and when I invoke tzt America/Los_Angeles I see
right now in zone America/Los_Angeles is 08:58:49
in zone America/Los_Angeles, 1976-07-04 12:00 was 205354800
Now, with that said, two further comments, tied to my opening "if"s:
a. If you don't want to muck around with a global environment variable, I don't blame you one tiny bit. I positively hate mucking around with global variables (let alone environment variables) to affect how a function like mktime or localtime behaves. Unfortunately, however, this is the recommended way, in C, in this situation — see this question's other answers 1, 2 for details.
b. Chances are unfortunately quite good that you don't, in fact, "have the NetBSD inspired time functions available to you". They're nonstandard and not even very popular. I was able to compile the test program above only because I had a copy of the IANA tz database and its code handy, which includes those functions if you also define NETBSD_INSPIRED. (That's why I broke the rules and didn't show a complete example, with all #include lines, since mine were weird and idiosyncratic.)

I need to find out if a non-local timezone is currently daylight savings time or not
Get the current time as a time_t from time.
Set env var TZ to the target time zone using putenv.
Call tzset. (I don't know if this is required, but there's surely no harm in calling it.)
Use localtime to convert the time_t into a struct tm according to the (modified) local time zone.
Check the tm_isdst field of that struct tm.

Here's some code I wrote a decade ago that does what ikegami suggests in their answer:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>
#include <unistd.h>
static void time_convert(time_t t0, char const *tz_value)
{
char old_tz[64] = "-none-";
char *tz = getenv("TZ");
if (tz != 0)
strcpy(old_tz, tz);
setenv("TZ", tz_value, 1);
tzset();
char new_tz[64];
strcpy(new_tz, getenv("TZ"));
char buffer[64];
struct tm *lt = localtime(&t0);
strftime(buffer, sizeof(buffer), "%Y-%m-%d %H:%M:%S", lt);
if (strcmp(old_tz, "-none-") == 0)
unsetenv("TZ");
else
setenv("TZ", old_tz, 1);
tzset();
printf("%lld = %s (TZ=%s, DST = %d)\n",
(long long)t0, buffer, new_tz, lt->tm_isdst);
}
int main(void)
{
time_t t0 = time(0);
char *tz = getenv("TZ");
if (tz != 0)
time_convert(t0, tz);
time_convert(t0, "UTC0");
time_convert(t0, "IST-5:30");
time_convert(t0, "EST5");
time_convert(t0, "EST5EDT");
time_convert(t0, "PST8");
time_convert(t0, "PST8PDT");
}
The output I get is:
1650647033 = 2022-04-22 17:03:53 (TZ=UTC0, DST = 0)
1650647033 = 2022-04-22 22:33:53 (TZ=IST-5:30, DST = 0)
1650647033 = 2022-04-22 12:03:53 (TZ=EST5, DST = 0)
1650647033 = 2022-04-22 13:03:53 (TZ=EST5EDT, DST = 1)
1650647033 = 2022-04-22 09:03:53 (TZ=PST8, DST = 0)
1650647033 = 2022-04-22 10:03:53 (TZ=PST8PDT, DST = 1)
Note that some of the time zones are specified without a daylight saving time, and the code reports DST = 0 for those zones.
Be wary of changing the time zone like this in multi-threaded applications. And be cautious about resetting the environment in case you fork() and exec() other programs with an unexpected value for the TZ environment variable.
Note: I've modified the code to:
Use long long instead of long for printing the time_t value. One of the annoying things is that there is no standard format string for printing the time_t type as an integer (indeed, the C standard doesn't even guarantee that the type is an integer, but it actually is on most systems, and POSIX requires it to be an integer type — see <sys/types.h>). This change should allow 32-bit systems to work in 2038. That assumes that time_t is a 64-bit value even though it is a 32-bit system; if the system still uses a 32-bit time_t, it is terminally broken when time_t wraps around to -231 — but that shouldn't still be my problem then.
Print lt->tm_isdst which is the information wanted in the question.

Related

Conversion of UTC Date and Time to UNIX Time gives wrong (time zone different) value. Why?

I wrote a small function to convert Date & Time in UTC to UNIX time (Epoch time). The value that I get, however, is dependent on the time-zone I am in.
Here is the code
#include<stdio.h>
#include<time.h>
time_t GenerateUnixTimeStampFromDateAndTime(char *DateAndTime);
void main()
{
long int UnixTime=0;
char *CurrentTime="01/22/2019 06:30:00";
UnixTime = (long int)GenerateUnixTimeStampFromDateAndTime(CurrentTime);
printf("Current Unix Time= %ld\r\n", UnixTime);
}
time_t GenerateUnixTimeStampFromDateAndTime(char *DateAndTime)
{
struct tm ti={0};
if( sscanf(DateAndTime, "%d/%d/%d %d:%d:%d", &ti.tm_mon, &ti.tm_mday, &ti.tm_year, &ti.tm_hour, &ti.tm_min, &ti.tm_sec) != 6 )
return -1;
ti.tm_year = ti.tm_year - 1900;
ti.tm_mon = ti.tm_mon - 1;
return mktime(&ti);
}
The answer I get is 1548118800 which is 01/22/2019 01:00:00 ie -5:30 which is the time-zone I am in (India). If I change my PC timezone to UTC, then it gives correct value of 1548138600.
What changes do i need to make it time-zone independent?
As documented, mktime() takes the broken-down time components in local time.
First, save the users current timezone:
char *old_timezone, *temp;
temp = getenv("TZ");
if (temp) {
const size_t len = temp;
old_timezone = malloc(len + 1);
if (!old_timezone) {
fprintf(stderr, "Out of memory!\n");
exit(EXIT_FAILURE);
}
if (len > 0)
memcpy(old_timezone, temp, len);
old_timezone[len] = '\0';
} else
old_timezone = NULL;
If the user uses the system default, old_timezone will be NULL.
Next, set the current timezone (for this process) to UTC:
setenv("TZ", "UTC", 1);
tzset();
Note that if you want, you can use any timezone specifier instead of "UTC" above; see tzset() for details. The tzset() call is usually done internally by your C library, but doing it explicitly here helps us humans see that something timezone-specific just occurred.
At this point, mktime() will operate in UTC, and localtime() and gmtime() will return the same results.
Afterwards, restore the timezone by
if (old_timezone) {
setenv("TZ", old_timezone, 1);
free(old_timezone);
old_timezone = NULL;
} else
unsetenv("TZ");
tzset();
Note that this does not affect anything except the current process (and any child processes you might create via popen() or system() or fork() and exec()). Timezone, like locale, is a per-process property.
Running
unsetenv("TZ");
tzset();
will change the current timezone for this process to the system default timezone.
If your program operates explicitly in UTC, you can do just
/* This program works explicitly in the UTC timezone.
User/system timezone configuration is completely ignored. */
setenv("TZ", "UTC", 1");
tzset();
near the beginning of your main().

Difference between mktime and timelocal

What is the difference between these two functions? It was my understanding that those should be the same: http://www.gnu.org/software/libc/manual/html_node/Broken_002ddown-Time.html.
I wrote this code to test the conversion (the Qt part is only for comparison):
#include <QCoreApplication>
#include <QDateTime>
int main(int argc, char *argv[])
{
QCoreApplication a(argc, argv);
QDateTime datetime(QDate(2012, 3, 25), QTime(5, 15));
qDebug("Timestamp: %lld.", datetime.toMSecsSinceEpoch()/1000L);
time_t timestamp;
tm* timeinfo = localtime(&timestamp);
timeinfo->tm_hour = 5;
timeinfo->tm_mday = 25;
timeinfo->tm_min = 15;
timeinfo->tm_mon = 2;
timeinfo->tm_year = 112;
timeinfo->tm_sec = 0;
qDebug("Timestamp: %ld.", timelocal(timeinfo));
return 0;
}
and found out that the output is:
Timestamp: 1332645300.
Timestamp: 1332645300.
which is what I'd expect. Then I replaced timelocal with mktime and found out that this was the output:
Timestamp: 1332645300.
Timestamp: 1332648900.
It seems like an hour was added (consider that my current timezone is GMT+2:00 and my locale is set to Italy). Why? What is the difference between the two and why mktime adds 1 hour to the date I set?
EDIT: I tested again and it seems that on Mac OS X (and iOS) timelocal is returning the same hour placed in the timeinfo structure, while mktime is actually adding an hour both in the returned time_t value and in the structure tm.
On Linux Kubuntu instead, with both functions I get that an hour is added to both the tm structure and the returned value.
Anyone who can explain why?
The man of OpenBSD's timelocal states:
timelocal is a deprecated interface that is equivalent to calling
mktime() with a negative value for tm_isdst
A negative value for tm_isdst means that timelocal doesn't take daylight saving time (DST) into account. It seems that QDateTime has troubles with DST as well.
mktime on the other hand, handles DST and this might explain the 1-hour difference on OS X.
Since timelocal is deprecated, it might have newer imlementations that deal with DST properly.

GMT confusion with ctime functions [duplicate]

How do I do the above? There is mktime function but that treats the input as expressed in local time but how do i perform the conversion if my input tm variable happens to be in UTC.
Use timegm() instead of mktime()
for those on windows, the below function is available:
_mkgmtime
link for more info: https://learn.microsoft.com/en-us/cpp/c-runtime-library/reference/mkgmtime-mkgmtime32-mkgmtime64
Here is a solution I use (Can't recall where I found it) when it isn't a windows platform
time_t _mkgmtime(const struct tm *tm)
{
// Month-to-day offset for non-leap-years.
static const int month_day[12] =
{0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334};
// Most of the calculation is easy; leap years are the main difficulty.
int month = tm->tm_mon % 12;
int year = tm->tm_year + tm->tm_mon / 12;
if (month < 0) { // Negative values % 12 are still negative.
month += 12;
--year;
}
// This is the number of Februaries since 1900.
const int year_for_leap = (month > 1) ? year + 1 : year;
time_t rt = tm->tm_sec // Seconds
+ 60 * (tm->tm_min // Minute = 60 seconds
+ 60 * (tm->tm_hour // Hour = 60 minutes
+ 24 * (month_day[month] + tm->tm_mday - 1 // Day = 24 hours
+ 365 * (year - 70) // Year = 365 days
+ (year_for_leap - 69) / 4 // Every 4 years is leap...
- (year_for_leap - 1) / 100 // Except centuries...
+ (year_for_leap + 299) / 400))); // Except 400s.
return rt < 0 ? -1 : rt;
}
The answer of Loki Astari was a good start, timegm is one of the possible solutions. However, the man page of timegm gives a portable version of it, as timegm is not POSIX-compliant. Here it is:
#include <time.h>
#include <stdlib.h>
time_t
my_timegm(struct tm *tm)
{
time_t ret;
char *tz;
tz = getenv("TZ");
if (tz)
tz = strdup(tz);
setenv("TZ", "", 1);
tzset();
ret = mktime(tm);
if (tz) {
setenv("TZ", tz, 1);
free(tz);
} else
unsetenv("TZ");
tzset();
return ret;
}
timegm() works, but is not present on all systems.
Here's a version that only uses ANSI C. (EDIT: not strictly ANSI C! I'm doing math on time_t, assuming that the units are in seconds since the epoch. AFAIK, the standard does not define the units of time_t.) Note, it makes use of a hack, so-to-speak, to determine the machine's time zone and then adjusts the result from mktime accordingly.
/*
returns the utc timezone offset
(e.g. -8 hours for PST)
*/
int get_utc_offset() {
time_t zero = 24*60*60L;
struct tm * timeptr;
int gmtime_hours;
/* get the local time for Jan 2, 1900 00:00 UTC */
timeptr = localtime( &zero );
gmtime_hours = timeptr->tm_hour;
/* if the local time is the "day before" the UTC, subtract 24 hours
from the hours to get the UTC offset */
if( timeptr->tm_mday < 2 )
gmtime_hours -= 24;
return gmtime_hours;
}
/*
the utc analogue of mktime,
(much like timegm on some systems)
*/
time_t tm_to_time_t_utc( struct tm * timeptr ) {
/* gets the epoch time relative to the local time zone,
and then adds the appropriate number of seconds to make it UTC */
return mktime( timeptr ) + get_utc_offset() * 3600;
}
The following implementation of timegm(1) works swimmingly on Android, and probably works on other Unix variants as well:
time_t timegm( struct tm *tm ) {
time_t t = mktime( tm );
return t + localtime( &t )->tm_gmtoff;
}
POSIX page for tzset, describes global variable extern long timezone which contains the local timezone as an offset of seconds from UTC. This will be present on all POSIX compliant systems.
In order for timezone to contain the correct value, you will likely need to call tzset() during your program's initialization.
You can then just subtract timezone from the output of mktime to get the output in UTC.
#include <stdio.h>
#include <stdlib.h>
#include <time.h>
time_t utc_mktime(struct tm *t)
{
return (mktime(t) - timezone) - ((t->tm_isdst > 0) * 3600);
}
int main(int argc, char **argv)
{
struct tm t = { 0 };
tzset();
utc_mktime(&t);
}
Note: Technically tzset() and mktime() aren't guaranteed to be threadsafe.
If a thread accesses tzname, [XSI] [Option Start] daylight, or timezone [Option End] directly while another thread is in a call to tzset(), or to any function that is required or allowed to set timezone information as if by calling tzset(), the behavior is undefined.
...but the majority of implementations are. GNU C uses mutexes in tzset() to avoid concurrent modifications to the global variables it sets, and mktime() sees very wide use in threaded programs without synchronization. I suspect if one were to encounter side effects, it would be from using setenv() to alter the value of TZ as done in the answer from #liberforce.
I was troubled by the issue of mktime() as well. My solution is the following
time_t myTimegm(std::tm * utcTime)
{
static std::tm tmv0 = {0, 0, 0, 1, 0, 80, 0, 0, 0}; //1 Jan 1980
static time_t utcDiff = std::mktime(&tmv0) - 315532801;
return std::mktime(utcTime) - utcDiff;
}
The idea is to get the time difference by calling std::mktime() with a known time (in this case 1980/01/01) and subtract its timestamp (315532801). Hope it helps.
Here's my take, which is based exclusively on time_t/tm conversion functions, and the only presumption it makes about time_t is that it is linear:
Pretending against better knowledge the tm structure holds local time (non-DST if anyone asks; it doesn't matter, but must be consistent with step 3), convert it to time_t.
Convert the date back into a tm structure, but this time in UTC representation.
Pretending against better knowledge that tm structure to also hold local (non-DST if anyone asks, but more importantly consistent with step 1), and convert it to time_t once more.
From the two time_t results I can now compute the difference between local time (non-DST if anyone asks) and UTC in time_t units.
Adding that difference to the first time_t result gives me the proper time in UTC.
Note that computation of the difference can conceivably be done once, and then applied later to as many dates as desired; this might be a way to solve issues arising from the lack of thread-safety in gmtime.
(Edit: Then again, this might cause issues if the time zone is changed between the date used to compute the offset, and the date to be converted.)
tm tt;
// populate tt here
tt.tm_isdst = 0;
time_t tLoc = mktime(&tt);
tt = *gmtime(&tLoc);
tt.tm_isdst = 0;
time_t tRev = mktime(&tt);
time_t tDiff = tLoc - tRev;
time_t tUTC = tLoc + tDiff;
Caveat: If the system uses a TAI-based time_t (or anything else that does respect leap seconds), the resulting time may be off by 1 second if applied to a point in time close to a leap second insertion.
This is really a comment with code to address the answer by Leo Accend:
Try the following:
#include <time.h>
#include <stdio.h>
#include <stdlib.h>
/*
* A bit of a hack that lets you pull DST from your Linux box
*/
time_t timegm( struct tm *tm ) { // From Leo's post, above
time_t t = mktime( tm );
return t + localtime( &t )->tm_gmtoff;
}
main()
{
struct timespec tspec = {0};
struct tm tm_struct = {0};
if (gettimeofday(&tspec, NULL) == 0) // clock_gettime() is better but not always avail
{
tzset(); // Not guaranteed to be called during gmtime_r; acquire timezone info
if (gmtime_r(&(tspec.tv_sec), &tm_struct) == &tm_struct)
{
printf("time represented by original utc time_t: %s\n", asctime(&tm_struct));
// Go backwards from the tm_struct to a time, to pull DST offset.
time_t newtime = timegm (&tm_struct);
if (newtime != tspec.tv_sec) // DST offset detected
{
printf("time represented by new time_t: %s\n", asctime(&tm_struct));
double diff = difftime(newtime, tspec.tv_sec);
printf("DST offset is %g (%f hours)\n", diff, diff / 3600);
time_t intdiff = (time_t) diff;
printf("This amounts to %s\n", asctime(gmtime(&intdiff)));
}
}
}
exit(0);
}
For all timezones and at all times would be exceedingly difficult if not impossible. You would need an accurate record of all the various arbitrary timezone and daylight savings time (DST) decrees. Sometimes, it is not clear who the local authority is, never mind what was decreed and when. Most systems, for example, are off by one second for uptime (time system has been up) or boottime (timestamp system booted), if a leap second was spanned. A good test would be a date that was once in DST but now is not (or vis versa). (It was not too long ago in the US that it changed.)

How to convert from UTC to local time in C?

It's a simple question, but the solution appears to be far from simple. I would like to know how to convert from UTC to local time. I am looking for a solution in C that's standard and more or less guaranteed to work on any computer at any location.
I have read the following links carefully but I can't find a solution there:
Converting string containing localtime into UTC in C
Converting Between Local Times and GMT/UTC in C/C++
I have tried a number of variations, such as (datetime is a string with time and date in UTC):
strptime(datetime, "%A %B %d %Y %H %M %S", tp);
strftime(printtime, strlen(datetime), "%A %B %d %Y %H %M %S", tp);
Or
strptime(datetime, "%A %B %d %Y %H %M %S", tp);
lt=mktime(tp);
printtime=ctime(&lt);
No matter what I try printtime ends up being the same as UTC.
Edit 11-29-2013: based on the very helpful answer by "R" below I finally got around to create a working example. I found it to be working correct in the two timezones I tested it, CET and PST:
#include <time.h>
#include <stdio.h>
#include <stdlib.h>
long long diff_tm(struct tm *a, struct tm *b)
{
return a->tm_sec - b->tm_sec
+60LL*(a->tm_min - b->tm_min)
+3600LL*(a->tm_hour - b->tm_hour)
+86400LL*(a->tm_yday - b->tm_yday)
+(a->tm_year-70)*31536000LL
-(a->tm_year-69)/4*86400LL
+(a->tm_year-1)/100*86400LL
-(a->tm_year+299)/400*86400LL
-(b->tm_year-70)*31536000LL
+(b->tm_year-69)/4*86400LL
-(b->tm_year-1)/100*86400LL
+(b->tm_year+299)/400*86400LL;
}
int main()
{
time_t utc, local;
char buf[100];
const char datetime[]="2013 11 30 23 30 26 UTC"; /* hard coded date and time in UTC */
struct tm *tp=malloc(sizeof(struct tm));
if(tp==NULL)
exit(-1);
struct tm *localt=malloc(sizeof(struct tm));
if(localt==NULL)
exit(-1);
memset(tp, 0, sizeof(struct tm));
memset(localt, 0, sizeof(struct tm));
printf("UTC date and time to be converted in local time: %s\n", datetime);
/* put values of datetime into time structure *tp */
strptime(datetime, "%Y %m %d %H %M %S %z", tp);
/* get seconds since EPOCH for this time */
utc=mktime(tp);
printf("UTC date and time in seconds since EPOCH: %d\n", utc);
/* lets convert this UTC date and time to local date and time */
struct tm e0={ .tm_year = 70, .tm_mday = 1 }, e1, new;
/* get time_t EPOCH value for e0 (Jan. 1, 1970) */
time_t pseudo=mktime(&e0);
/* get gmtime for this value */
e1=*gmtime(&pseudo);
/* calculate local time in seconds since EPOCH */
e0.tm_sec += utc - diff_tm(&e1, &e0);
/* assign to local, this can all can be coded shorter but I attempted to increase clarity */
local=e0.tm_sec;
printf("local date and time in seconds since EPOCH: %d\n", local);
/* convert seconds since EPOCH for local time into localt time structure */
localt=localtime(&local);
/* get nicely formatted human readable time */
strftime(buf, sizeof buf, "%Y-%m-%d %H:%M:%S %Z", localt);
printf("local date and time: %s\n", buf);
}
It should compile without problems on most systems. I hard coded a time and date in UTC which then will be converted to the local time and date.
If you can assume POSIX (and thus the POSIX specification of time_t as seconds since the epoch), I would first use the POSIX formula to convert to seconds since the epoch:
tm_sec + tm_min*60 + tm_hour*3600 + tm_yday*86400 +
(tm_year-70)*31536000 + ((tm_year-69)/4)*86400 -
((tm_year-1)/100)*86400 + ((tm_year+299)/400)*86400
Next, use localtime((time_t []){0}) to get a struct tm representing the epoch in local time. Add the seconds since the epoch to the tm_sec field of this struct tm, then call mktime to canonicalize it.
Edit: Actually the only POSIX dependency is having a known epoch which (time_t)0 corresponds to. Perhaps you can find a way around that if you really need to... for instance using calls to both gmtime and localtime at time_t 0..
Edit 2: A sketch of how to do this:
#include <time.h>
#include <stdio.h>
long long diff_tm(struct tm *a, struct tm *b)
{
return a->tm_sec - b->tm_sec
+60LL*(a->tm_min - b->tm_min)
+3600LL*(a->tm_hour - b->tm_hour)
+86400LL*(a->tm_yday - b->tm_yday)
+(a->tm_year-70)*31536000LL
-(a->tm_year-69)/4*86400LL
+(a->tm_year-1)/100*86400LL
-(a->tm_year+299)/400*86400LL
-(b->tm_year-70)*31536000LL
+(b->tm_year-69)/4*86400LL
-(b->tm_year-1)/100*86400LL
+(b->tm_year+299)/400*86400LL;
}
int main(int argc, char **argv)
{
char buf[100];
struct tm e0 = { .tm_year = 70, .tm_mday = 1 }, e1, new;
time_t pseudo = mktime(&e0);
e1 = *gmtime(&pseudo);
e0.tm_sec += atoi(argv[1]) - diff_tm(&e1, &e0);
mktime(&e0);
strftime(buf, sizeof buf, "%c", &e0);
puts(buf);
}
Please don't mind the ugly output code. This program takes an argument in the form of "seconds relative to the POSIX epoch" and outputs the resulting time in local time. You can convert any UTC time to seconds since the epoch using the formula I cited above. Note that this code does not in any way depend on POSIX, but it does assume the offset returned by diff_tm combined with the seconds-since-the-epoch value does not overflow int. A fix for this would be to use a long long offset and a loop that keeps adding increments no larger than INT_MAX/2 (or smaller than INT_MIN/2) and calling mktime to renormalize until the offset reaches 0.
Ahm ... I might just be a beginner in C, but I got this working example:
#include <time.h>
#include <stdio.h>
int main(void)
{
time_t abs_ts,loc_ts,gmt_ts;
struct tm loc_time_info,gmt_time_info;
/*Absolute time stamp.*/
time(&abs_ts);
/*Now get once the local time for this time stamp,
**and once the GMT (UTC without summer time) time stamp.*/
localtime_r(&abs_ts,&loc_time_info);
gmtime_r(&abs_ts,&gmt_time_info);
/*Convert them back.*/
loc_ts=mktime(&loc_time_info);
gmt_ts=mktime(&gmt_time_info);
/*Unfortunately, GMT still has summer time. Get rid of it:*/
if(gmt_time_info.tm_isdst==1)
{gmt_ts-=3600;}
printf("Local timestamp: %lu\n"
"UTC timestamp: %lu\n"
"Difference in hours: %lu\n\n",
loc_ts,
gmt_ts,
(loc_ts-gmt_ts)/3600);
return 0;
}
Which produces this output:
Local timestamp: 1412554119
GMT timestamp: 1412546919
Difference in hours: 2
Now you have the difference between UTC and local time in seconds. That should be enough to convert it.
One note to your code, aseq: you are using malloc without need here (you can memset values on the stack as well, and malloc can be expensive while stack allocation is often much faster), and you do not free it. That's very, very bad practise.
Another thing:
memset(tp, 0, sizeof(struct tm));
Would be better done if you'd pass sizeof(*tp) (or, if you put tp on the stack, sizeof(tp)) to memset. That ensures that even if the type of your object changes, it will still be fully memset.
To sum-up: the conversion of a broken down date (struct tm) in UTC to a (local) calendar time (time_t) is achieved with timegm() - the opposite of mktime() - BUT timegm() is not a standard function (how logic is that).
The C standard leaves us with only time(), gmtime(), mktime() and difftime().
A workaround found in other docs advises to emulate timegm() by setting first the environment variable TZ to a null string, then calling mktime() resulting in an UTC calendar time, then resetting TZ to its initial value, but once again, this is not standard.
Basically, as I understand it, the difference between a local time and UTC time is just an offset so if we can evaluate that offset, we can adjust the result of mktime(), so here's my proposition:
time_t my_timegm(struct tm *tm) {
time_t epoch = 0;
time_t offset = mktime(gmtime(&epoch));
time_t utc = mktime(tm);
return difftime(utc, offset);
}
A quick test:
int main(void) {
time_t now = time(0);
struct tm local = *localtime(&now);
struct tm utc = *gmtime(&now);
time_t t1 = mktime(&local);
time_t t2 = my_timegm(&utc);
assert(t1 == t2);
printf("t =%lu\nt1=%lu\nt2=%lu\n",now,t1,t2);
return 0;
}
//working stand alone function adjusting UTC to local date and time
//globals(unsigned integers): gps.Mth, gps.Yr, gps.Hm (eg:2115 for 21:15)
//adjust date and time according to UTC
//tz(timezone) eg: 1100, for 11 hours, tzdir: 1 forward, 0 backwards
void AdjustUTCToTimeZone(u16 tz, u8 tzdir){
u8 maxDayInAnyMonth[13] = {0,31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31}; //gps.Mth 1-12 (not zero)
if(gps.Yr%4==0){maxDayInAnyMonth[2]=29;}//adjust for leapyear
u8 maxDayUtcMth =maxDayInAnyMonth[gps.Mth];
u8 maxDayPrevMth=maxDayInAnyMonth[gps.Mth-1];
if(!maxDayPrevMth){maxDayPrevMth=31;} //month before utc month
u16 hr=(gps.Hm/100)*100;u16 m=gps.Hm-hr; //2115 --> 2100 hr and 15 min
if(tzdir){//adjusting forwards
tz+=gps.Hm;
if(tz>2400){gps.Hm=tz-2400;gps.Day++; //spill over to next day
if(gps.Day>maxDayUtcMth){ gps.Day=1;gps.Mth++; //spill over to next month
if(gps.Mth>12){gps.Mth=1; gps.Yr++; //spill over to next year
}
}
}else{gps.Hm=tz;}
}else{//adjusting backwards
if(tz>gps.Hm){gps.Hm=(2400-(tz-hr))+m;gps.Day--; // back to previous day
if(gps.Day==0){ //back to previous month
gps.Mth--;gps.Day=maxDayPrevMth;
if(!gps.Mth){gps.Mth=12; //back to previous year
gps.Yr--;
}
}
}else{gps.Hm-=tz;}
}
}
I think it's easier than that; time.h defines three variables:
extern int daylight;
extern long timezone;
extern char *tzname[];
which are loaded based on the TZ env variable when you call
tzset();
if you have a utc time in
struct tm date;
date.tm_isdst = 0;
convert it to a time_t using mktime
time_t utc = mktime( &date );
then convert it to local time
time_t local = utc - timezone + ( daylight?3600:0 );
timezone is the number of seconds away from utc for the current timezone and daylight is 1 to indicate daylight savings time is in play and zero for not.
A small caution: When I coded this for a microcontroller and cross compiled, it's time.h defined those variables with initial underscores.
See the man page for time.h
I found that the solution the OP gave did not work in cases when DST applies. For example, in my case, at the current time, DST was not in effect, but if I set the initial date which should convert to local time with DST, then it would not work, i.e. today's date is 3/1/2018 and DST is not in effect, but if I set the date for conversion to, say, 8/1/2018 0:00:00 when DST is in effect, then the solution given would convert to local time, but would not take DST into account. I found that initializing e0 to the date and hour of the initial date/time string and its member tm_isdst to -1 solved the problem. I then created the following program with complementary functions which you can include in your code. The initial format of the date and time is the same that MySQL uses, because I needed it for such purposes.
#include <stdio.h>
#include <time.h>
#include <string.h>
long long diff_tm(struct tm *a, struct tm *b) {
return a->tm_sec - b->tm_sec
+ 60LL * (a->tm_min - b->tm_min)
+ 3600LL * (a->tm_hour - b->tm_hour)
+ 86400LL * (a->tm_yday - b->tm_yday)
+ (a->tm_year - 70) * 31536000LL
- (a->tm_year - 69) / 4 * 86400LL
+ (a->tm_year - 1) / 100 * 86400LL
- (a->tm_year + 299) / 400 * 86400LL
- (b->tm_year - 70) * 31536000LL
+ (b->tm_year - 69) / 4 * 86400LL
- (b->tm_year - 1) / 100 * 86400LL
+ (b->tm_year + 299) /400 * 86400LL;
}
void localToUTC(char *buf, const char *localTime) {
struct tm tp;
strptime(localTime, "%Y-%m-%d %H:%M:%S", &tp);
tp.tm_isdst = -1;
time_t utc = mktime(&tp);
struct tm res = *gmtime(&utc);
strftime(buf, 20, "%Y-%m-%d %H:%M:%S", &res);
}
void utcToLocal(char *buf, const char *utcTime) {
struct tm tp;
strptime(utcTime, "%Y-%m-%d %H:%M:%S", &tp);
tp.tm_isdst = -1;
time_t utc = mktime(&tp);
struct tm e0 = { .tm_year = tp.tm_year, .tm_mday = tp.tm_mday, .tm_mon = tp.tm_mon, .tm_hour = tp.tm_hour, .tm_isdst = -1 };
time_t pseudo = mktime(&e0);
struct tm e1 = *gmtime(&pseudo);
e0.tm_sec += utc - diff_tm(&e1, &e0);
time_t local = e0.tm_sec;
struct tm localt = *localtime(&local);
strftime(buf, 20, "%Y-%m-%d %H:%M:%S", &localt);
}
int main(void) {
char mytime_1[20] = "2018-02-28 13:00:00";
char utctime_1[20], back_1[20];
localToUTC(utctime_1, mytime_1);
utcToLocal(back_1, utctime_1);
printf("My time: %s\n", mytime_1);
printf("UTC time: %s\n", utctime_1);
printf("Back: %s\n", back_1);
printf("-------------------------------------------\n");
char mytime_2[20] = "2018-07-28 17:00:00";
char utctime_2[20], back_2[20];
localToUTC(utctime_2, mytime_2);
utcToLocal(back_2, utctime_2);
printf("My time: %s\n", mytime_2);
printf("UTC time: %s\n", utctime_2);
printf("Back: %s\n", back_2);
printf("-------------------------------------------\n");
return 0;
}
I followed the answer by #Dachschaden and I made an example which also shows human-readable output and I remove the DST option for the difference in seconds between UTC and local time. Here it is:
#include <time.h>
#include <stdio.h>
#define DATE_MAX_STR_SIZE 26
#define DATE_FMT "%FT%TZ%z"
int main() {
time_t now_time, now_time_local;
struct tm now_tm_utc, now_tm_local;
char str_utc[DATE_MAX_STR_SIZE];
char str_local[DATE_MAX_STR_SIZE];
time(&now_time);
gmtime_r(&now_time, &now_tm_utc);
localtime_r(&now_time, &now_tm_local);
/* human readable */
strftime(str_utc, DATE_MAX_STR_SIZE, DATE_FMT, &now_tm_utc);
strftime(str_local, DATE_MAX_STR_SIZE, DATE_FMT, &now_tm_local);
printf("\nUTC: %s", str_utc);
printf("\nLOCAL: %s\n", str_local);
/* seconds (epoch) */
/* let's forget about DST for time difference calculation */
now_tm_local.tm_isdst = 0;
now_tm_utc.tm_isdst = 0;
now_time_local = now_time + (mktime(&now_tm_local) - mktime(&now_tm_utc));
printf("\nUTC in seconds: %lu", now_time);
printf("\nLOCAL in seconds: %lu\n", now_time_local);
return 0;
}
Output on my machine is:
UTC: 2016-05-05T15:39:11Z-0500
LOCAL: 2016-05-05T11:39:11Z-0400
UTC in seconds: 1462462751
LOCAL in seconds: 1462448351
Note that DST is on in this case (there's a 1 hour time zone offset difference between UTC and LOCAL).
try this, test output:
utcEpochTime: 1487652688, localEpochTime: 1487699488, diff: 46800
$ python
>>>46800 / 60 / 60
13
the diff is 13 hours, which is good, as my timezone is UTC+8.
#include <stdio.h>
#include <time.h>
int main(int argc, char *argv[])
{
time_t utcEpochTime = time(0);
time_t localEpochTime = 0;
struct tm tm = {0};
localtime_r(&utcEpochTime, &tm);
tm.tm_isdst = -1;
localEpochTime = timegm(&tm);
printf("utcEpochTime: %d, localEpochTime: %d, diff: %d\n", (int)utcEpochTime, (int)localEpochTime, (int)(localEpochTime - utcEpochTime));
return 0;
}
A simple and effective way: Add (or subtract) the number of seconds between your time zone and UTC (considering daylight saving time).
As an example that worked just fine a minute ago, on December 30, 2017, with U.S. Mountain Standard Time (no DST), which is 7 hours behind UTC:
time_t current_time_UTC;
time_t current_time_MST;
struct tm *current_broken_time_MST;
uint32_t seven_hours_in_seconds = 25200; // Get this any way you want
current_time_UTC = time (NULL); // UTC
current_time_MST = current_time_UTC - seven_hours_in_seconds; // MST
current_broken_time_MST = localtime (&current_time_MST); // MST
Enjoy.
void CTestDlg::OnBtnTest()
{
HANDLE hFile;
WIN32_FIND_DATA wfd;
SYSTEMTIME systime;
FILETIME localtime;
char stime[32]; //
memset(&wfd, 0, sizeof(wfd));
if((hFile=FindFirstFile( "F:\\VC\\MFC\\Test\\Release\\Test.exe ", &wfd))==INVALID_HANDLE_VALUE)
{
char c[2];
DWORD dw=GetLastError();
wsprintf(c, "%d ", dw);
AfxMessageBox(c);
return ;//
}
FileTimeToLocalFileTime(&wfd.ftLastWriteTime,&localtime);
FileTimeToSystemTime(&localtime,&systime);
sprintf(stime, "%4d-%02d-%02d %02d:%02d:%02d ",
systime.wYear,systime.wMonth,systime.wDay,systime.wHour,
systime.wMinute,systime.wSecond);
AfxMessageBox(stime);
}

UTC to Time of the day in ANSI C?

how to convert the utc time to local time of the day?
You must use a mix of tzset() with time/gmtime/localtime/mktime functions.
Try this:
#include <stdio.h>
#include <stdlib.h>
#include <time.h>
time_t makelocal(struct tm *tm, char *zone)
{
time_t ret;
char *tz;
tz = getenv("TZ");
setenv("TZ", zone, 1);
tzset();
ret = mktime(tm);
if(tz)
setenv("TZ", tz, 1);
else
unsetenv("TZ");
tzset();
return ret;
}
int main(void)
{
time_t gmt_time;
time_t local_time;
struct tm *gmt_tm;
gmt_time = time(NULL);
gmt_tm = gmtime(&gmt_time);
local_time = makelocal(gmt_tm, "CET");
printf("gmt: %s", ctime(&gmt_time));
printf("cet: %s", ctime(&local_time));
return 0;
}
Basically, this program takes the current computer day as GMT (time(NULL)), and convert it to CET:
$ ./tolocal
gmt: Tue Feb 16 09:37:30 2010
cet: Tue Feb 16 08:37:30 2010
M. MARIE's answer does not in fact work for the question as posed: tzset() is POSIX, but not ANSI C as the title of the original question asked. There is no mention of it in either C90 or C99 (from searching the draft standards; I have no access to the final standards).
OP's question is perhaps a little vague as it is not clear what he means by "utc time", but presumably he means broken-down components, let's say filled into a struct tm.
It is possible in C99 to determine local TZ's offset from UTC by parsing the output of strftime("%z",...) (make sure that you call it with your own date values, as this offset will change over time); but this format-code is not available in C90, so AFAIK you're out of luck if you must conform to C90, unless you want to try to parse the output of strftime("%Z",...), but that's going to be fundamentally non-portable.
You then could convert your UTC components to time_t using mktime(), although they will be interpreted as in the local timezone; then apply the offset, and convert back to broken-down components using localtime(). You may run into edge cases around the time when your local timezone switches to and from DST (or when changes to your timezone's offset where effected), but this can be easily avoided by moving to a locale that does not use DST, or ameliorated by setting tm_dst to 0 when calling both strftime() and mktime().
Alternatively, don't restrict yourself to ANSI C.

Resources