如何使用C++解析日期时间并将其转换为 RFC 3339

How do I parse and convert DateTime to the RFC 3339 with C++

本文关键字:转换 3339 RFC 时间 C++ 何使用 日期      更新时间:2023-10-16

如何将等效的字符串 RFC 3339 解析为任何类型的常规 DateTime 结构?RFC 3339 日期时间格式用于许多规范,例如 Atom 联合格式。

以下是 ATOM(RFC 3339( 格式的日期时间示例:

2005-08-15T15:52:01+04:00

这是一个完整但不幸的是不令人满意但可移植的程序,它是最新版本的libc ++,libstdc++,VS实现,它将字符串解析为您显示的格式为std::chrono::system_clock::time_point

我找不到你提到的DateTime。 但是std::chrono::system_clock::time_point"日期时间"结构。 std::chrono::system_clock::time_point是自某个未指定纪元以来的某个持续时间(秒、微秒、纳秒等(的计数。 您可以查询std::chrono::system_clock::time_point以了解其持续时间。 事实证明,自1970年新年以来,每次实施都测量时间,忽略了闰秒。

#include <chrono>
#include <iostream>
#include <limits>
#include <locale>
#include <sstream>
template <class Int>
// constexpr
Int
days_from_civil(Int y, unsigned m, unsigned d) noexcept
{
    static_assert(std::numeric_limits<unsigned>::digits >= 18,
             "This algorithm has not been ported to a 16 bit unsigned integer");
    static_assert(std::numeric_limits<Int>::digits >= 20,
             "This algorithm has not been ported to a 16 bit signed integer");
    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;
}
using days = std::chrono::duration
    <int, std::ratio_multiply<std::ratio<24>, std::chrono::hours::period>>;
namespace std
{
namespace chrono
{
template<class charT, class traits>
std::basic_istream<charT,traits>&
operator >>(std::basic_istream<charT,traits>& is, system_clock::time_point& item)
{
    typename std::basic_istream<charT,traits>::sentry ok(is);
    if (ok)
    {
        std::ios_base::iostate err = std::ios_base::goodbit;
        try
        {
            const std::time_get<charT>& tg = std::use_facet<std::time_get<charT> >
                                                           (is.getloc());
            std::tm t = {};
            const charT pattern[] = "%Y-%m-%dT%H:%M:%S";
            tg.get(is, 0, is, err, &t, begin(pattern), end(pattern)-1);
            if (err == std::ios_base::goodbit)
            {
                charT sign = {};
                is.get(sign);
                err = is.rdstate();
                if (err == std::ios_base::goodbit)
                {
                    if (sign == charT('+') || sign == charT('-'))
                    {
                        std::tm t2 = {};
                        const charT pattern2[] = "%H:%M";
                        tg.get(is, 0, is, err, &t2, begin(pattern2), end(pattern2)-1);
                        if (!(err & std::ios_base::failbit))
                        {
                            auto offset = (sign == charT('+') ? 1 : -1) *
                                          (hours{t2.tm_hour} + minutes{t2.tm_min});
                            item = system_clock::time_point{
                                days{days_from_civil(t.tm_year+1900, t.tm_mon+1,
                                                     t.tm_mday)} +
                                hours{t.tm_hour} + minutes{t.tm_min} + seconds{t.tm_sec} -
                                offset};
                        }
                        else
                        {
                            err |= ios_base::failbit;
                        }
                    }
                    else
                    {
                        err |= ios_base::failbit;
                    }
                }
                else
                {
                    err |= ios_base::failbit;
                }
            }
            else
            {
                err |= ios_base::failbit;
            }
        }
        catch (...)
        {
            err |= std::ios_base::badbit | std::ios_base::failbit;
        }
        is.setstate(err);
    }
    return is;
}
}  // namespace chrono
}  // namespace std
int
main()
{
    std::istringstream infile("2005-08-15T15:52:01+04:00");
    std::chrono::system_clock::time_point tp;
    infile >> tp;
    std::cout << tp.time_since_epoch().count() << 'n';
}

这已经针对libc++,libstdc++-5.0和VS-2015进行了测试,并分别产生:

1124106721000000
1124106721000000000
11241067210000000

在libc++上,这是自1970年新年以来的微秒计数,忽略了闰秒。 在libstdc++-5.0上,它是纳秒的计数,而在VS-2015上,它是100纳秒的计数。

此解决方案的问题在于它涉及将函数插入 std 命名空间。 将来,C++委员会可能会决定将相同的函数插入到同一命名空间中,这可能会使您的代码无效。

这段代码的另一个问题是它非常复杂。 遗憾的是,该标准没有提供更简单的解决方案。

此代码的另一个问题是它不使用 C 标准中记录的更简单的"%F"、"%T"和"%z"解析模式(尽管记录为格式化模式(。 我通过实验发现它们的使用不是便携式的。

此代码的另一个问题是它将需要 gcc-5.0。 如果您运行的是 gcc-4.9,那么您就不走运了。 你必须自己解析东西。 在VS-2015之前,我无法测试VS实现。 libc++应该没问题(尽管即使libc++也不支持"%z"(。

如果需要,您可以通过此处的公式将std::chrono::system_clock::time_point转换回"分解"结构。但是,如果这是您的最终目标,那么修改上面的代码以直接解析到您的"分解"结构中而不是解析为std::chrono::system_clock::time_point会更有效。

免责声明:仅经过非常轻微的测试。 我很高兴用任何错误报告更新这个答案。

更新

自从我第一次给出这个答案以来的几年里,我编写了一个库,它用更简洁的语法完成了上面的所有计算。

#include "date/date.h"
#include <iostream>
#include <sstream>
int
main()
{
    using namespace date;
    std::istringstream infile{"2005-08-15T15:52:01+04:00"};
    sys_seconds tp;  // This is a system_clock time_point with seconds precision
    infile >> parse("%FT%T%Ez", tp);
    std::cout << tp.time_since_epoch() << " is " << tp << 'n';
}

你可以在这里找到"date.h"。 它是一个免费的、开源的、仅标题的库。 在此链接中,还有指向完整文档的链接,"date.h"甚至是视频教程。 尽管视频教程是在实现parse功能之前创建的。

上述程序的输出为:

1124106721s is 2005-08-15 11:52:01

它给出了自纪元 (1970-01-01 00:00:00 UTC( 以来的秒数,以及以 UTC 为单位的日期/时间(考虑到偏移量(。

如果您需要计算自纪元以来的闰秒,则同一 GitHub 链接中的另一个库可用,但不仅仅是标头,需要少量安装。 但是使用它是对上述程序的简单修改:

#include "date/tz.h"
#include <iostream>
#include <sstream>
int
main()
{
    using namespace date;
    std::istringstream infile{"2005-08-15T15:52:01+04:00"};
    utc_seconds tp;  // This is a utc_clock time_point with seconds precision
    infile >> parse("%FT%T%Ez", tp);
    std::cout << tp.time_since_epoch() << " is " << tp << 'n';
}

输出现在为:

1124106743s is 2005-08-15 11:52:01

代码的区别在于现在包含"tz.h"而不是"date.h",并且utc_seconds被解析而不是sys_secondsutc_seconds仍然是一个std::chrono::time_point,但现在基于一个闰秒感知时钟。 程序输出相同的日期/时间,但自纪元以来的秒数现在多 22 秒,因为这是在 1970-01-01 和 2005-08-15 之间插入的闰秒数。