Reputation: 10310
I'm writing an application in C that parses data files recorded by an external program (over which I have no control). It's storing binary data, one field of which is a time in standard UNIX "epoch" format (seconds since January 1st, 1970, UTC).
Another field is the timezone, stored as an offset in seconds from UTC.
Cool, I've got everything I need to make a date/time string representing that information in the timezone it was recorded in, right? Well... it doesn't seem so, and/or I'm not sure how to do it.
I've boiled things down to a fairly simple test case:
#include <stdio.h>
#include <time.h>
int main(void)
{
time_t t;
struct tm *tm;
char buf[BUFSIZ];
int offset = 4980; /* slightly bizarre, just to test this - an hour
* and 23 minutes ahead of UTC */
t = time(NULL);
tm = localtime(&t);
strftime(buf, BUFSIZ, "%FT%T%z", tm);
printf("before: %s\n", buf);
/* since we're not telling localtime anything different,
* compensate here (by subtracting applied offset, and adding
* desired one): */
t += offset - tm->tm_gmtoff;
tm = localtime(&t);
tm->tm_zone = "XYZ"; // not used -- but it was in an earlier version
tm->tm_gmtoff = offset;
// on macos, I used to also have %+, which referenced tm_zone
strftime(buf, BUFSIZ, "%FT%T%z", tm);
printf("after: %s\n", buf);
return 0;
}
When I run this on MacOS X 10.6, I get:
before: 2011-02-23T00:53:04-0800
after: 2011-02-23T10:16:04-0800
What I would expect (and in fact get, on a Linux box) would be:
before: 2011-02-23T00:53:04-0800
after: 2011-02-23T10:16:04+0123
Do I need to change the TZ
environment variable (and maybe call tzset
)? It seems like there ought to be a way to manipulate the data structures and get the right thing, but the above certainly isn't working (on MacOS X 10.6, anyway -- works great on Linux).
As a workaround, I suppose I can drop the %z from my format string and create that part myself.
Ideally, though, I'd like to have either a modification of my struct tm
, or some other function call that I can use (like strftime, but with an extra parameter or something, or perhaps an alternate form of localtime, instead), that would make things do the right thing.
Since Linux seems to behave (though even there, the above solution isn't quite ideal, because I'm fudging my time_t
value; I'd prefer to have a parameter that changes how the struct tm
is calculated), is this something that I should report as a bug against MacOS?
Alternately, is there a different set of library routines I could call, even if that ends up requiring a third-party (something from the GNU folks, I'm imagining) library? I'd prefer to keep to C, though I'd consider ObjC or C++ options.
Upvotes: 4
Views: 3159
Reputation: 9509
I personally like to embed small python scripts in my bash code. Python has many powerful out-of-the-box libraries for functionalities like you require.
You can embed python code in a bash script as follows (Assuming python is installed)
python << END
from pytz import timezone
south_africa = timezone('Africa/Johannesburg')
sa_time = datetime.now(south_africa)
print sa_time.strftime('%Y-%m-%d_%H-%M-%S')
END
You can change the timezone setting as you wish to display in different timezones. Have a look at http://pytz.sourceforge.net/ for more details.
Upvotes: -1
Reputation: 7554
I prefer to use mktime()
instead of modifying the time_t
value. However, this applies the TZ
offset (just like the localtime()
solution), so a mkgmtime()
implementation is required.
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>
static void set_timezone(char *tz)
{
static char var[1024];
snprintf(var, sizeof(var), "TZ=%s", tz);
putenv(var);
tzset();
}
static time_t mkgmtime(struct tm *tm)
{
char var[1024];
time_t t;
snprintf(var, sizeof(var), "%s", getenv("TZ") ? getenv("TZ") : "");
set_timezone("GMT0");
t = mktime(tm);
set_timezone(var);
return t;
}
int main(void)
{
time_t t;
struct tm tm;
char buf[BUFSIZ];
int offset = 4980; /* slightly bizarre, just to test this - an hour
* and 23 minutes ahead of UTC */
t = time(NULL);
tm = *localtime(&t);
strftime(buf, BUFSIZ, "%FT%T%z", &tm);
printf("before: %s\n", buf);
tm = *gmtime(&t);
tm.tm_sec += offset;
mkgmtime(&tm);
strftime(buf, BUFSIZ, "%FT%T%z", &tm);
printf("after: %s\n", buf);
return 0;
}
Upvotes: 3
Reputation: 11797
I think that the best approach would be
read your value into a time_t
variable;
set the TZ environment variable following these guidelines. See also this page for info on how you should specify TZ;
invoke localtime()
.
This should be guaranteed to work, althought I can't test it on a MacOS platform.
Upvotes: 0