Converting from UTC to "local" time

D

Dilip

I was trying to find a generic way to do this. Under Windows I had
this until a few days ago:

struct tm local_time = { 0 };
struct tm t; // t has time in UTC
time_t tt = _mkgmtime(&t);
localtime_s(&local_time, &tt);

This stopped working ever since Daylight savings came into effect last
month. In New York, we are now in EDT when is UTC-4. The code above is
always off by an hour.

Is there a generic way to write this so that it works no matter where
my code runs?

There are some Windows APIs that will do the same thing but I'd like
to first see if I can get this done via standard CRT functions.
 
I

ImpalerCore

I was trying to find a generic way to do this. Under Windows I had
this until a few days ago:

struct tm local_time = { 0 };
struct tm t; // t has time in UTC
time_t tt = _mkgmtime(&t);
localtime_s(&local_time, &tt);

This stopped working ever since Daylight savings came into effect last
month. In New York, we are now in EDT when is UTC-4. The code above is
always off by an hour.

Is there a generic way to write this so that it works no matter where
my code runs?

There are some Windows APIs that will do the same thing but I'd like
to first see if I can get this done via standard CRT functions.

There is no standard C interface to modify UTC time to local time.
You can build a table of rules that specify when Daylight Savings Time
comes in and out of effect. You can create an associated array of a
region to its rules.

region --> DST rules

DST rules will need to parameterize the days of month that DST is
active, as well as what time on those days the time changes. For
example, timezones in the United States roll back at 2:00 am local
time (some regions do not enact DST at all), while Europe rolls back
the time at 01:00 UTC across all participating regions.

Since you said you're in New York, I found this.

Beginning in 2007, Daylight Saving Time was extended one month and the
schedule for the states of the United States that adopt daylight
saving time are:
2 a.m. on the Second Sunday in March to
2 a.m. on the First Sunday of November.

If you can convert these rules to their corresponding time windows for
the year in UTC, and check to see whether the current UTC time is in
that window, conversion to DST is fairly simple for a single region.
You just need an interface to get the nth weekday of a month, and
convert those local times to a corresponding UTC window. For this
year, it's

2012 Start/End
11 March 2012 02:00 (local) --> 11 March 2012 07:00 (UTC)
4 November 2012 02:00 (local DST) --> 4 November 2012 06:00 (UTC)

/* times in UTC */
if (time >= 1 January 2012 00:00 and time < 11 March 2012 07:00)
(subtract 5 hours)
else if (time >= 11 March 2012 07:00 and time < 4 Nov 2012 06:00 )
(subtract 4 hours)
else if (time >= 4 Nov 2012 06:00 and time < 31 December 2013 00:00 )
(subtract 5 hours)

That's the basic idea. The tedious part is building the database of
these rules and having the infrastructure to handle the myriad ways
regions specify their DST times.

Best regards,
John D.
 
I

ImpalerCore

2012 Start/End
11 March 2012 02:00 (local)       --> 11 March 2012 07:00 (UTC)
4 November 2012 02:00 (local DST) --> 4 November 2012 06:00 (UTC)

/* times in UTC */
if (time >= 1 January 2012 00:00 and time < 11 March 2012 07:00)
  (subtract 5 hours)
else if (time >= 11 March 2012 07:00 and time < 4 Nov 2012 06:00 )
  (subtract 4 hours)
else if (time >= 4 Nov 2012 06:00 and time < 31 December 2013 00:00 )
  (subtract 5 hours)

typo, should be

else if (time >= 4 Nov 2012 06:00 and time < 1 January 2013 00:00 )
(subtract 5 hours)
 
K

Kenny McCormack

ImpalerCore said:
There is no standard C interface to modify UTC time to local time.
You can build a table of rules that specify when Daylight Savings Time
comes in and out of effect. You can create an associated array of a
region to its rules.

Um, er, can I have "re-inventing the wheel for $1000, Alex???"

