使用C++20计算基于ISO周的日期< chrono>

ttp71kqs  于 12个月前  发布在  其他
关注(0)|答案(1)|浏览(142)

我知道我可以用基于ISO周的日历(%G%V%u)格式化计时日期,但我如何使用<chrono>自己计算这些值?

dsekswqp

dsekswqp1#

我建议创建一个类型来保存基于ISO周的年、周数和工作日,并为该类型提供std::chrono::sys_daysstd::chrono::sys_days之间的转换函数。这可以满足您的要求(转换为基于ISO周的日历)* 和**从 * 基于ISO周的日历。它可以让您与任何其他具有sys_dayssys_days之间转换的日历系统进行互操作。
此数据结构的初稿可能如下所示:

struct iso_week_date
{
    std::int32_t year;
    std::uint8_t weeknum;
    std::chrono::weekday dow;

    constexpr iso_week_date(int y, unsigned wn, std::chrono::weekday wd) noexcept;
    constexpr iso_week_date(std::chrono::sys_days tp) noexcept;
    constexpr operator std::chrono::sys_days() const noexcept;
};

字符串
为了演示的目的,我已经尽可能的简单了,你可以很容易地看到,你可以添加一些功能,比如年份算法,使数据成员私有化,给它们给予getter和setter,添加流和解析操作符,等等。
接受sys_days的构造函数是问题中要求的转换函数。operator sys_days是反向转换。这些可以简单地变成noexceptconstexpr,从而实现这些操作的编译时计算。
让我们从sys_days构造函数开始:
首先要了解的是ISO基于周的日历是,它将一年分为52或53周,周从星期一开始。每周都完全在ISO年的一年内。因此ISO年并不总是与民用年相同,尽管通常是。在民用年的开始或结束附近,ISO年份可以是民用年份之一。例如,当12月31日福尔斯落在星期一时,该星期一与下一个星期四共享同一年,比该星期一的民用年份大一。
除了星期四,一周中的每一天的民用年都可以比ISO年份晚一年或早一年。但是对于星期四,民用年和ISO年份总是相同的。结合一周(星期一到星期日)总是在同一年的事实,在计算从sys_daysiso_week_date的转换时很重要。

constexpr
iso_week_date::iso_week_date(std::chrono::sys_days tp) noexcept
    : iso_week_date{0, 0, std::chrono::weekday{}}
{
    using namespace std::chrono;

    auto closest_thursday = [](sys_days tp)
    {
        weekday wd{tp};
        auto i = static_cast<int>(wd.iso_encoding());
        tp += days{4-i};
        return tp;
    };

    auto y = year_month_day{closest_thursday(tp)}.year();
    auto start = sys_days{y/1/Thursday[1]} - (Thursday - Monday);
    year = int{y};
    weeknum = floor<weeks>(tp - start) / weeks{1} + 1;
    dow = weekday{tp};
}


closest_thursday辅助函数获取sys_days并将其Map到最近的星期四。也就是说,它将星期一,星期二或星期三添加几天,并从星期五,星期六或星期日减去几天以产生星期四。这用于为tp引用的ISO周获取正确的ISO年份,无论tp是星期几。
一旦计算出正确的ISO年份,ISO年份的开始是该年第一个星期四之前的星期一。表达式(Thursday - Monday)只是days{3}的一种详细写法,旨在表明表达式正在查找第一个星期四之前的星期一。如果需要,可以替换days{3}。这不会影响代码的正确性或性能。
接下来,直接填写iso_week_date数据成员。注意,周计数从1开始,而不是0,因此+ 1
反向转换(从iso_week_datesys_days)更容易:

constexpr
iso_week_date::operator std::chrono::sys_days() const noexcept
{
    using std::chrono::sys_days;
    using std::chrono::Monday;
    using std::chrono::Thursday;
    using std::chrono::weeks;

    auto start = sys_days{std::chrono::year{year}/1/Thursday[1]} - (Thursday - Monday);
    return start + weeks{weeknum-1} + (dow - Monday);
}


首先计算ISO年份的开始(第一个星期四之前的星期一),然后加上正确的周数和星期一之后的天数。
这可以像这样练习:

int
main()
{
    using namespace std::chrono;

    iso_week_date d1{2024y/March/29};
    std::cout << d1.year << "-W" << unsigned{d1.weeknum} << " " << d1.dow << '\n';
    iso_week_date d2{Friday[last]/March/2024};
    std::cout << d2.year << "-W" << unsigned{d2.weeknum} << " " << d2.dow << '\n';
    sys_days d3 = sys_days{d2} + days{3};
    std::cout << d3 << '\n';
}


这将year_month_day转换为iso_week_date。请注意,代码中没有任何地方是iso_week_date知道类型year_month_day。然后它将year_weekday_last转换为iso_week_date。这恰好与前面的示例相同。请注意,iso_week_date不知道类型year_weekday_last。转换是从year_weekday_last反弹的。sys_days引擎盖下。
请注意,如果其他人创建了另一个具有与sys_days(朱利安,希伯来语,玛雅语,伊斯兰教等)之间转换的日历,那么iso_week_date也将能够与这些日历进行转换。
最后用逆转换求出3天后的日期。
输出量:

2024-W13 Fri
2024-W13 Fri
2024-04-01


Demo.

相关问题