Why localtime() gives different timezone on the same machine? - c

As the title said, below is the code. The timezone in the output will be changed according to the value of "tmt".
Environment:
[/tmp#16:01]uname -a
Linux ubuntu 3.13.0-100-generic #147-Ubuntu SMP Tue Oct 18 16:48:51 UTC 2016 x86_64 x86_64 x86_64 GNU/Linux
Code:
#include <stdio.h>
#include <time.h>
#include <strings.h>
#define LOCKOUT_TIME_FORMAT "%Y-%m-%dT%H:%M:%S%z"
int main()
{
time_t tmt = 0;
time_t tmt1 = 0;
struct tm * ptm = NULL;
char str[128] = {0};
time(&tmt1);
ptm = localtime(&tmt1);
strftime(str, sizeof(str), LOCKOUT_TIME_FORMAT, ptm);
printf("time1 is %s\n", str);
//tmt=3600 * 24 * 30; //one month
//tmt=3600 * 24 * 30 * 6; //about six month
//tmt=3600 * 24 * 30 * 12; //about one year
//tmt=3600 * 24 * 30 * 12 * 10; //about ten years
//tmt=3600 * 24 * 30 * 12 * 11; //about 11 years
tmt=3600 * 24 * 30 * 12 * 20; //about 20 years
ptm = localtime(&tmt);
strftime(str, sizeof(str), LOCKOUT_TIME_FORMAT, ptm);
printf("time is %s\n", str);
return 0;
}
When tmt=3600 * 24 * 30 * 12 * 20, the output timezone of tmt and tmt1 will be the same.
[/tmp#15:58]./a.out
time1 is 2017-03-23T15:58:20-0700
time is 1989-09-17T17:00:00-0700
When tmt is other values commented out in the code, the output timezone of tmt and tmt1 will be different!
[/tmp#16:01]./a.out
time1 is 2017-03-23T16:01:07-0700
time is 1980-11-03T16:00:00-0800
Personally, I think the timezone should be the same since the code is run on the same machine. Why the timezone changes as the value of time?
Thanks,

localtime() handles summer time transitions properly.
The date you entered int tmt variable is one of the summer days while the current date is not a summer day.
I suggest this output format for testing ("%Z" means "The timezone name or abbreviation")
#define LOCKOUT_TIME_FORMAT "%Y-%m-%dT%H:%M:%S\t|%Z"
Results:
$ TZ="Europe/Moscow" ./test
time1 is 2017-03-24T03:01:07 |MSK
time is 1989-09-18T04:00:00 |MSD
MSK is Moscow "regular" time and MSD means Moscow Summer Time.
This is known as Daylight Saving Time.

Related

struct tm tm_isdst disagrees with BST

I'm in the UK. I'm using C++ builder 10.2 with the clang compiler. The following code
#include <stdio.h>
#include <conio.h>
#include <time.h>
#ifdef _WIN32
#include <tchar.h>
#else
typedef char _TCHAR;
#define _tmain main
#endif
int _tmain()
{
printf("TZ set = %s\r\n",putenv("TZ=Europe/London")==0 ? "true" : "false");
printf("TZ=%s\r\n",getenv("TZ"));
for (int dst = 0, year = 2017; year <= 2023; year++)
for (int mon = 1; mon <= 12; mon++)
for (int mday = 1; mday <= 31; mday++)
{
struct tm st = { 0, 0, 12, mday, mon - 1, year - 1900 };
st.tm_isdst=-1;
time_t tt = mktime(&st); // this sets the tm_isdst to 1 or 0
if (st.tm_isdst != dst)
{
dst = st.tm_isdst;
printf("%02d/%02d/%d (%ld) ", mday - !dst, mon, year, tt-(!dst)*24*60*60);
if (!dst) printf("\r\n");
}
}
getch();
}
produces the following output
12/03/2017 (1489316400) 04/11/2017 (1509796800)
11/03/2018 (1520766000) 03/11/2018 (1541246400)
10/03/2019 (1552215600) 02/11/2019 (1572696000)
08/03/2020 (1583665200) 00/11/2020 (1604145600)
14/03/2021 (1615719600) 06/11/2021 (1636200000)
13/03/2022 (1647169200) 05/11/2022 (1667649600)
12/03/2023 (1678618800) 04/11/2023 (1699099200)
(The 00/11/2020 should be 30/10/2020 but I don't see the point of complicating the code to correct it).
The problem is the above dates are totally at odds with British Summer Time as listed by wiki -
2017 26 March 29 October
2018 25 March 28 October
2019 31 March 27 October
2020 29 March 25 October
2021 28 March 31 October
2022 27 March 30 October
2023 26 March 29 October
The BST starting dates provided by my code (left hand side) return unix timestamps that are 3600 secs (1 hour) out. From comments below it seems the output would be all correct if my TZ was set to Canadian-American but it's set to London.
EDIT: I'm rephrasing the question. HITF do you get the code above to use the time zone as set in the Windows 10 settings? No matter what I set the time zone to it still comes up with similar dates. The only time I get a correct answer is if I specifically make the time zone (UTC-8.00) Pacific Time (US & Canada). It seems to use that time zone regardless of the one selected in settings. It's been bad enough waking up during this lockdown and not knowing what day it is. Now I don't even know what time zone it is.
EDIT2: I added the lines
printf("TZ set = %s\r\n",putenv("TZ=Europe/London")==0 ? "true" : "false");
printf("TZ=%s\r\n",getenv("TZ"));
to the code and while they printed
TZ set = true
TZ=Europe/London
nothing changed.
Those dates are the first and last date of Canadian-American DST. Check what time zone is specified by the TZ environment variable.
Other issues:
You assume the order of fields in struct tm, but the order isn't specified by the language.
You don't initialize the tm_isdst field correctly. -1 should be used for if it's unknown whether DST is being used or not. The value is presumably used to handle the overlapped ("fall back") hours in a change out of DST.
Your code assumes the switch to DST happens earlier in the year than the switch from DST, but it would be the opposite in the southern hemisphere.
Program with these issues fixed:
#include <stdio.h>
#include <time.h>
int main(void) {
int dst = -1, dst_syear, dst_smon, dst_smday;
for (int year=2017; year<=2023; ++year) {
for (int mon=1; mon<=12; ++mon) {
for (int mday=1; mday<=31; ++mday) {
// Note that using .tm_isdst = -1 instead of the proper value
// will cause mktime to fail during one of the overlapped hours
// of a "fall back" change from DST.
struct tm st = {
.tm_year = year-1900,
.tm_mon = mon-1,
.tm_mday = mday,
.tm_hour = 12,
.tm_isdst = -1,
};
mktime(&st); // This sets the tm_isdst to 1 or 0
if (dst == -1) {
if (st.tm_isdst == 0) {
dst = 0;
}
} else {
if (st.tm_isdst != dst) {
dst = st.tm_isdst;
if (st.tm_isdst) {
dst_syear = year;
dst_smon = mon;
dst_smday = mday;
} else {
printf("%d-%02d-%02d %d-%02d-%02d\n",
dst_syear, dst_smon, dst_smday,
year, mon, mday);
}
}
}
}
}
}
return 0;
}
Output:
$ TZ=Europe/London ./a
2017-03-26 2017-10-29
2018-03-25 2018-10-28
2019-03-31 2019-10-27
2020-03-29 2020-10-25
2021-03-28 2021-10-31
2022-03-27 2022-10-30
2023-03-26 2023-10-29
$ TZ=America/Toronto ./a
2017-03-12 2017-11-05
2018-03-11 2018-11-04
2019-03-10 2019-11-03
2020-03-08 2020-11-01
2021-03-14 2021-11-07
2022-03-13 2022-11-06
2023-03-12 2023-11-05
$ TZ=Australia/Sydney ./a
2017-09-31 2018-04-01
2018-10-07 2019-04-07
2019-10-06 2020-04-05
2020-10-04 2021-04-04
2021-10-03 2022-04-03
2022-10-02 2023-04-02

C code to get local time offset in minutes relative to UTC?

I didn't find a trivial way to get the time offset in minutes between the local time and the UTC time.
At first I intended to use tzset() but it doesn't provide the daylight saving time. According to the man page, it is simply an integer different of zero if day light saving is in effect. While it is usually an hour, it may be half an hour in some country.
I would prefer avoiding to compute the time difference between current UTC returned by gmtime() and localtime().
A more general solution would give me this information for a specified location and a positive time_t value, or at least locally.
Edit 1: the use case is to get the right local time offset for https://github.com/chmike/timez.
BTW, If you thought libc functions to manipulate time were Ok, read this https://rachelbythebay.com/w/2013/03/17/time/.
Edit 2: the best and simplest solution I have so far to compute the time offset to UTC in minutes is
// Bogus: assumes DST is always one hour
tzset();
int offset = (int)(-timezone / 60 + (daylight ? 60 : 0));
The problem is to determine the real day light saving time.
Edit 3: Inspired by the answer of #trenki, I came up with the following solution. This is a hack in that it tricks mktime() to consider the output of gmtime() as the localtime. The result is inaccurate when the DST change is in the time span between UTC time and localtime.
#include <stdio.h>
#include <time.h>
int main()
{
time_t rawtime = time(NULL);
struct tm *ptm = gmtime(&rawtime);
// Request that mktime() looksup dst in timezone database
ptm->tm_isdst = -1;
time_t gmt = mktime(ptm);
double offset = difftime(rawtime, gmt) / 60;
printf("%f\n", offset);
return 0;
}
This C code computes the local time offset in minutes relative to UTC. It assumes that DST is always one hour offset.
#include <stdio.h>
#include <time.h>
int main()
{
time_t rawtime = time(NULL);
struct tm *ptm = gmtime(&rawtime);
time_t gmt = mktime(ptm);
ptm = localtime(&rawtime);
time_t offset = rawtime - gmt + (ptm->tm_isdst ? 3600 : 0);
printf("%i\n", (int)offset);
}
It uses gmtime and localtime though. Why don't you want to use those functions?
Does your system's strftime() function support the %z and %Z specifiers? On FreeBSD,
%Z is replaced by the time zone name.
%z is replaced by the time zone offset from UTC; a leading plus sign
stands for east of UTC, a minus sign for west of UTC, hours and
minutes follow with two digits each and no delimiter between them
(common form for RFC 822 date headers).
and I can use this to print this:
$ date +"%Z: %z"
CEST: +0200
ISO C99 has this in 7.23.3.5 The strftime function:
%z is replaced by the offset from UTC in the ISO 8601 format
‘‘−0430’’ (meaning 4 hours 30 minutes behind UTC, west of Greenwich),
or by no characters if no time zone is determinable. [tm_isdst]
%Z is replaced by the locale’s time zone name or abbreviation, or by no
characters if no time zone is determinable. [tm_isdst]
... to get local time offset ... relative to UTC?
#Serge Ballesta answer is good. So I though I would test it and clean-up a few details. I would have posted as a comment but obviously too big for that. I only exercised it for my timezone, but though others may want to try on their machine and zone.
I made to community wiki as not to garner rep. Imitation is the sincerest form of flattery
This answer is akin to #trenki except that it subtracts nearby struct tm values instead of assuming DST shift is 1 hour and time_t is in seconds.
#include <limits.h>
#include <stdlib.h>
#include <stdio.h>
#include <time.h>
// return difference in **seconds** of the tm_mday, tm_hour, tm_min, tm_sec members.
long tz_offset_second(time_t t) {
struct tm local = *localtime(&t);
struct tm utc = *gmtime(&t);
long diff = ((local.tm_hour - utc.tm_hour) * 60 + (local.tm_min - utc.tm_min))
* 60L + (local.tm_sec - utc.tm_sec);
int delta_day = local.tm_mday - utc.tm_mday;
// If |delta_day| > 1, then end-of-month wrap
if ((delta_day == 1) || (delta_day < -1)) {
diff += 24L * 60 * 60;
} else if ((delta_day == -1) || (delta_day > 1)) {
diff -= 24L * 60 * 60;
}
return diff;
}
void testtz(void) {
long off = -1;
int delta = 600;
for (time_t t = 0; t < LONG_MAX-delta; t+=delta) {
long off2 = tz_offset_second(t);
// Print time whenever offset changes.
if (off != off2) {
struct tm utc = *gmtime(&t);
printf("%10jd %04d-%02d-%02dT%02d:%02d:%02dZ\n", (intmax_t) t,
utc.tm_year + 1900, utc.tm_mon + 1, utc.tm_mday,
utc.tm_hour, utc.tm_min, utc.tm_sec);
struct tm local = *localtime(&t);
off = off2;
printf("%10s %04d-%02d-%02d %02d:%02d:%02d %2d %6ld\n\n", "",
local.tm_year + 1900, local.tm_mon + 1, local.tm_mday,
local.tm_hour, local.tm_min, local.tm_sec, local.tm_isdst ,off);
fflush(stdout);
}
}
puts("Done");
}
Output
v----v Difference in seconds
0 1970-01-01T00:00:00Z
1969-12-31 18:00:00 0 -21600
5731200 1970-03-08T08:00:00Z
1970-03-08 03:00:00 1 -18000
26290800 1970-11-01T07:00:00Z
1970-11-01 01:00:00 0 -21600
...
2109222000 2036-11-02T07:00:00Z
2036-11-02 01:00:00 0 -21600
2120112000 2037-03-08T08:00:00Z
2037-03-08 03:00:00 1 -18000
2140671600 2037-11-01T07:00:00Z
2037-11-01 01:00:00 0 -21600
Done
IMHO the only foolproof and portable way is to use localtime and gmtime and manually compute the delta in minute because those 2 functions exist on all known systems. For example:
int deltam() {
time_t t = time(NULL);
struct tm *loc = localtime(&t);
/* save values because they could be erased by the call to gmtime */
int loc_min = loc->tm_min;
int loc_hour = loc->tm_hour;
int loc_day = loc->tm_mday;
struct tm *utc = gmtime(&t);
int delta = loc_min - utc->tm_min;
int deltaj = loc_day - utc->tm_mday;
delta += (loc_hour - utc->tm_hour) * 60;
/* hack for the day because the difference actually is only 0, 1 or -1 */
if ((deltaj == 1) || (deltaj < -1)) {
delta += 1440;
}
else if ((deltaj == -1) || (deltaj > 1)) {
delta -= 1440;
}
return delta;
}
Beware, I did not test all possible corner cases, but it could be a starting point for your requirement.
I would like to submit yet another answer to this question, one that AFAICS also deals with the IDL.
This solution depends on timegm and mktime. On Windows timegm is available as _mkgmtime from the CRT, in other words define a conditional macro.
#if _WIN32
# define timegm _mkgmtime
#endif
int local_utc_offset_minutes ( ) {
time_t t = time ( NULL );
struct tm * locg = localtime ( &t );
struct tm locl;
memcpy ( &locl, locg, sizeof ( struct tm ) );
return (int)( timegm ( locg ) - mktime ( &locl ) ) / 60;
}
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 the field 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.

how to deduce current time (Clock) from unix gettime Jan 1970 API?

I am using the time() function which returns the number of seconds passed since jan 1970;
I want to deduce the current time and Date , here is what i have seen..
#define SECONDS_PER_YEAR 31536000u
#define SECONDS_PER_MONTH 2628288u
#define SECONDS_PER_WEEK 604800u
...
time_t unaccountedSeconds = time(NULL);
// Calculate years
int years = unaccountedSeconds / SECONDS_PER_YEAR;
// Seconds not absorbed by years
unaccountedSeconds = unaccountedSeconds % SECONDS_PER_YEAR;
// Calculate months
int months = unaccountedSeconds / SECONDS_PER_MONTH;
// Seconds not absorbed by months
unaccountedSeconds = unaccountedSeconds % SECONDS_PER_MONTH;
// Calculate weeks
that didn't work for me.
I suggest you use the localtime(3) function. That does the breakdown for you, and gives you a filled struct tm.
Try this:
time_t now = time(NULL);
struct tm* tmTime = localtime(&now);
int years = tmTime.tm_year;
int months = tmTime.tm_mon;
int wDay = tmTime.tm_wday; // day of the week
// ...
man -a time
man -a asctime
man -a localtime
result = time(NULL);
printf("current time %s its %ju secs since the Epoch\n",
asctime(localtime(&result)), (uintmax_t)result);
return(0);

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);
}

Converting string containing localtime into UTC in C

I have a string containing a local date/time and I need to convert it to a time_t value (in UTC) - I've been trying this:
char* date = "2009/09/01/00";
struct tm cal = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, NULL};
strptime(date, "%Y/%m/%d/%H", &cal);
time_t t = mktime(&cal);
but the time_t value I get back is the value that I would expect if the string was being parsed as UTC not local time. Maybe I have misunderstood what strptime is supposed to do, but in my timezone (UK) on the 1st September we are using BST (ie UTC + 1 hour) so I would expect the value I end up with to be 1 hour ahead of UTC.
Is there a way to interpret the string as localtime, automatically taking into account the UTC offset that would have been in effect on that date? Note that I need the time_t value not a struct tm, in the example above I want the time_t value to correspond to 2009-09-01 01:00:00 GMT
You can use mktime to interpret a struct tm in the local timezone. When you do so, be careful to set the tm_isdst flag. It's 0 for summertime, 1 for wintertime, and to -1 to have mktime figure it out. Here's some example code:
void main()
{
char* date = "2009/09/01/00";
struct tm cal = {};
// Read string into struct tm
strptime(date, "%Y/%m/%d/%H", &cal);
// Tell mktime to figure out the daylight saving time
cal.tm_isdst = -1;
printf("%20s: %s", "Before mktime", asctime(&cal));
// Convert struct tm to time_t
time_t t = mktime(&cal);
// Convert time_t to localtime
struct tm localcal = *localtime(&t);
printf("%20s: %s", "Local time", asctime(&localcal));
printf("%20s: %i\n", "Local DST", localcal.tm_isdst);
// Convert time_t to GMT
struct tm gmcal = *gmtime(&t);
printf("%20s: %s", "GM time", asctime(&gmcal));
printf("%20s: %i\n", "GM DST", gmcal.tm_isdst);
}
This prints (I live in GMT+1, and it's wintertime now):
Before mktime: Tue Sep 1 00:00:00 2009
Local time: Tue Sep 1 00:00:00 2009
Local DST: 1
GM time: Mon Aug 31 22:00:00 2009
GM DST: 0
It looks like mktime converts a date in September based on the current daylight savings time. It's November now, so it's actually one hour off. I haven't found a way to correct that.
Here's my version, using tm_gmtoff. Hopefully, the library takes care of daylight savings time ...
#define _BSD_SOURCE
#define _XOPEN_SOURCE
#include <stdio.h>
#include <time.h>
int gmtoffset(void) {
struct tm *dummy;
time_t t = 0;
dummy = localtime(&t);
return dummy->tm_gmtoff; /* _BSD_SOURCE */
}
int main(void) {
int off;
const char *date = "2009/09/01/00";
struct tm cal = {0};
time_t t;
off = gmtoffset();
strptime(date, "%Y/%m/%d/%H", &cal); /* _XOPEN_SOURCE */
t = mktime(&cal);
printf("t --> %s", ctime(&t)); /* ctime includes a final '\n' */
t -= off;
printf("t-off --> %s", ctime(&t));
return 0;
}
$ /usr/bin/gcc ptime.c
$ ./a.out
t --> Tue Sep 1 01:00:00 2009
t-off --> Tue Sep 1 00:00:00 2009
I think I've cracked it now, thanks to Andomar - this code does what I need and appears to work regardless of the current DST status (I changed the clock on my PC to check this):
#include <time.h>
#include <assert.h>
time_t parseLocalDate(char* date){
struct tm cal = {0, 0, 0, 0, 0, 0, 0, 0, -1, 0, NULL};
strptime(date, "%Y/%m/%d/%H", &cal);
return mktime(&cal);
}
int main(int argc, char *argv[]){
// DST is effect, Local Time = GMT+1
assert(1251759600 == parseLocalDate("2009/09/01/00")); // Mon, 31 Aug 2009 23:00:00 GMT
assert(1254351600 == parseLocalDate("2009/10/01/00")); // Wed, 30 Sep 2009 23:00:00 GMT
// DST not in effect, Local Time = GMT
assert(1257033600 == parseLocalDate("2009/11/01/00")); // Sun, 01 Nov 2009 00:00:00 GMT
}

Resources