This is about the most egregious rendition of the Unix "You *could* do that"
philosophy I've seen in quite some time.
 
I

ImpalerCore

...


Um, er, can I have "re-inventing the wheel for $1000, Alex???"

This is about the most egregious rendition of the Unix "You *could* do that"
philosophy I've seen in quite some time.

I can't argue with that, but, someone was required at one time to do
the dirty work. Hopefully the OP realizes the magnitude of work to
roll his own using standard C and decides on the path of least
resistance, to use an OS-specific or external library.
 
K

Kaz Kylheku

Um, er, can I have "re-inventing the wheel for $1000, Alex???"

This is about the most egregious rendition of the Unix "You *could* do that"
philosophy I've seen in quite some time.

Speaking of Unix; you could do that, very easily.

BSD introduced some additional members to struct tm long ago. The tm_gmtoff
field indicates the offset from local time to UTC expressed as a time_t.

With this offset, you can shift your "coordinate system" betweeen local time
and UTC, making local time look like UTC if you want.

Vixie Cron does exactly this. It keeps the offset in a global variable, which
it updates every time it call localtime (in case the DST has changed).
 
D

Dilip

...


Um, er, can I have "re-inventing the wheel for $1000, Alex???"

This is about the most egregious rendition of the Unix "You *could* do that"
philosophy I've seen in quite some time.

I am pretty sure he was just pulling my leg but in that process
probably letting me know that what I want is next to impossible.
Windows does have this API: http://msdn.microsoft.com/en-us/library/windows/desktop/ms724949(v=vs.85).aspx
which will let me do the job in a minute. I was just hoping to avoid
shuffling some old codebase to accommodate this.
 
G

Guest

I was trying to find a generic way to do this. Under Windows I had
this until a few days ago:

struct tm local_time = { 0 };
struct tm t; // t has time in UTC
time_t tt = _mkgmtime(&t);
localtime_s(&local_time, &tt);

This stopped working ever since Daylight savings came into effect last
month. In New York, we are now in EDT when is UTC-4. The code above is
always off by an hour.

Is there a generic way to write this so that it works no matter where
my code runs?

There are some Windows APIs that will do the same thing but I'd like
to first see if I can get this done via standard CRT functions.

can't you compute the difference between gmtime() and localtime() and use this value to do the conversion? It's a bit of a PITA because you have to manipulate struct tm-s

if your platform is giving you incorrect localtime then you ned to fix *that*

<rant>
DST is an invention of the devil- the sun *should* be at its zenith at noon.
</rant>
 
G

Guest

can't you compute the difference between gmtime() and localtime() and use this value to do the conversion? It's a bit of a PITA because you have to manipulate struct tm-s

if your platform is giving you incorrect localtime then you need to fix *that*

<rant>
DST is an invention of the devil- the sun *should* be at its zenith at noon.
</rant>

is this any help:-


// utc2loc.c

#include <string.h>
#include <time.h>
#include <stdio.h>

typedef struct tm Tm;

void printTime (const char *name, const Tm* tm)
{
printf ("%s: %4d-%02d-%02d:%02d:%02d:%02d DST flag: %d\n",
name, tm->tm_year + 1900, tm->tm_mon + 1, tm->tm_mday, tm->tm_hour, tm->tm_min, tm->tm_sec, tm->tm_isdst);
}

toSeconds (const Tm* tm)
{
return tm->tm_sec + 60 * (tm->tm_min + 60 * (tm->tm_hour + 24 * tm->tm_yday));
}

// calculate local offset from UTC in seconds
long calcUtcOffset (const Tm* utc, const Tm* local)
{
long utcSecs = toSeconds (utc);
long locSecs = toSeconds (local);
return locSecs - utcSecs;
}

