Подтвердить что ты не робот

Преобразование часовых поясов C API в Linux, кто-нибудь?

Я ищу что-то, что, по моему мнению, было бы очень простым - учитывая локальное время Unix в определенном часовом поясе (указанном в виде строки, например, "Америка/Нью-Йорк" - обратите внимание, что не мое местное время), получите соответствующее значение времени в GMT. I.e, что-то вдоль линий

time_t get_gmt_time(time_t local_time,
                    const char* time_zone);

Как обманчиво просто, как это звучит, самым близким, который я мог найти, был следующий фрагмент кода из страницы man-времени:

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

Должен быть лучший путь, чем эта воинственно небезопасная мерзость, не так ли? Право??

4b9b3361

Ответ 1

Требуется добавить немного подробнее здесь.

Если вы попробуете следующее:

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

вы заметите, что изменения в часовом поясе гарантируют, что:

  • mktime(localtime(t)) == t и
  • mktime(gmtime(t)) == t + timezone
    поэтому:
  • difftime(mktime(gmtime(t)), mktime(localtime(t))) == timezone
    (последняя является глобальной переменной, инициализированной либо tzset(), либо вызовом любой функции преобразования часового пояса).

Пример вывода выше:

$ 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

В этом смысле вы пытаетесь "сделать это назад" - time_t считается абсолютным в UN * X, то есть всегда относительно "EPOCH" (0:00 UTC, 01.01.1970).

Разница между UTC и текущим часовым поясом (последний вызов tzset()) всегда находится в глобальном external long timezone.

Это не избавит от уродства манипуляции с окружающей средой, но вы можете сэкономить усилия на прохождение mktime().

Ответ 2

Из tzfile (5), который документирует файлы в /usr/share/zoneinfo (в моей системе) с ужасной детализацией:

Кажется, что timezone использует tzfile внутренне, но glibc отказывается выставить его в пользовательское пространство. Это больше всего вероятно, потому что стандартизованный функции более полезны и переносится и фактически документируется Glibc.

Опять же, это, вероятно, не то, что вы ищете (например, API), но информация есть, и вы можете анализировать ее без особых проблем.

Ответ 3

Я действительно думал, что в чём-то есть что-то, но, похоже, забыл. Я знаю, что вы, вероятно, ищете прямой код C, но здесь лучшее, что у меня есть:

Я знаю, что у Python есть некоторое понятие часовых поясов через класс tzinfo - вы можете прочитать об этом в документации datetime. Вы можете посмотреть исходный код модуля (в tarball, он в модулях /datetime.c) - он, похоже, имеет некоторые документы, так что, возможно, вы можете получить что-то из этого.

Ответ 4

Как и в ответе Python, я могу показать вам, что R:

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> 

но R использует внутреннее представление, которое расширяет обычный struct tm --- см. R-2.9.1/src/main/datetime.c.

Тем не менее, это волосатая тема, и было бы неплохо, если бы это была стандартная библиотека. Поскольку вам не лучше всего использовать Boost Date_Time (пример)

Ответ 5

Проблема с gmtime, localtime и их вариантами - это зависимость от переменной окружения TZ. Функции времени сначала вызывают tzset (void), который считывает TZ для определения смещений DST и т.д. Если TZ не задана в пользовательской среде, (g) libc использует системный часовой пояс. Поэтому, если у вас есть локальная структура, например, "Европа/Париж", а ваша машина или среда установлена ​​в "Америка/Денвер", неправильное смещение будет применяться при конвертации в GMT. Все функции времени вызывают tzset (void), который считывает TZ для установки char * tzname [2], длинного часового пояса (разность, в секундах, от GMT) и int daylight (boolean for DST). Установка этих параметров напрямую не влияет, потому что tzset() перезапишет их при следующем вызове localtime и т.д.

Я столкнулся с той же проблемой, что и "igor" в исходном вопросе, а setenv работает, кажется, проблематично (re-entran?). Я решил еще раз посмотреть, могу ли я изменить tzset (void) на tzset (char *), чтобы явно установить вышеупомянутые переменные. Ну, конечно, это просто плохая идея... но, исследуя источник glibc и источник базы данных IANA TZ, я пришел к выводу, что подход setenv не так уж плох.

Во-первых, setenv только модифицирует процесс global 'char ** environ' (а не вызывающая оболочка, поэтому "реальный" TZ не влияет). И, во-вторых, glibc фактически ставит блокировку в setenv. Недостаток заключается в том, что вызовы setenv/tzset не являются атомарными, поэтому другой поток, возможно, может записываться в TZ перед исходным вызовом tzset потока. Но хорошо реализованное приложение, использующее потоки, должно все равно следить за этим.

Было бы здорово, если бы POSIX определил tzset, чтобы взять char * для поиска в обширной базе данных IANA TZ (и принять значение NULL, чтобы использовать "пользователь или система TZ/), но, если это не так, setenv кажется все в порядке.

Ответ 6

Почему вы не можете использовать gmtime_r()? После меня работали хорошо:

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