C++ преобразование временной строки в секунды из эпохи

У меня есть строка в следующем формате:

2010-11-04T23:23:01Z

Z указывает, что время UTC.
Я бы предпочел сохранить это как эпохальное время, чтобы упростить сравнение.

каков рекомендуемый метод для этого?

В настоящее время (после поиска quck) упрощенный алгоритм:

1: <Convert string to struct_tm: by manually parsing string>
2: Use mktime() to convert struct_tm to epoch time.

// Problem here is that mktime uses local time not UTC time.

10 ответов


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


используя функциональность C++11, теперь мы можем использовать потоки для анализа времени:

в iomanip std::get_time преобразует строку на основе набора параметров формата и преобразует их в


вы можете использовать такую функцию, как strptime преобразовать строку struct tm, вместо разбора вручную.


это не точный dup, но вы найдете ответ @Cubbi от здесь полезно, я думаю. Это специально предполагает ввод UTC.

Boost также поддерживает прямое преобразование из ISO 8601 через boost::posix_time::from_iso_string которых звонки boost::date_time::parse_iso_time, здесь снова вы просто очистите трейлинг ' Z ' и обработаете TZ как неявный UTC.

#include <iostream>
#include <boost/date_time.hpp>

namespace bt = boost::posix_time;

const std::locale formats[] = {
std::locale(std::locale::classic(),new bt::time_input_facet("%Y-%m-%d %H:%M:%S")),
std::locale(std::locale::classic(),new bt::time_input_facet("%Y/%m/%d %H:%M:%S")),
std::locale(std::locale::classic(),new bt::time_input_facet("%d.%m.%Y %H:%M:%S")),
std::locale(std::locale::classic(),new bt::time_input_facet("%Y-%m-%d"))};
const size_t formats_n = sizeof(formats)/sizeof(formats[0]);

std::time_t pt_to_time_t(const bt::ptime& pt)
{
    bt::ptime timet_start(boost::gregorian::date(1970,1,1));
    bt::time_duration diff = pt - timet_start;
    return diff.ticks()/bt::time_duration::rep_type::ticks_per_second;

}
void seconds_from_epoch(const std::string& s)
{
    bt::ptime pt;
    for(size_t i=0; i<formats_n; ++i)
    {
        std::istringstream is(s);
        is.imbue(formats[i]);
        is >> pt;
        if(pt != bt::ptime()) break;
    }
    std::cout << " ptime is " << pt << '\n';
    std::cout << " seconds from epoch are " << pt_to_time_t(pt) << '\n';
}
int main()
{
    seconds_from_epoch("2004-03-21 12:45:33");
    seconds_from_epoch("2004/03/21 12:45:33");
    seconds_from_epoch("23.09.2004 04:12:21");
    seconds_from_epoch("2003-02-11");
}

проблема здесь в том, что mktime использует местное время, а не время UTC.

Linux предоставляет timegm что вы хотите (т. е. mktime для времени UTC).

вот мое решение, которое я заставил принять только "Зулу" (часовой пояс Z). Отмечать это strptime на самом деле, похоже, не разбирает часовой пояс правильно, даже хотя glib, похоже, имеет некоторую поддержку для этого. Вот почему я просто бросаю исключение, если строка не заканчивается на "Z".

static double EpochTime(const std::string& iso8601Time)
{
    struct tm t;
    if (iso8601Time.back() != 'Z') throw PBException("Non Zulu 8601 timezone not supported");
    char* ptr = strptime(iso8601Time.c_str(), "%FT%T", &t);
    if( ptr == nullptr)
    {
        throw PBException("strptime failed, can't parse " + iso8601Time);
    }
    double t2 = timegm(&t); // UTC
    if (*ptr)
    {
        double fraction = atof(ptr);
        t2 += fraction;
    }
    return t2;
}

новый ответ на старый вопрос. Обоснование нового ответа: в случае, если вы хотите использовать <chrono> типы для решения такой проблемы.

в дополнение к C++11 / C++14 вам понадобится это бесплатная библиотека даты/времени с открытым исходным кодом:

#include "tz.h"
#include <iostream>
#include <sstream>

int
main()
{
    std::istringstream is("2010-11-04T23:23:01Z");
    is.exceptions(std::ios::failbit);
    date::sys_seconds tp;
    date::parse(is, "%FT%TZ", tp);
    std::cout << "seconds from epoch is " << tp.time_since_epoch().count() << "s\n";
}

программа мероприятия:

seconds from epoch is 1288912981s

если синтаксический анализ не удается каким-либо образом, будет создано исключение. Если вы не хотите бросать исключения, не is.exceptions(std::ios::failbit);, но вместо того, чтобы проверить is.fail().


можно использовать boost::date_time и напишите небольшой ручной парсер (вероятно, на основе regexp) ваших строк.


проблема здесь в том, что mktime использует местное время, а не время UTC.

как насчет просто вычисления разницы во времени между UTC и локальным временем, а затем добавления его к значению, возвращаемому mktime?

time_t local = time(NULL),
       utc   = mktime(gmtime(&local));
int    diff  = utc - local;

что с strptime() ?

и в Linux вы даже получаете поле "секунды к востоку от UTC", освобождающее вас от необходимости анализировать:

#define _XOPEN_SOURCE
#include <iostream>
#include <time.h>

int main(void) {

    const char *timestr = "2010-11-04T23:23:01Z";

    struct tm t;
    strptime(timestr, "%Y-%m-%dT%H:%M:%SZ", &t);

    char buf[128];
    strftime(buf, sizeof(buf), "%d %b %Y %H:%M:%S", &t);

    std::cout << timestr << " -> " << buf << std::endl;

    std::cout << "Seconds east of UTC " << t.tm_gmtoff << std::endl;
}   

что для меня дает

/tmp$ g++ -o my my.cpp 
/tmp$ ./my
2010-11-04T23:23:01Z -> 04 Nov 2010 23:23:01
Seconds east of UTC 140085769590024

X / Open обеспечивает глобальный timezone переменная, которая указывает количество секунд, что местное время отстает от UTC. Вы можете использовать это для настройки вывода mktime():

#define _XOPEN_SOURCE
#include <stdio.h>
#include <time.h>

/* 2010-11-04T23:23:01Z */
time_t zulu_time(const char *time_str)
{
    struct tm tm = { 0 };

    if (!strptime(time_str, "%Y-%m-%dT%H:%M:%SZ", &tm))
        return (time_t)-1;

    return mktime(&tm) - timezone;
}