int main (void)
{
time_t rawtime;
Tm utcTm;
Tm localTm;
long utcOffset;

time (&rawtime);
memcpy (&localTm, localtime(&rawtime), sizeof(Tm));
memcpy (&utcTm, gmtime(&rawtime), sizeof(Tm));

printTime ("UTC time ", &utcTm);
printTime ("local time", &localTm);

utcOffset = calcUtcOffset (&utcTm, &localTm);
printf ("UTC offset is %s%ld\n", (utcOffset >= 0) ? "+" : "-", utcOffset);

return 0;
}


output:
UTC time : 2012-06-27:11:15:19 DST flag: 0
local time: 2012-06-27:12:15:19 DST flag: 1
UTC offset is +3600
 
H

Heinrich Wolf

Hi,

you need not bother with timezones and bias values at all to do this job.
You need only a few very useful functions from time.h .

time_t time(time_t *) gives the current time in seconds since Jan 1 1970
0:00:00 UTC
struct tm *localtime(time_t *) calculates the local time in a struct
(tm_year, tm_mon, tm_mday, ... tm_sec).
struct tm *gmtime(time_t *) calculates UTC in the same struct type.

Fill struct tm with a local time, set tm_isdst = -1 (unknown) and call
time_t mktime(struct tm *) to get the time_t.

I just have no idea, how to get time_t from a struct tm filled with UTC.

kind regards
Heiner
 
H

Heinrich Wolf

....
I just have no idea, how to get time_t from a struct tm filled with UTC.
....
In this thread I found _mkgmtime()

I am sorry for telling old stories.

Maybe just the hint for setting tm_isdst to -1 before calling mktime is
important
 
H

Heinrich Wolf

....
adjusted.tm_hour -= TIME_ZONE_OFFSET; /* set this for your region */
....
Do you mean a constant like
#define TIME_ZONE_OFFSET 1 ?
Please remember that there is a global variable
extern long timezone;
The unit is seconds.

Windows API also offers
DWORD GetTimeZoneInformation(
LPTIME_ZONE_INFORMATION lpTimeZoneInformation // address of time-zone
settings
);
 
H

Heinrich Wolf

....
Substituting timegm() for _mkgmtime(), this works for me. I'm in
Central time, but the issues for DST are the same. Yes, I tested
this for every half hour between 2-Jan-1970 and 1-Dec-2037.
....

My linux man page for timegm tells:

....
DESCRIPTION
The functions timelocal() and timegm() are the inverses of
localtime(3) and gmtime(3).

CONFORMING TO
These functions are nonstandard GNU extensions that are also present
on the BSDs. Avoid their use; see NOTES.

NOTES
The timelocal() function is equivalent to the POSIX standard function
mktime(3). There is no reason to ever use it.

For a portable version of timegm(), set the TZ environment variable
to UTC, call mktime(3) and restore the value of TZ.
....

kind regards
Heiner
 
H

Heinrich Wolf

....
For a portable version of timegm(), set the TZ environment variable
to UTC, call mktime(3) and restore the value of TZ.

The man page continues with a sample code for that. I tried it. The sample
code results in the same as timegm(). On my linux, getenv("TZ") results in
NULL. You have to call setenv("TZ", "", 1) and restore TZ with
unsetenv("TZ") in place of NULL.

....
 
H

Heinrich Wolf

Heinrich Wolf said:
...

The man page continues with a sample code for that. I tried it. The sample
code results in the same as timegm(). On my linux, getenv("TZ") results in
NULL. You have to call setenv("TZ", "", 1) and restore TZ with
unsetenv("TZ") in place of NULL.

...
I took the sample code, put a main() around it and did a very quick
successful test.
Here is my code:
#include <stdio.h>
#include <time.h>
#include <string.h>
#include <stdlib.h>

