lindes
lindes

Reputation: 10310

C: How to print a particular `time` value in a particular timezone (by offset)?

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

Answers (3)

dopplesoldner
dopplesoldner

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

Randy Proctor
Randy Proctor

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

Simone
Simone

Reputation: 11797

I think that the best approach would be

  1. read your value into a time_t variable;

  2. set the TZ environment variable following these guidelines. See also this page for info on how you should specify TZ;

  3. invoke localtime().

This should be guaranteed to work, althought I can't test it on a MacOS platform.

Upvotes: 0

Related Questions