如何根据 ISO 8601 指定的日期获取固定的计时 time_point?

How*_*ant 4 c++ c++-chrono c++20

受到这个 SO Answer 的启发,其中有一个问题:

我只是希望 chrono 能让我做这样的事情:

std::chrono::time_point<std::chrono::system_clock> xmas = std::chrono::datetime("2023-12-25");
Run Code Online (Sandbox Code Playgroud)

例如,让我根据 ISO 8601 指定的日期获取固定时间点。如果日期以某种方式错误,则引发异常或以其他方式将时间点设置为纪元,并使用任何认为合适的错误处理。不支持非 ISO 8601 日期,但值得注意的是也可以实施其他标准。

或者,您可以使用 XXXX 表示当年,例如 XXXX-01-01 变为今年 1 月 1 日,XXXX-12-25 变为 12 月 1 日。25 号,但现在我真的在愿望清单上了:)

How*_*ant 8

将 C++20 视为std::chrono一组日期和时间代码的构建块。确实没有什么是你不能用这些基本构建块轻松构建的,同时避免与时间、时区和日历相关的大部分技巧。

例如,这里有一个只有几十行长的函数,它允许您完全执行您想要的操作,包括获取您希望列表中的所有内容。代码后面逐行解释:

#include <chrono>
#include <sstream>
#include <stdexcept>

namespace my
{

std::chrono::system_clock::time_point
datetime(std::string const& s)
{
    std::istringstream in{s};
    std::chrono::year y;
    std::chrono::month_day md;
    if (in.peek() == 'X')
    {
        in >> std::chrono::parse("XXXX-%m-%d", md);
        if (in.fail())
            throw std::runtime_error(
                "Unable to parse a date of the form XXXX-mm-dd out of \"" + s + '"');
        y = std::chrono::year_month_day{
                std::chrono::floor<std::chrono::days>(
                    std::chrono::system_clock::now())}.year();
    }
    else
    {
        in >> std::chrono::parse("%Y-", y) >> std::chrono::parse("%m-%d", md);
        if (in.fail())
            throw std::runtime_error(
                "Unable to parse a date of the form yyyy-mm-dd out of \"" + s + '"');
    }
    auto date = y/md;
    if (!date.ok())
        throw std::runtime_error("Parsed invalid date out of \"" + s + '"');
    return std::chrono::sys_days{date};
}

}  // namespace my
Run Code Online (Sandbox Code Playgroud)
  • 首先要做的是查明字符串是否具有XXXX-mm-ddor的形式yyyy-mm-dd。通过查看字符串的第一个字符可以轻松完成此操作。如果是,X那么它一定是XXXX-mm-dd,否则它一定是yyyy-mm-dd,否则这是一个错误,我们通过抛出带有详细错误消息的异常来标记。

  • 如果字符串看起来具有以下形式XXXX-mm-dd,则chrono::month_day使用格式字符串解析 a "XXXX-%m-%d"。如果存在任何解析错误,或者解析的内容month_day不可能有效,则解析将失败。

  • 如果解析失败,则抛出异常并提供有用的错误消息。

  • 如果解析成功,则计算当前年份 (UTC) 并将其分配给y。如果需要本地年份,或者需要任何 IANA 时区的年份,只需要多写几行代码即可。

  • 否则,字符串必须采用以下形式yyyy-mm-dd。分别解析为achrono::year和a chrono::month_day

  • 如果任何解析失败,请抛出异常并提供有用的错误消息。

  • 最后将 theyear和 the组合month_day成 a year_month_daydate在此演示代码中调用)。

  • 检查 是否year有效,以及month_day是否有效,但两者的组合无效。这将捕获非闰年的 2 月 29 日等日期。如果找到,则抛出异常并提供有用的错误消息。

  • 通过首先转换为datea ,然后让隐式转换将精度细化为 ,将解析的结果转换为 a 。system_clock::time_pointsys_dayssystem_clock::time_point

可以这样练习:

#include <iostream>

int
main()
{
    auto xmas = my::datetime("2023-12-25");
    std::cout << xmas << '\n';
    xmas = {};
    xmas = my::datetime("XXXX-12-25");
    std::cout << xmas << '\n';
    try
    {
        xmas = my::datetime("XXXX-25-12");
    }
    catch (std::exception const& e)
    {
        std::cout << e.what() << '\n';
    }
}
Run Code Online (Sandbox Code Playgroud)

哪个输出:

2023-12-25 00:00:00.000000
2023-12-25 00:00:00.000000
Unable to parse a date of the form XXXX-mm-dd out of "XXXX-25-12"
Run Code Online (Sandbox Code Playgroud)

请注意,我们的代码捕获了正确语法的错误,但捕获了 25 月 12 日的无效日期。

另请注意,如果需要,可以通过更多检查和分支来支持其他日期格式。