Преобразование часового пояса C API на Linux, кто-нибудь? - PullRequest
26 голосов
/ 03 августа 2009

Я ищу что-то, что, как я предполагал, было бы очень простым - учитывая местное время Unix в определенном часовом поясе (указан в виде строки, например, "America / New_York") - обратите внимание, что не мой по местному времени), получите соответствующее значение времени в GMT. Т.е. что-то вроде

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

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

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

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

Ответы [ 6 ]

6 голосов
/ 16 мая 2011

Хотелось добавить сюда немного больше деталей.

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

#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 global.

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

4 голосов
/ 04 августа 2009

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

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

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

1 голос
/ 14 апреля 2013

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

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

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

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

1 голос
/ 04 августа 2009

Как и в ответе на 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 (пример)

1 голос
/ 03 августа 2009

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

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

0 голосов
/ 16 мая 2011

Почему вы не можете использовать 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;
}
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...