int main(int argc, char *argv[])
{
struct tm sZeit1,
sZeit2,
*pZeit;
time_t lZeit;
char *TZ;

if (argc != 3)
printf("%s dd.mm.YYYY hh:mm:ss\n"
"takes the arguments as GMT and prints the local time\n",
argv[0]);
else
{
memset(&sZeit1, 0, sizeof(sZeit1));
if (sscanf(argv[1], "%d.%d.%d", &sZeit1.tm_mday, &sZeit1.tm_mon,
&sZeit1.tm_year) != 3)
printf("%s is not dd.mm.YYYY\n", argv[1]);
else
{
sZeit1.tm_mon --;
sZeit1.tm_year -= 1900;
if (sscanf(argv[2], "%d:%d:%d", &sZeit1.tm_hour, &sZeit1.tm_min,
&sZeit1.tm_sec) != 3)
printf("%s is not hh:mm:ss\n", argv[2]);
else
{
memcpy(&sZeit2, &sZeit1, sizeof(sZeit2));
lZeit = timegm(&sZeit2);
printf("timegm(%s %s) = %ld\n", argv[1], argv[2], lZeit);
pZeit = localtime(&lZeit);
if (! pZeit)
printf("localtime(&%ld) is NULL\n", lZeit);
else
{
printf("localtime(&%ld) is %02d.%02d.%04d %02d:%02d:%02d\n",
lZeit,
pZeit->tm_mday,
pZeit->tm_mon + 1,
pZeit->tm_year + 1900,
pZeit->tm_hour,
pZeit->tm_min,
pZeit->tm_sec);
TZ = getenv("TZ");
if (! TZ)
printf("TZ = NULL\n");
else
printf("TZ = %s\n", TZ);
setenv("TZ", "", 1);
tzset();
lZeit = mktime(&sZeit2);
printf("GMT mktime(%s %s) = %ld\n", argv[1], argv[2], lZeit);
if (TZ)
setenv("TZ", TZ, 1);
else
unsetenv("TZ");
tzset();
}
}
}
}
return 0;
}
 
H

Heinrich Wolf

....
I took the sample code, put a main() around it and did a very quick
successful test.
Here is my code:
....
This code worked on linux. But on Windows with Borland C++Builder 5 tzset()
did not change _timezone and _daylight after putenv(). So the result was off
by 1 hour. Instead of putenv(TZ=\"\"") I had to set both _timezone and
_daylight to 0. Then the results were the expected ones. I did not need to
save the old values of _timezone and _daylight. Calling tzset() restored
them.

