16

我有一个格式如下的字符串:

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.
4

10 回答 10

12

使用 C++11 功能,我们现在可以使用流来解析时间:

iomanipstd::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;
}
于 2016-06-04T03:07:36.663 回答
6

这是 ISO8601 格式。您可以使用strptime函数来解析它的%FT%T%z参数。它不是 C++ 标准的一部分,尽管您可以使用它的开源实现(例如this)。

于 2010-11-09T19:52:05.693 回答
5

您可以使用诸如strptime 之类的函数将字符串转换为 a struct tm,而不是手动解析它。

于 2010-11-09T19:46:10.810 回答
3

这不是一个精确的重复,但我敢打赌,你会发现 @Cubbi 的答案从这里很有用。这特别假设 UTC 输入。

boost::posix_time::from_iso_stringBoost 还支持通过调用从 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");
}
于 2010-11-09T19:51:40.313 回答
3

这里的问题是 mktime 使用本地时间而不是 UTC 时间。

Linux 提供timegm了您想要的(即 mktime 为 UTC 时间)。

这是我的解决方案,我被迫只接受“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;
}
于 2016-06-04T00:58:21.967 回答
2

老问题的新答案。新答案的基本原理:如果您想使用<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().

于 2016-06-04T02:08:31.140 回答
1

您可以使用boost::date_time并为您的字符串编写一个小型手动解析器(可能基于正则表达式)

于 2010-11-09T19:44:58.903 回答
1

这里的问题是 mktime 使用本地时间而不是 UTC 时间。

仅计算 UTC 和本地时间之间的时差,然后将其添加到返回的值中mktime如何?

time_t local = time(NULL),
       utc   = mktime(gmtime(&local));
int    diff  = utc - local;
于 2010-11-09T19:57:55.643 回答
1

有什么问题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
于 2010-11-09T19:58:13.630 回答
1

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;
}
于 2010-11-12T01:50:37.233 回答