Неправильное преобразование в временную метку unix

У меня есть функция, которую я написал (если есть хорошая стандартная замена, дайте мне знать...)

time_t get_unix_time(string time_str) {
    time_t loctime;
    time(&loctime);

    struct tm *given_time;
    time_str = time_str.substr(0, time_str.find_first_of('.'));

    replace(time_str.begin(), time_str.end(), ':', ',');
    replace(time_str.begin(), time_str.end(), '-', ',');
    replace(time_str.begin(), time_str.end(), '/', ',');
    replace(time_str.begin(), time_str.end(), ' ', ',');

    given_time = localtime(&loctime);
    vector<string> trecord = split_string(time_str, ',');

    given_time->tm_year = atoi(trecord.at(0).c_str()) - 1900;
    given_time->tm_mon  = atoi(trecord.at(1).c_str()) - 1;
    given_time->tm_mday = atoi(trecord.at(2).c_str());
    given_time->tm_hour = atoi(trecord.at(3).c_str());
    given_time->tm_min  = atoi(trecord.at(4).c_str());
    given_time->tm_sec  = atoi(trecord.at(5).c_str());

    return mktime(given_time);
}

Входные данные (time_str) функции имеют формат 1970-01-01 00:00:00.0. Функция split_string() разбивает строку time_str на вектор, содержащий:

{ 1970, 01, 01, 00, 00, 00 }

который используется для заполнения структуры заданного_времени.

Я написал функцию для ее проверки и передал именно этот ввод (начало эпохи). Однако время, которое он мне возвращает, составляет 21 600, то есть 1970-01-01 06:00:00 или UTC+6. Ожидаемый результат: 0 (начало эпохи).

Примечание: я нахожусь в часовом поясе Центрального региона США, то есть UTC – 6. В полночь 1 января 1970 года по центральному поясному времени время по UTC будет 1 января 1970 года в 06:00:00.

Есть ли в моей функции что-то, что делает ее специфичной для моего часового пояса? Я делаю что-то неправильно в этой функции, или я могу сделать что-то другое, чтобы сделать ее независимой от зоны или, по крайней мере, всегда UTC.


person Sagar    schedule 13.01.2011    source источник
comment
Кроме того, я не могу использовать библиотеку boost или любые другие дополнительные библиотеки. Я могу использовать только стандартные вещи С++.   -  person Sagar    schedule 13.01.2011
comment
Обработка часовых поясов в библиотеке C++ чертовски неясна. Если вы запустили свою программу с переменной среды TZ, установленной на значение UTC, этот фрагмент кода будет работать.   -  person Omnifarious    schedule 13.01.2011
comment
mktime интерпретирует данное время как местное время. Таким образом, если вы укажете 1970-01-01 00:00:00, это будет ваше местное время, поэтому mktime вернет время UTC-0, то есть 1970-01-01 06:00:00.   -  person Robert    schedule 13.01.2011


Ответы (6)


Если вы используете glibc, у вас есть функция timegm, которая является версией mktime. который всегда интерпретирует время так, как будто оно находится в часовом поясе GMT. К сожалению, в документации для этой функции в основном указано, что иначе ее нельзя реализовать с помощью стандартных библиотечных вызовов. Так что вам не повезло, если вы не имеете его.

person Omnifarious    schedule 13.01.2011
comment
Спасибо! Это сработало отлично. Я просматривал справочную страницу gmtime и нигде ее не видел. - person Sagar; 13.01.2011

mktime занимает время в местном часовом поясе. Итак, если вы передадите его 1970-01-01 00:00:00 по местному времени, он вернет 1970-01-01 06:00:00 UTC, как и должно быть. .

В качестве альтернативы вы можете вызвать timegm, если вы используете glibc. Если вы не используете glibc, вы временно меняете местное время на UTC при вызове mktime, изменяя переменную окружения TZ, как описано на справочной странице timegm:

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

Also, your call to localtime is unnecessary, and you probably ought to set given_time->tm_isdst, to avoid possible daylight savings time issues.

person Josh Kelley    schedule 13.01.2011
comment
Спасибо. Я проверил timegm, и он делает то, что я хочу, но я обязательно проверю и установлю проблему dst. - person Sagar; 13.01.2011

