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.
Related
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.
I am looking for the UTC (from RFC3339 format) to IST conversion in C program. But I could not find any generic way to convert the time.
Here, I found the shell script to convert the UTC time (in RFC3339 format) to IST and I am trying to implement in C code.
From the script, I can't find the equivalent way for the statement
newdate=$(TZ=IST date -d "$formatT UTC-5:30") in C code.
So, I did time diff of -5:30 with the GMT time as shown in below snippet. But, it is not working as expected.
int main(int argc, char *argv[])
{
const char *utctime = "2019-07-24T11:47:33";
struct tm tm = {0};
char s[128] = {0};
if (NULL == (strptime(utctime, "%Y-%m-%dT%H:%M:%S", &tm))) {
printf("strptime failed\n");
}
printf("IST Time : %2d:%02d\n", ((tm.tm_hour-5)%24), tm.tm_min-30);
}
Kindly, guide me to do the task in C code that the script does.
Non-portable, linux:
struct tm time;
// fill appropriately
time_t utc = timegm(&time)
localtime_r(&utc, &time);
If your local time zone isn't IST, you need to change to before calling local time:
setenv("TZ", "IST-05:30:00", 1);
// ^ not entirely sure if this is correct, please verify yourself (*)
tzset();
Edit (following the comments): (*) Especially if daylight saving time (DST) has to be applied, the time zone string looks different; you find information about at tzset documentation. Possibly the server provides time zone information in a local file, then you might try :Asia/Kolkata as well.
You might retrieve current timezone with getenv first if you intend to restore it afterwards, complete example is shown at timegm documentation.
The portable way would now be to first set timezone to UTC, call mktime instead of timegm, then set timezone to IST and call localtime just as in non-portable version and finally restore local timezone (if intended/needed and you haven't already done so by setting it to IST).
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, <);
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);
};
In C, there is any API which converts time returned by time() function to a specific timezone?
There is a function strftime() which converts into current timezone of the system.
But what I want is function input is time (returned by time()) and timeZone and it will convert the specific formatted time in said timezone format.
Is there such an API?
POSIX specifies tzset():
The tzset() function shall use the value of the environment variable TZ to set time conversion information used by ctime, localtime, mktime, and strftime. If TZ is absent from the environment, implementation-defined default timezone information shall be used.
So, you could in theory use something like this to convert t0 (a time_t) into US/Eastern time when that is not your default TZ:
char old_tz[64];
strcpy(old_tz, getenv("TZ"));
setenv("TZ", "EST5", 1);
tzset();
struct tm *lt = localtime(&t0);
char buffer[64];
strftime(buffer, sizeof(buffer), "%Y-%m-%d %H:%M:%S", lt);
setenv("TZ", old_tz, 1);
tzset();
This preserves a copy of the old time zone (you can't rely on it not changing or going out of scope while you're dinking with this stuff), then sets the target time zone in the environment. It then tells the system to look at $TZ and set the local time zone accordingly. You then convert the given time_t value to a broken down local time with localtime(), format the string, then reset the TZ environment variable and tell the system to take another look at it with tzset() again.
In practice, this may or may not work; it depends on the system.
If you need thread-safe versions of this stuff, you'll have to work harder. This is categorically not thread-safe as written.
The sample code has skimped on error checking — it doesn't have any.
I would never claim this is a simple way of doing it; there ought to be a better way.
Test Code
#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];
strcpy(old_tz, getenv("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);
setenv("TZ", old_tz, 1);
tzset();
printf("%ld = %s (TZ=%s)\n", (long)t0, buffer, new_tz);
}
int main(void)
{
time_t t0 = time(0);
char *tz = getenv("TZ");
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");
}
Test Output
1335761328 = 2012-04-29 23:48:48 (TZ=CST6CDT)
1335761328 = 2012-04-30 04:48:48 (TZ=UTC0)
1335761328 = 2012-04-30 10:18:48 (TZ=IST-5:30)
1335761328 = 2012-04-29 23:48:48 (TZ=EST5)
1335761328 = 2012-04-30 00:48:48 (TZ=EST5EDT)
1335761328 = 2012-04-29 20:48:48 (TZ=PST8)
1335761328 = 2012-04-29 21:48:48 (TZ=PST8PDT)
Note that when the strings for EST and PST do not have a daylight saving code specified, you get one time zone offset; when there is a daylight saving code set (such as "EST5EDT" or "PST8PDT") and the time to be printed is at the appropriate time of year (such as end of April), you get the EDT or PDT time value printed.
Note, too, that there are conflicting ISO standards on how to handle time zones. ISO 8601 (for date/time formatting) says that time zones east of UTC (Greenwich) are positive and those west of UTC are negative. At least some other standards (e.g. SQL, ISO 9075) use the same notation. On the other hand, POSIX (aka ISO 9945) uses the opposite convention in TZ, as shown in the example code. The conflict is accepted only because of long-standing precedent. The C standard is silent on the format (or existence) of the TZ environment variable (though C99 §7.23.3.5 The strftime function does reference ISO 8601 a number of times).
And (just as a salutary reminder why error checking is a good idea), on Mac OS X 10.7.3, my environment doesn't have TZ set in it (but it is normally US/Pacific, aka Cupertino, aka America/Los_Angeles, time). So the code above crashes when getenv() returns a null pointer. I ran it with TZ=CST6CDT in the environment, as you can see from the first line of output.
There are really three steps to look at here. First, a time_t represents an absolute time stamp -- something at least conceptually on the order of a UTC timestamp. It's not (normally) in any particular time zone -- it typically represents the number of seconds since some epoch (often midnight January 1, 1970).
You can convert that to a struct tm, which breaks that time stamp out into an actual date and time -- a year, month, day, hour, minute, and second. There are two functions to do that conversion: gmtime, which leaves it as a UTC-style time (i.e., in the Greenwich time zone). The other is localtime, which converts it to whatever time zone the environment has been configured to believe represents the location of the machine.
The next step is to use strftime to convert that to something readable. This is based on the current locale, so (for example) as I write this it's what I think of as "Sunday". If my machine was configured for Spanish, however, that would probably show up as "Domingo", or something on that order.
You really have two rather different questions. You can only convert a time_t into a time for one of two time zones: the "local" time zone (at least, whatever the machine has been configured to think of a "local"), or Greenwich time. If you want any other time zone, you're pretty much stuck with something like converting to GM time, then adjusting to the timezone of your choice.
There is more provision for adjusting formatting. By default the library will always convert for the "C" locale, which is pretty much a simplified version of US English. You can also set the nameless locale ("") which gives you a locale that's suppose to match how the machine has been configured. As a third alternative, most libraries will let you specify a name for a specific locale, so if you wanted your date formatted as somebody in the French-speaking part of Canada would expect, you'd set the locale to something like "French_Canada" and that's what you'd get (though the string you use varies with the standard library, so you might need to use something like "fr-can" instead).
I'm looking for something that I presumed would be very simple - given local Unix time in a specific time zone (specified as a string, e.g., "America/New_York" - note that's not my local time), get the corresponding time value in GMT. I.e., something along the lines of
time_t get_gmt_time(time_t local_time,
const char* time_zone);
As deceptively simple as it sounds, the closest I could find was the following code snippet from timegm's man page:
#include <time.h>
#include <stdlib.h>
time_t
my_timegm(struct tm *tm)
{
time_t ret;
char *tz;
tz = getenv("TZ");
setenv("TZ", "", 1);
tzset();
ret = mktime(tm);
if (tz)
setenv("TZ", tz, 1);
else
unsetenv("TZ");
tzset();
return ret;
}
There gotta be a better way than this belligerently not thread-safe abomination, right? Right??
Wanted to add a bit more detail here.
If you try the following:
#include <stdio.h>
#include <time.h> /* defines 'extern long timezone' */
int main(int argc, char **argv)
{
time_t t, lt, gt;
struct tm tm;
t = time(NULL);
lt = mktime(localtime(&t));
gt = mktime(gmtime(&t));
printf( "(t = time(NULL)) == %x,\n"
"mktime(localtime(&t)) == %x,\n"
"mktime(gmtime(&t)) == %x\n"
"difftime(...) == %f\n"
"timezone == %d\n", t, lt, gt,
difftime(gt, lt), timezone);
return 0;
}
you'll notice that timezone conversions make sure that:
mktime(localtime(t)) == t, and
mktime(gmtime(t)) == t + timezone,
therefore:
difftime(mktime(gmtime(t)), mktime(localtime(t))) == timezone
(the latter is a global variable initialized by either tzset() or the invocation of any timezone conversion function).
Example output of the above:
$ TZ=GMT ./xx
(t = time(NULL)) == 4dd13bac,
mktime(localtime(&t)) == 4dd13bac,
mktime(gmtime(&t)) == 4dd13bac
difftime(...) == 0.000000
timezone == 0
$ TZ=EST ./xx
(t = time(NULL)) == 4dd13baf,
mktime(localtime(&t)) == 4dd13baf,
mktime(gmtime(&t)) == 4dd181ff
difftime(...) == 18000.000000
timezone == 18000
$ TZ=CET ./xx
(t = time(NULL)) == 4dd13bb2,
mktime(localtime(&t)) == 4dd13bb2,
mktime(gmtime(&t)) == 4dd12da2
difftime(...) == -3600.000000
timezone == -3600
In that sense, you're attempting to "do it backwards" - time_t is treated as absolute in UN*X, i.e. always relative to the "EPOCH" (0:00 UTC on 01/01/1970).
The difference between UTC and the current timezone (last tzset() call) is always in the external long timezone global.
That doesn't get rid of the environment manipulation uglyness, but you can save yourself the effort of going through mktime().
From tzfile(5), which documents the files in /usr/share/zoneinfo (on my system) in gruesome detail:
It seems that timezone uses tzfile
internally, but glibc refuses to
expose it to userspace. This is most
likely because the standardised
functions are more useful and
portable, and actually documented by
glibc.
Again, this is probably not what you're looking for (ie. an API), but the information is there and you can parse it without too much pain.
The problem with gmtime, localtime and their variants is the reliance on the TZ environment variable. The time functions first call tzset(void), which reads TZ to determine offsets DST, etc. If TZ is not set in the user's environment, (g)libc uses the system timezone. So if you have a local struct tm in, say, 'Europe/Paris' and your machine or environment is set to 'America/Denver', the wrong offset will be applied when you convert to GMT. All the time functions call tzset(void) which reads TZ to set char *tzname[2], long timezone (diff, in seconds, from GMT) and int daylight (boolean for DST). Setting these directly has no affect, because tzset() will overwrite them the next time you call localtime, etc.
I was faced with the same issue as 'igor' in the original question, while setenv works it seems problematic (re-entran?). I decided to look further to see if I could modify tzset (void) to tzset(char*) to explicitly set the above mentioned variables. Well, of course, that's just a bad idea... but in probing the glibc source and the IANA TZ database source, I came to the conclusion that the setenv approach ain't so bad.
First, setenv only modifies the process global 'char **environ' (not the calling shell, so the 'real' TZ is not affected). And, second, glibc actually puts a lock in setenv. The drawback is that setenv/tzset calls are not atomic, so another thread could conceivably write to TZ before the original thread call tzset. But a well-implemented application that uses threads should watch for that anyway.
It would be cool if POSIX defined tzset to take a char* for look up in the extensive IANA TZ database (and take NULL to mean, 'use the user or system TZ/), but failing that, setenv seems to be ok.
I really thought there was something in glib, but seem to have misremembered. I know you're probably looking for straight-up C code, but here's the best I've got:
I know that Python has some notion of timezones through a tzinfo class - you can read about it in the datetime documentation. You can have a look at the source code for the module (in the tarball, it's in Modules/datetime.c) - it appears to have some documentation, so maybe you can get something out of it.
Similar to the Python answer, I can show you what R does:
R> now <- Sys.time() # get current time
R> format(now) # format under local TZ
[1] "2009-08-03 18:55:57"
R> format(now,tz="Europe/London") # format under explicit TZ
[1] "2009-08-04 00:55:57"
R> format(now,tz="America/Chicago") # format under explicit TZ
[1] "2009-08-03 18:55:57"
R>
but R uses an internal representation that extends the usual struct tm --- see R-2.9.1/src/main/datetime.c.
Still, this is a hairy topic and it would be nice if it were the standard library. As it isn't maybe your best bet is to use Boost Date_Time (example)
Why can't you use the gmtime_r()? Following worked fine for me:
int main()
{
time_t t_gmt, t_local=time(NULL);
struct tm tm_gmt;
gmtime_r(&t_local, &tm_gmt);
t_gmt = mktime(&tm_gmt);
printf("Time now is: %s", ctime(&t_local));
printf("Time in GMT is: %s", ctime(&t_gmt));
return 0;
}