我正在尝试使用 Howard Hinnant 的日期库(https://github.com/HowardHinnant/date)将用户输入读取到日期时间对象中。我想使用这个库,因为它很现代,并且将包含在 C++20 中。
该程序应该能够接受 ISO 8601 格式的日期时间(YYYY-MM-DDTHH:MM:SS+HHMM
例如2020-07-07T18:30+0100
)以及DD-MM HH:MM
或形式的简单日期时间HH:MM
。在第二种情况下,应该假设缺失的信息将用当前日期/年份填写(时区将在稍后处理)。
这是我这样做的尝试。
using namespace date;
int main(int argc, char ** argv)
{
std::istringstream ss { argv[1] };
std::chrono::system_clock::time_point dt
{ std::chrono::system_clock::now() };
if ( date::from_stream(ss, "%F T %T %z", dt) ) {
std::cout << "Cond 1 entered\n";
}
else if ( ss.clear(), ss.seekg(0); date::from_stream(ss, "%d-%m %R", dt)) {
std::cout << "Cond 2 entered\n";
}
std::cout << dt << "\n";
}
Run Code Online (Sandbox Code Playgroud)
对于第一种格式,这按预期工作:
./a.out 2020-06-07T18:30:00+0200
Cond 1 entered
2020-06-07 16:30:00.000000000
Run Code Online (Sandbox Code Playgroud)
但是,第二种方法会返回一些奇怪的内容,具体取决于所使用的编译器。当使用 GCC 编译时-std=c++17/-std=c++2a
:
./a.out "07-08 15:00"
Cond 2 entered
1754-04-06 13:43:41.128654848
Run Code Online (Sandbox Code Playgroud)
编辑2:使用 LLVM 编译时-std=c++2a
:
./a.out "07-08 15:00"
Cond 2 entered
0000-08-07 15:00:00.000000
Run Code Online (Sandbox Code Playgroud)
这更接近我的预期。我宁愿不让行为依赖于所使用的编译器!
我真的对这里发生的事情感到困惑,而且我似乎无法理解文档的头或尾。我怎样才能date::from_stream
简单地覆盖时间和日期并保留其他所有内容?
编辑1:
为了清楚起见,我(错误地)期望当输入第二个条件时保留当前年份,因为time_point
对象是使用当前年份初始化的。例如,我希望第二次调用会像我的第二个示例中那样from_stream
留下对象。请参阅评论以获取更多信息。time_point
2020-08-07 15:00:33.803726000
编辑2:
添加了尝试使用不同编译器的结果。
好问题!!!
你做得不太正确,并且你在 date.h 中发现了一个错误!:-)
首先,我修复了您在这里遇到的错误。问题是我的not_a_year
in值是错误的from_stream
,而且这个错误已经隐藏在那里很多年了!非常感谢你帮我找到它!要更新,只需拉动 master 分支的尖端即可。
当您的程序使用带有参数的固定 date.h 运行时"07-08 15:00"
,它不会输入任何条件并打印出当前时间。
解释:
的语义from_stream(stream, fmt, x)
是,如果stream
没有包含足够的信息来完全指定x
using fmt
,则被stream.failbit
设置并且x
不被修改。并且"07-08 15:00"
没有完全指定system_clock::time_point
.
date.h 中的错误是 date.h 无法识别没有足够的信息来完全指定system_clock::time_point
,并且正在向其写入确定性垃圾。由于system_clock::time_point
( microseconds
vs nanoseconds
) 的精度不同,该垃圾碰巧在 LLVM/libc++ 和 gcc 上产生了两个不同的值。
修复错误后,解析会彻底失败,因此不会写入垃圾。
我确信您的下一个问题将是:
如何使第二个解析工作?
int main(int argc, char ** argv)
{
std::istringstream ss { argv[1] };
auto dt = std::chrono::system_clock::now();
ss >> date::parse("%FT%T%z", dt);
if (!ss.fail())
{
std::cout << "Cond 1 entered\n";
}
else
{
ss.clear();
ss.seekg(0);
date::month_day md;
std::chrono::minutes hm;
ss >> date::parse("%d-%m", md) >> date::parse(" %R", hm);
if (!ss.fail())
{
std::cout << "Cond 2 entered\n";
using namespace date;
auto y = year_month_day{floor<days>(dt)}.year();
dt = sys_days{y/md} + hm;
}
}
std::cout << dt << "\n";
}
Run Code Online (Sandbox Code Playgroud)
parse
第一个解析与您所拥有的一样,只是我切换了for的使用,from_stream
这是一个更高级别的 API。这对于第一次解析并不重要,但使第二次解析更整洁。
对于第二次解析,您需要解析两项:
month_day
然后将这两个元素与电流结合起来year
以产生所需的time_point
。
现在,每个解析都完全指定了它从流中解析的变量。
您最初犯的错误是想象在 的引擎盖下有一个“年份字段” system_clock::time_point
。实际上,这个数据结构只不过是自 1970-01-01 00:00:00 UTC 以来的微秒或纳秒(或其他)计数。所以第二个解析必须:
time_point
into 字段以获取当前年份,然后time_point
.