使用C++的日期库读取时间

J. *_*che 5 c++ date

我正在尝试使用 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_point2020-08-07 15:00:33.803726000

编辑2:

添加了尝试使用不同编译器的结果。

How*_*ant 6

好问题!!!

你做得不太正确,并且你在 date.h 中发现了一个错误!:-)

首先,我修复了您在这里遇到的错误。问题是我的not_a_yearin值是错误的from_stream,而且这个错误已经隐藏在那里很多年了!非常感谢你帮我找到它!要更新,只需拉动 master 分支的尖端即可。

当您的程序使用带有参数的固定 date.h 运行时"07-08 15:00",它不会输入任何条件并打印出当前时间。

解释:

的语义from_stream(stream, fmt, x)是,如果stream没有包含足够的信息来完全指定xusing fmt,则被stream.failbit设置并且x不被修改。并且"07-08 15:00"没有完全指定system_clock::time_point.

date.h 中的错误是 date.h 无法识别没有足够的信息来完全指定system_clock::time_point,并且正在向其写入确定性垃圾。由于system_clock::time_point( microsecondsvs 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。这对于第一次解析并不重要,但使第二次解析更整洁。

对于第二次解析,您需要解析两项:

  1. Amonth_day
  2. 一天中的某个时间

然后将这两个元素与电流结合起来year以产生所需的time_point

现在,每个解析都完全指定了它从流中解析的变量。

您最初犯的错误是想象在 的引擎盖下有一个“年份字段” system_clock::time_point。实际上,这个数据结构只不过是自 1970-01-01 00:00:00 UTC 以来的微秒或纳秒(或其他)计数。所以第二个解析必须:

  1. 解析字段,然后
  2. 解构time_pointinto 字段以获取当前年份,然后
  3. 将这些字段再次重新组合到一个time_point.