Time zone conversion C API on Linux, anyone? - c

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

Related

mktime() for non-local timezone

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.

How do you undo the changes caused by the TZ environment variable and tzset() function?

I have unit test code with logic that applies to a specific time zone. Ideally the unit test executable would always run with the same behavior, and be isolated from the system time zone settings.
In other words, if the system time is U.S. Central, then the unit test passes, but if Asia/Tokyo (for example), then the unit test fails, and the goal would be for the unit test to always pass.
I know that I can set the TZ environment variable with setenv() or putenv(), then call the tzset() function, and this will affect results from mktime() and localtime() according to a new time zone. And examples of what you can set TZ are here:
http://famschmid.net/timezones.html
The general format of TZ is described here:
http://famschmid.net/uclinuxtutorial_nios.html#ntp
So the unit test executable can be made to have mktime() and localtime() behave as though the system were America/Chicago, with: TZ=CST+6CDT,M3.2.0,M11.1.0 (DST begins 2nd Sunday of March and ends 1st Sunday of November, with standard offset of UTC-6).
The same code works on both Windows and Linux.
However, the moment you do this in the executable, it affects all mktime() and localtime() calls throughout the code for the remainder of the execution (not just the specific tests).
My question is how do you undo the changes, so other code executing later that uses time.h local time functions, uses the system time settings (for example in timestamps in log messages)?
There are examples on the internet of doing a save/restore of the TZ environment variable around the test code. However, what if the TZ environment variable is initially blank?
Is there a way to populate the TZ variable with whatever is correct for the current system time settings? Or more directly, is there a way to revert any changes brought on by TZ and tzset();, and go back to how the process was on startup?
If there is no POSIX/ANSI C way to do it, is there any Windows and/or Linux specific API to undo the changes?
Here is a test program. It appears simply setting TZ to an empty string and calling tzset() undoes the changes on Windows, but not on Linux.
#include <stdio.h>
#include <stdlib.h>
#include <time.h>
#ifdef _WIN32
#include <Windows.h>
#endif
const char *Europe_Istanbul = "EET-2EEST,M3.5.0,M10.5.0";
const char *original_tz;
int set_env(const char *name, const char *value)
{
#ifdef _WIN32
return _putenv_s(name, value == NULL ? "" : value);
#else
if (value == NULL)
return unsetenv(name);
else
return setenv(name, value, 1);
#endif
}
void log_time(time_t t)
{
struct tm T;
char timestamp[128] = "";
T = *localtime(&t);
strftime(timestamp, sizeof(timestamp), "%FT%T%z", &T);
printf("%s\n", timestamp);
}
int main(void)
{
time_t current_time = time(NULL);
/* save TZ */
original_tz = getenv("TZ");
printf("TZ=%s\n", original_tz);
log_time(current_time);
set_env("TZ", Europe_Istanbul);
tzset();
log_time(current_time);
/* restore TZ */
set_env("TZ", original_tz);
tzset();
log_time(current_time);
return 0;
}
Output on Windows (no TZ):
D:\>echo %TZ%
%TZ%
D:\>test_tz
TZ=(null)
2019-11-26T11:16:58-0600
2019-11-26T19:16:58+0200
2019-11-26T11:16:58-0600
Output on Windows (with TZ)
D:\>set TZ=EST+5EDT,M3.2.0,M11.1.0
D:\>echo %TZ%
EST+5EDT,M3.2.0,M11.1.0
D:\>test_tz
TZ=EST+5EDT,M3.2.0,M11.1.0
2019-11-26T12:17:59-0500
2019-11-26T19:17:59+0200
2019-11-26T12:17:59-0500
Output on Linux (no TZ):
[user#host dir]$ echo $TZ
[user#host dir]$ ./test_tz
TZ=(null)
2019-11-26T11:23:18-0600
2019-11-26T19:23:18+0200
2019-11-26T11:23:18-0600
Output on Linux (with TZ):
[user#host dir]$ export TZ=EST+5EDT,M3.2.0,M11.1.0
[user#host dir]$ echo $TZ
EST+5EDT,M3.2.0,M11.1.0
[user#host dir]$ ./test_tz
TZ=EST+5EDT,M3.2.0,M11.1.0
2019-11-26T12:25:20-0500
2019-11-26T19:25:20+0200
2019-11-26T12:25:20-0500

Convert the time string of UTC to IST time in C language

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).

C API to return timestring in specified time zone?

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).

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