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

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

Related

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

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.

Time zone conversion C API on Linux, anyone?

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

Converting between timezones in C

I need to convert time between timezones in C (on linux, so anything specific would do too).
I know my current time, local and UTC, I have the offset of the target time. I am trying to use mktime, gmtime, localtime and similar set of functions but still can't figure it out.
Thanks in advance.
As comments do not allow posting the code, posting as a separate answer.. If you know "local" time and "UTC" time, you can calculate the offset of the "other" time from your "local" time. Then you convert the struct tm into calendar time, add the desired number of seconds (being the offset of the target time), and convert it back to struct tm:
(edited to account for another scenario to use mktime's normalization)
#include <stdio.h>
#include <stdlib.h>
#include <time.h>
#include <sys/time.h>
int main(int argc, char *argv) {
struct timeval tv_utc;
struct tm *local_tm, *other_tm;
/* 'synthetic' time_t to convert to struct tm for the other time */
time_t other_t_synt;
/* Other time is 1 hour ahead of local time */
int other_local_delta = 1*3600;
/* the below two lines are just to set local_tm to something */
gettimeofday(&tv_utc, NULL);
local_tm = localtime(&tv_utc.tv_sec);
printf("Local time: %s", asctime(local_tm));
#ifdef DO_NOT_WRITE_TO_LOCAL_TM
other_t_synt = mktime(local_tm) + other_local_delta;
#else
local_tm->tm_sec += other_local_delta;
/* mktime will normalize the seconds to a correct calendar date */
other_t_synt = mktime(local_tm);
#endif
other_tm = localtime(&other_t_synt);
printf("Other time: %s", asctime(other_tm));
exit(0);
}
You can use gmtime() and the tm structure to directly set this, provided you know the offsets.
If you know your local time and UTC, you know your local offset. Provided you also know the target offset, it's just a matter of setting tm_hour appropriate (and potentially flipping the day, too, if you go <0 or >23).
For some sample code, see this gmtime reference page. It shows offsetting based off time zone offsets.
Edit:
In response to the comments - you can also let mktime handle the shifting for you, which allows you to simplify this by converting back to a time_t. You can use something like:
time_t currentTime;
tm * ptm;
time ( &currentTime );
ptm = gmtime ( &rawtime );
ptm->tm_hour += hours_to_shift;
ptm->tm_minutes += minutes_to_shift; // Handle .5 hr timezones this way
time_t shiftedTime = mktime( ptm );
// If you want to go back to a tm structure:
tm * pShiftedTm = gmtime( &shiftedTime );
In all likelyhood, your operating system provides some support for this.
In unix derived OSs you might want to look at the man pages for asctime, asctime_r, ctime, ctime_r, difftime, gmtime, gmtime_r, localtime, localtime_r, mktime, timegm.

Resources