使用 C++20 chrono,如何计算有关日期的各种事实

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

https://www.timeanddate.com/date/weekday.html计算一年中某一天的各种事实,例如:

https://i.stack.imgur.com/WPWuO.png

给定一个任意日期,如何使用C++20 chrono 规范计算这些数字?

How*_*ant 26

使用C++20 chrono 规范,这非常容易。下面我展示了一个输入任意日期的函数,并将此信息打印到cout. 尽管在撰写本文时,C++20 chrono 规范尚未发布,但它近似于一个免费的开源库。因此,您现在就可以试验它,甚至只要您采用 C++11 或更高版本,就可以将其包含在交付应用程序中。

这个答案将采用函数的形式:

void info(std::chrono::sys_days sd);
Run Code Online (Sandbox Code Playgroud)

sys_days每天精度time_pointsystem_clock家庭。这意味着它只是自 1970-01-01 00:00:00 UTC 以来的天数计数。类型别名sys_days在 C++20 中是新的,但从 C++11 ( time_point<system_clock, duration<int, ratio<86400>>>)开始就可以使用底层类型。如果您使用开源 C++20 预览库sys_days则在namespace date.

下面的代码假设函数是本地的:

using namespace std;
using namespace std::chrono;
Run Code Online (Sandbox Code Playgroud)

以减少冗长。如果您正在试验开源 C++20 预览库,还要假设:

using namespace date;
Run Code Online (Sandbox Code Playgroud)

标题

输出前两行很简单:

cout << format("{:%d %B %Y is a %A}\n", sd)
     << "\nAdditional facts\n";
Run Code Online (Sandbox Code Playgroud)

只需获取日期sd并使用format熟悉的strftime/put_time标志打印出日期和文本。该开源C ++ 20预览库尚未整合的FMT库,所以使用稍微改变格式字符串"%d %B %Y is a %A\n"

这将输出(例如):

26 December 2019 is a Thursday

Additional facts
Run Code Online (Sandbox Code Playgroud)

一次计算的常见中间结果

函数的这一部分写在最后,因为人们还不知道需要多次进行哪些计算。但是一旦你知道,这里是如何计算它们:

year_month_day ymd = sd;
auto y = ymd.year();
auto m = ymd.month();
weekday wd{sd};
sys_days NewYears = y/1/1;
sys_days LastDayOfYear = y/12/31;
Run Code Online (Sandbox Code Playgroud)

我们将需要 的年和月字段sd,以及weekday(星期几)。以这种方式一劳永逸地计算它们是有效的。我们还需要(多次)今年的第一天和最后一天。这是很难说在这一点上,但它是有效的,这些值存储类型sys_days为他们的后续使用仅面向天算术这sys_days非常在(亚纳秒的速度)有效。

事实 1:一年中的天数,以及一年中剩余的天数

auto dn = sd - NewYears + days{1};
auto dl = LastDayOfYear - sd;
cout << "* It is day number " << dn/days{1} << " of the year, "
     << dl/days{1} << " days left.\n";
Run Code Online (Sandbox Code Playgroud)

这将打印出一年中的天数,1 月 1 日是第 1 天,然后还打印出一年中剩余的天数,不包括sd. 执行此操作的计算是微不足道的。除以每个结果days{1}是一种方法,在提取的天数dndl成一个整体型格式化的目的。

事实2:这个工作日的数量和一年中的工作日总数

sys_days first_wd = y/1/wd[1];
sys_days last_wd = y/12/wd[last];
auto total_wd = (last_wd - first_wd)/weeks{1} + 1;
auto n_wd = (sd - first_wd)/weeks{1} + 1;
cout << format("* It is {:%A} number ", wd) << n_wd << " out of "
     << total_wd << format(" in {:%Y}.\n}", y);
Run Code Online (Sandbox Code Playgroud)

wd是在本文顶部计算的星期几(星期一至星期日)。为了执行这个计算,我们首先需要wd一年中第一个和最后一个的日期yy/1/wd[1]是第一次wd在一月,和y/12/wd[last]是最后wd在12月。

