С++ Преобразование временной строки в секундах от эпохи
У меня есть строка со следующим форматом:
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.
Ответы
Ответ 1
Используя функциональность С++ 11, мы можем теперь использовать потоки для разбора времени:
Iomanip std::get_time
преобразует строку на основе набора параметров формата и преобразует их в объект struct tz
.
Затем вы можете использовать std::mktime()
чтобы преобразовать это значение в эпоху.
#include <iostream>
#include <sstream>
#include <locale>
#include <iomanip>
int main()
{
std::tm t = {};
std::istringstream ss("2010-11-04T23:23:01Z");
if (ss >> std::get_time(&t, "%Y-%m-%dT%H:%M:%S"))
{
std::cout << std::put_time(&t, "%c") << "\n"
<< std::mktime(&t) << "\n";
}
else
{
std::cout << "Parse failed\n";
}
return 0;
}
Ответ 2
Это формат ISO8601. Вы можете использовать функцию strptime
, чтобы проанализировать ее с помощью аргумента %FT%T%z
. Он не является частью С++ Standard, хотя вы можете использовать его с открытым исходным кодом (this, например).
Ответ 3
Вы можете использовать такую функцию, как strptime, чтобы преобразовать строку в struct tm
, вместо ее разбора вручную.
Ответ 4
Это не точный дубликат, но вы найдете @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");
}
Ответ 5
Проблема в том, что 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;
}
Ответ 6
Новый ответ на старый вопрос. Обоснование для нового ответа: если вы хотите использовать типы <chrono>
для решения такой проблемы.
В дополнение к С++ 11/С++ 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()
.
Ответ 7
Вы можете использовать boost::date_time
и написать небольшой ручной синтаксический анализатор (возможно, regexp-based) для ваших строк.
Ответ 8
Проблема в том, что mktime использует локальное время, а не время UTC.
Как просто вычислить разницу во времени между временем по Гринвичу и местным временем, а затем добавить его к значению, возвращаемому mktime
?
time_t local = time(NULL),
utc = mktime(gmtime(&local));
int diff = utc - local;
Ответ 9
Что случилось с 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
Ответ 10
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;
}