C API to return timestring in specified time zone?

2019-04-08 04:18发布

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?

标签: c timezone
2条回答
可以哭但决不认输i
2楼-- · 2019-04-08 04:47

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.

查看更多
3楼-- · 2019-04-08 05:08

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

查看更多
登录 后发表回答