wd一年中s的总数只是这两个日期之间的周数(加 1)。子表达式last_wd - first_wd是两个日期之间的天数。将此结果除以 1 周会得到一个整数类型,其中包含两个日期之间的周数。

周数的处理方式与总周数相同,只是从当天而不是wd一年的最后一天开始:sd - first_wd

事实 3:该工作日的数量和该月的工作日总数

first_wd = y/m/wd[1];
last_wd = y/m/wd[last];
total_wd = (last_wd - first_wd)/weeks{1} + 1;
n_wd = (sd - first_wd)/weeks{1} + 1;
cout << format("* It is {:%A} number }", wd) << n_wd << " out of "
     << total_wd << format(" in {:%B %Y}.\n", y/m);
Run Code Online (Sandbox Code Playgroud)

这就像事实 2 一样,除了我们从wd年月对的第一个和最后一个s开始,y/m而不是从整年开始。

事实 4:一年中的天数

auto total_days = LastDayOfYear - NewYears + days{1};
cout << format("* Year {:%Y} has ", y) << total_days/days{1} << " days.\n";
Run Code Online (Sandbox Code Playgroud)

代码几乎不言自明。

事实 5 一个月的天数

total_days = sys_days{y/m/last} - sys_days{y/m/1} + days{1};
cout << format("* {:%B %Y} has ", y/m) << total_days/days{1} << " days.\n";
Run Code Online (Sandbox Code Playgroud)

表达式y/m/last是年月对的最后一天,y/m当然y/m/1是月的第一天。两者都被转换为 ,sys_days以便可以减去它们以获得它们之间的天数。为基于 1 的计数加 1。

info 可以这样使用:

info(December/26/2019);
Run Code Online (Sandbox Code Playgroud)

或者像这样:

info(floor<days>(system_clock::now()));
Run Code Online (Sandbox Code Playgroud)

这是示例输出:

26 December 2019 is a Thursday

Additional facts
* It is day number 360 of the year, 5 days left.
* It is Thursday number 52 out of 52 in 2019.
* It is Thursday number 4 out of 4 in December 2019.
* Year 2019 has 365 days.
* December 2019 has 31 days.
Run Code Online (Sandbox Code Playgroud)

编辑

对于那些不喜欢“常规语法”的人,有一个完整的“构造函数语法”可以替代。

例如:

sys_days NewYears = y/1/1;
sys_days first_wd = y/1/wd[1];
sys_days last_wd = y/12/wd[last];
Run Code Online (Sandbox Code Playgroud)

可以替换为:

sys_days NewYears = year_month_day{y, month{1}, day{1}};
sys_days first_wd = year_month_weekday{y, month{1}, weekday_indexed{wd, 1}};
sys_days last_wd = year_month_weekday_last{y, month{12}, weekday_last{wd}};
Run Code Online (Sandbox Code Playgroud)

  • 这种新的除法运算符滥用比旧的移位运算符滥用更严重。这让我很难过 :( (6认同)
  • 更严肃地说,我可以建议您将一些预先计算的变量移至使用它们的部分吗?当必须上下滚动才能查看值来自哪里以及它们是如何生成时,有点难以理解。你可以通过先进行除法来整理一年中的某天的东西,就像你在几周内所做的那样。 (2认同)
  • 完全不同意。它看起来不错,很容易理解,而且值得注意的是,它比更冗长的版本更容易阅读。 (2认同)
  • @Ruslan 任何新库都必须谨慎。这就是为什么自 2015 年以来一直对其进行免费公开测试的原因。客户的反馈已被纳入设计中。在拥有多年积极的现场经验的坚实基础之前,并未提议将其标准化。特别是,运算符的使用在设计时考虑了运算符优先级,经过广泛的现场测试,并附带等效的“构造函数 API”。请参阅 https://star-history.t9t.io/#HowardHinnant/date&amp;google/cctz 和 https://www.youtube.com/watch?v=tzyGjOm8AKo 。 (2认同)