Возможно, вам следует использовать gmtime вместо time, чтобы избавиться от проблем с часовым поясом.

Редактировать: я не очень понимаю, почему вы заполняете структуру текущим временем, а затем перезаписываете все ее компоненты. Почему бы просто не:

time_t get_unix_time(const string& time_str)
{
    vector<string> trecord = split_string(time_str, ',');

    tm given_time;
    given_time.tm_year = atoi(trecord.at(0).c_str()) - 1900;
    given_time.tm_mon  = atoi(trecord.at(1).c_str()) - 1;
    given_time.tm_mday = atoi(trecord.at(2).c_str());
    given_time.tm_hour = atoi(trecord.at(3).c_str());
    given_time.tm_min  = atoi(trecord.at(4).c_str());
    given_time.tm_sec  = atoi(trecord.at(5).c_str());

    return mktime(&given_time);
}

Еще одно изменение:

Тьфу, mktime тоже считает местное время. Я не совсем уверен, как вы можете обойти это, кроме как установить локаль часового пояса на UTC.

person Lightness Races in Orbit    schedule 13.01.2011
comment
- замена текущего времени и замена на то, что мне нужно, на самом деле не проблема. Я попробовал вашу функцию, и она все равно дает мне 21600 вместо 0. 21600 = UTC + 6. - person Sagar; 13.01.2011
comment
@Sagar: Тьфу, mktime тоже учитывает местное время. Я не совсем уверен, как вы можете обойти это, кроме как установить локаль часового пояса на UTC. Ты сделал это? Или вы просто вставили код, больше ничего не меняли и надеялись на лучшее? - person Lightness Races in Orbit; 13.01.2011
comment
К сожалению, хотя я могу изменить часовой пояс на своем компьютере для разработки, это невозможно на клиентском компьютере. - person Sagar; 13.01.2011
comment
@Сагар: Не так. У вас есть контроль над переменными среды, влияющими на процесс. Я считаю, что команда setenv. - person Lightness Races in Orbit; 13.01.2011

Просто избегайте этих неудобных функций и посчитайте сами. POSIX указывает, что time_t — это арифметический тип в виде секунд с «эпохи» (1970-01-01 00:00:00 по Гринвичу) без какой-либо ерунды дополнительной секунды (все дни составляют ровно 86400 календарных секунд). , которые незначительно отличаются от секунд СИ), поэтому, если не считать небольшой логики високосного года, вычисления чрезвычайно просты.

Вычисления календаря, подобные этому, являются стандартным вводным упражнением по программированию, поэтому я уверен, что вы сможете решить его или найти решения в Интернете.

Кроме того, возможно, причина, по которой ISO C и POSIX опускают такую ​​функцию, заключается в том, что, в отличие от преобразований, включающих часовые пояса, которые могут быть сколь угодно сложными и которые только библиотека хоста может надежно и последовательно выполнять в различных приложениях, преобразования GMT являются чистой арифметикой без каких-либо внешних параметры.

person R.. GitHub STOP HELPING ICE    schedule 13.01.2011
comment
Я думал сделать это, если не смог найти решение этой проблемы, но решил, что если такая функция уже существует, почему бы не попробовать поискать... это избавит меня от дополнительной работы по тестированию. - person Sagar; 13.01.2011
comment
Кстати, вот ссылка с формулой для другого направления: pubs .opengroup.org/onlinepubs/9699919799/basedefs/ - person R.. GitHub STOP HELPING ICE; 14.01.2011

Когда вы вызываете mktime, он интерпретирует параметр как местное время. Вы также использовали такие функции, как «местное время», которые кажутся бесполезными, и я думаю, что вы можете отказаться от них.

person Simone-Cu    schedule 13.01.2011

Вы можете написать обертку вокруг strptime для анализа.

struct tm given_time;

strptime(time_str.c_str(), "%Y-%m-%d %H:%M:%S", &given_time);

return mktime(&given_time);

Ответ @ Джоша Келли подробно объясняет проблему часового пояса.

person Daniel Gallagher    schedule 13.01.2011