如何使用 C++ 解析 DateTime 并将其转换为 RFC 3339
How do I parse and convert DateTime to the RFC 3339 with C++
如何解析等效字符串 RFC 3339 to any type of regular DateTime structure? The RFC 3339 date-time format is used in a number of specifications such as the Atom Syndication Format。
以下是 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
是一个"DateTime"结构。 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”)。
如果需要,您可以通过 formulas here 将 std::chrono::system_clock::time_point
转换回 "broken down" 结构。但是,如果这是您的最终目标,那么修改上面的代码以直接解析到您的 "broken down" 结构而不是解析到 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"
here. It is a free, open-source, header only library. At the this link there are also links to full documentation, and for "date.h"
even a video tutorial. Though the video tutorial 是在执行 parse
函数之前创建的。
以上程序的输出为:
1124106721s is 2005-08-15 11:52:01
它给出自纪元(1970-01-01 00:00:00 UTC)以来的秒数,以及 UTC 中的 date/time(考虑到偏移量)。
如果您需要计算纪元以来的闰秒,another library at this same GitHub link is available, but is not header only and requires a small amount of installation。但是使用它是对上面程序的简单修改:
#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_seconds
。 utc_seconds
仍然是 std::chrono::time_point
,但现在基于闰秒感知时钟。该程序输出相同的 date/time,但自纪元以来的秒数现在多了 22 秒,因为这是在 1970-01-01 和 2005-08-15 之间插入的闰秒数。
如何解析等效字符串 RFC 3339 to any type of regular DateTime structure? The RFC 3339 date-time format is used in a number of specifications such as the Atom Syndication Format。
以下是 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
是一个"DateTime"结构。 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”)。
如果需要,您可以通过 formulas here 将 std::chrono::system_clock::time_point
转换回 "broken down" 结构。但是,如果这是您的最终目标,那么修改上面的代码以直接解析到您的 "broken down" 结构而不是解析到 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"
here. It is a free, open-source, header only library. At the this link there are also links to full documentation, and for "date.h"
even a video tutorial. Though the video tutorial 是在执行 parse
函数之前创建的。
以上程序的输出为:
1124106721s is 2005-08-15 11:52:01
它给出自纪元(1970-01-01 00:00:00 UTC)以来的秒数,以及 UTC 中的 date/time(考虑到偏移量)。
如果您需要计算纪元以来的闰秒,another library at this same GitHub link is available, but is not header only and requires a small amount of installation。但是使用它是对上面程序的简单修改:
#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_seconds
。 utc_seconds
仍然是 std::chrono::time_point
,但现在基于闰秒感知时钟。该程序输出相同的 date/time,但自纪元以来的秒数现在多了 22 秒,因为这是在 1970-01-01 和 2005-08-15 之间插入的闰秒数。