我有一个具有以下格式的字符串:
2010-11-04T23:23:01Z
Z 表示时间为 UTC。
我宁愿将其存储为纪元时间,以便于比较。
推荐的方法是什么?
目前(经过快速搜索后)最简单的算法是:
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.
使用 C++11 功能,我们现在可以使用流来解析时间:
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;
}
您可以使用诸如 strptime 之类的函数将字符串转换为
struct tm
,而不是手动解析它。
这不是一个精确的重复,但我敢打赌,你会发现here中@Cubbi的答案很有用。这特别假设 UTC 输入。
Boost 还支持通过
boost::posix_time::from_iso_string
从 ISO 8601 直接转换,调用 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
这正是您想要的(即 UTC 时间的 mktime)。
这是我的解决方案,我被迫只接受“Zulu”(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 "date/date.h"
#include <chrono>
#include <iostream>
#include <sstream>
int
main()
{
std::istringstream is("2010-11-04T23:23:01Z");
is.exceptions(std::ios::failbit);
date::sys_seconds tp;
is >> date::parse("%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()
。
在 C++20 中,您将不再需要这个库,因为它成为
<chrono>
: 的一部分
#include <chrono>
#include <iostream>
#include <sstream>
int
main()
{
std::istringstream is("2010-11-04T23:23:01Z");
is.exceptions(std::ios::failbit);
std::chrono::sys_seconds tp;
is >> std::chrono::parse("%FT%TZ", tp);
std::cout << "seconds from epoch is " << tp.time_since_epoch().count() << "s\n";
}
最新版本的MSVC已经有了它。它将在 gcc-14 中发布。 LLVM 正在实施中。
boost::date_time
并为您的字符串编写一个小型手动解析器(可能基于正则表达式)。
这里的问题是 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;
}