Here is my new code (with #ifdef for both linux and C++Builder):
#include <stdio.h>
#include <time.h>
#include <string.h>
#include <stdlib.h>

#ifdef linux
#define _timezone timezone
#define _daylight daylight
#endif

int main(int argc, char *argv[])
{
struct tm sZeit1,
sZeit2,
*pZeit;
time_t lZeit;
char *TZ1,
*TZ2,
buf[80];

if (argc != 3)
printf("%s dd.mm.YYYY hh:mm:ss\n"
"takes the arguments as GMT and prints the local time\n",
argv[0]);
else
{
memset(&sZeit1, 0, sizeof(sZeit1));
if (sscanf(argv[1], "%d.%d.%d",
&sZeit1.tm_mday, &sZeit1.tm_mon, &sZeit1.tm_year) != 3)
printf("%s is not dd.mm.YYYY\n", argv[1]);
else
{
sZeit1.tm_mon --;
sZeit1.tm_year -= 1900;
if (sscanf(argv[2], "%d:%d:%d",
&sZeit1.tm_hour, &sZeit1.tm_min, &sZeit1.tm_sec) != 3)
printf("%s is not hh:mm:ss\n", argv[2]);
else
{
memcpy(&sZeit2, &sZeit1, sizeof(sZeit2));
#ifdef linux
lZeit = timegm(&sZeit2);
printf("timegm(%s %s) = %ld\n", argv[1], argv[2], lZeit);
#endif
TZ1 = getenv("TZ");
if (! TZ1)
puts("TZ = NULL");
else
printf("TZ = %s\n", TZ1);
#ifdef linux
putenv("TZ=");
TZ2 = getenv("TZ");
if (! TZ2)
puts("TZ = NULL");
else
printf("TZ = %s\n", TZ2);
#else
_daylight = 0;
_timezone = 0;
#endif
printf("_daylight = %d\n", _daylight);
printf("_timezone = %d\n", _timezone);
lZeit = mktime(&sZeit2);
printf("GMT mktime(%s %s) = %ld\n", argv[1], argv[2], lZeit);
if (TZ1)
{
strcpy(buf, "TZ=\"");
strncat(buf, TZ1, sizeof(buf) - strlen(buf) - 1);
strncat(buf, "\"", sizeof(buf) - strlen(buf) - 1);
putenv(buf);
}
else
#ifdef linux
unsetenv("TZ");
#else
putenv("TZ=");
#endif
tzset();
printf("_daylight = %d\n", _daylight);
printf("_timezone = %d\n", _timezone);
TZ2 = getenv("TZ");
if (! TZ2)
puts("TZ = NULL");
else
printf("TZ = %s\n", TZ2);
pZeit = localtime(&lZeit);
if (! pZeit)
printf("localtime(&%ld) is NULL\n", lZeit);
else
printf("localtime(&%ld) is %02d.%02d.%04d %02d:%02d:%02d\n",
lZeit,
pZeit->tm_mday,
pZeit->tm_mon + 1,
pZeit->tm_year + 1900,
pZeit->tm_hour,
pZeit->tm_min,
pZeit->tm_sec);
}
}
}
puts("please press ENTER");
fgets(buf, sizeof(buf), stdin);
return 0;
}
 
H

Heinrich Wolf

Gordon Burditt said:
You cannot do the job with the pieces you have indicated. "The job" here
is to take a struct tm filled in with a GMT time stamp and convert it
to a struct tm filled in with a local time stamp.

I'm, sorry. It took a few readings until I realized your problem.

....
We are given a struct tm filled in with GMT; we are not trying to generate
one.


It would appear that you are proposing pieces of a solution to a
problem you didn't read carefully.

You are right. My fault.
This is comp.lang.c. The C standard does not guarantee that:

A time_t is a "number of <anything> since <anything>" (although POSIX
does).
A lot of implementations use this.

The functions _mkgmtime or timegm or any equivalent function exist.
Many implementations have at least 1 of these.

The external variables daylight, timezone, tzname, _daylight, or
_timezone exist (with time-related definitions). Some implementations
provide other alternatives, such as extra struct tm members.

The struct tm members tm_gmtoff and tm_zone exist.

I did not find tm_gmtoff in the compilers I use. These are Borland
C++Builder 5 and good old Turbo C 2.0 on Windows and gcc on fedora Linux.

Did you read, what I posted later? On Linux I found timegm() and a short
test revealed no problem. It also recommended not to use it. It suggested
and I tried using mktime() with a struct tm filled as GMT and having set the
timezone to GMT temporarily. There need to be some differences of the
implementation between gcc and Borland C++Builder 5, but I tested it
successfully.
 
H

Heinrich Wolf

Thank you very much for telling me things from the top of this thread. I
have resetted my newsreader not long ago. So I have no copy of this thread
before June 27th. As far as Windows is concerned, I mentioned the API call
GetTimeZoneInformation(). I believe, that is better than fiddling around
with Jan 1 and other assumptions about DST.
 
H

Heinrich Wolf

....
I tried that in the first post in this thread I made. I suggest
that the tm_isdst member was uninitialized in the OP's code (which
used _mkgmtime), causing the result to be off by an hour during
daylight saving time periods.
....
When filling structs for passing them to some library function, I have
acquired the habit of preparing the struct with memset(s, 0, sizeof(s));
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Members online

Forum statistics

Threads
473,755
Messages
2,569,534
Members
45,007
Latest member
obedient dusk

Latest Threads

Top