Время чтения из строки как GMT в С++

Я пишу текущее время по Гринвичу в виде строки следующим образом:

  const std::time_t now = std::time(nullptr);
  std::stringstream ss;
  ss << std::put_time(std::gmtime(&now), "%Y-%m-%d %H:%M:%S");

Позже я хочу выполнить обратную операцию, прочитав время из строкового потока как GMT и сравнив его с текущей меткой времени:

std::tm tm = {};
ssTimestamp >> std::get_time(&tm, "%Y-%m-%d %H:%M:%S");
const std::time_t&& time = std::mktime(&tm);
const double timestampDiff((std::difftime(std::time(nullptr), time)));

В приведенном ниже коде чего-то не хватает, потому что декодированное время никогда не преобразуется в GMT, поэтому я получаю разницу во времени в 1 час из-за моего местного часового пояса.

PS: можно использовать только стандартные библиотеки и нельзя изменить формат строки даты


person Othman Benchekroun    schedule 04.07.2019    source источник
comment
В вашей строке нет информации о часовом поясе, поэтому mktime использует часовой пояс по умолчанию (местный). Решением было бы добавить информацию о часовом поясе в строку (%Z).   -  person Gwen    schedule 04.07.2019
comment
Я не могу изменить формат строки, есть ли способ указать часовой пояс при декодировании?   -  person Othman Benchekroun    schedule 04.07.2019
comment
Затем измените строку непосредственно перед вызовом get_time. Смотрите ответ ниже.   -  person Gwen    schedule 04.07.2019


Ответы (3)


В спецификации C++20 есть удобный способ сделать это:

using namespace std::chrono;
sys_seconds tp;
ssTimestamp >> parse("%Y-%m-%d %H:%M:%S", tp);
std::time_t time = system_clock::to_time_t(tp);

Ни один поставщик еще не реализовал эту часть C++20, но есть пример реализации здесь, в namespace date. .

В C++ до C++20 нет поддержки библиотек для выполнения этой операции.

Лучшее, что вы можете сделать, используя только стандартные библиотеки, — это проанализировать поля в tm с помощью std::get_time (как показывает ваш вопрос), а затем преобразовать эту структуру {y, m, d, h, M, s} в time_t, используя вашу собственную математику и предположение (что в целом верно), что std::time_t — это время Unix с точностью до секунд.

Вот коллекция общедоступных календарных алгоритмов, которая поможет вам в этом. Это не сторонняя библиотека. Это кулинарная книга для написания собственной библиотеки дат.

Например:

#include <ctime>

std::time_t
to_time_t(std::tm const& tm)
{
    int y = tm.tm_year + 1900;
    unsigned m = tm.tm_mon + 1;
    unsigned d = tm.tm_mday;
    y -= m <= 2;
    const int era = (y >= 0 ? y : y-399) / 400;
    const unsigned yoe = static_cast<unsigned>(y - era * 400);      // [0, 399]
    const unsigned doy = (153*(m + (m > 2 ? -3 : 9)) + 2)/5 + d-1;  // [0, 365]
    const unsigned doe = yoe * 365 + yoe/4 - yoe/100 + doy;         // [0, 146096]
    return (era * 146097 + static_cast<int>(doe) - 719468)*86400 +
           tm.tm_hour*3600 + tm.tm_min*60 + tm.tm_sec;
}

Ссылка выше содержит очень подробное описание этого алгоритма и модульные тесты, чтобы убедиться, что он работает в диапазоне +/- миллионов лет.

Вышеупомянутый to_time_t по сути является переносимой версией timegm, поставляемой на платформах Linux и bsd. Эта функция также называется _mkgmtime в Windows.

person Howard Hinnant    schedule 04.07.2019

Структура tm не хранит информацию о часовом поясе, а mktime по умолчанию использует местный часовой пояс. Следуя этой теме, лучше всего использовать:

#include "time.h" 
timestamp = mktime(&tm) - timezone; //or _timezone

если timezone или _timezone доступны для вашего компилятора. Комментарий в связанном ответе предупреждает, что могут возникнуть проблемы с переходом на летнее время, но он не должен применяться к GMT.

person Gwen    schedule 04.07.2019
comment
Спасибо, Гвен, но std::get_time, похоже, не имеет значения для часового пояса. Проверьте ideone.com/3Noc1R. - person Othman Benchekroun; 04.07.2019
comment
Часовой пояс и %Z не упоминаются в get_time en.cppreference.com/w/cpp /io/manip/get_time - person Othman Benchekroun; 04.07.2019
comment
Ты прав, мой плохой. На самом деле tm даже не хранит информацию о часовом поясе (в стандартном определении C). См. измененный ответ. - person Gwen; 04.07.2019

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

std::time_t from_time_str(std::string time_str) {
    std::stringstream ss;
    std::tm tm = {};
    ss << time_str;
    ss >> std::get_time(&tm, "%Y-%m-%d %H:%M:%S");
    std::time_t t = std::mktime(&tm);
    std::tm* gm_tm = std::gmtime(&t);
    gm_tm->tm_isdst = false;
    std::time_t gm_t = std::mktime(gm_tm);
    std::time_t gm_offset = (gm_t - t);
    std::time_t real_gm_t = t - gm_offset;
    return real_gm_t;
}

Идея состоит в том, чтобы использовать функцию gmtime, чтобы получить gmtime метки времени, чтобы мы могли вычислить смещение часового пояса целевого компьютера. Затем мы вычитаем смещение, чтобы получить время GM.

Обратите внимание, что строка gm_tm->tm_isdst = false; необходима для любого часового пояса, в котором включен переход на летнее время, в противном случае время gmtime рассчитывается со смещением перехода на летнее время (выход на 1 час), и это не должно быть желаемым эффектом при расчете фактического времени GM.

person Kenneth Tang    schedule 18.04.2021