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


19

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

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

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


2
“……我们都知道ISO第1周是什么时候,对吗?……” -“不,但我有一个图书馆”……:-)-布拉沃·霍华德!
Ted Lyngmo

图片取自stackoverflow.com/q/59391132/560648(现已删除)。可惜它被删除了,因为它本来应该是这个问题的答案。
Lightness Races in Orbit

正确。我投票决定重新打开那个。
Howard Hinnant

Answers:


22

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

该答案将采用函数形式:

void info(std::chrono::sys_days sd);

sys_days每天精度time_pointsystem_clock家庭。这意味着距UTC 1970-01-01 00:00:00只是几天的时间。类型别名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;

减少冗长。如果您正在尝试使用开放源代码的C ++ 20预览库,请假设:

using namespace date;

标题

要输出前两行很简单:

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

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

这将输出(例如):

26 December 2019 is a Thursday

Additional facts

共同的中间结果计算一次

函数的这一部分最后编写,因为尚不知道将需要多次计算。但是一旦您知道了,这就是如何计算它们:

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;

我们将需要的年和月字段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";

这会打印出一年中的天数,其中1月1日为第1天,然后还会打印出该年中剩余的天数(不包括)sd。执行此操作的计算量很小。将每个结果除以days{1}一种方法可以提取整整类型中的天数dn并将其dl分成整数,以进行格式化。

事实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);

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

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);

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

事实4:一年中的天数

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

该代码几乎可以说明一切。

事实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";

表达式y/m/last是年份-月份对的最后一天,y/m当然y/m/1是月份的第一天。两者都转换为sys_days,因此可以减去它们以得到它们之间的天数。从1开始的计数加1。

采用

info 可以这样使用:

info(December/26/2019);

或像这样:

info(floor<days>(system_clock::now()));

这是示例输出:

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.

编辑

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

例如:

sys_days NewYears = y/1/1;
sys_days first_wd = y/1/wd[1];
sys_days last_wd = y/12/wd[last];

可以替换为:

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}};

5
这种除法运算符的新滥用甚至比旧的移位运算符的滥用还要严重。这让我很难过:(
Dave

2
更严重的是,我是否建议您将一些预先计算的变量移到使用它们的部分中?当必须向上和向下滚动以查看值的来源和如何生成时,遵循起来会有些尴尬。您可以像数周前一样,先进行部门划分,以使一年中的日常工作变得混乱。
戴夫

1
完全不同意。它看起来不错,易于理解,尤其是它比详细版本更易于阅读。
卡西奥·雷南

@CássioRenan可能是,但是请记住,语法滥用经常伴随着意外行为。例如,使用上述位移,请注意的行为std::cout << "a*b = " << a*b << "; a^b = " << a^b << '\n';(幸运的是,它几乎总是在编译时捕获,但仍然很烦人)。因此,在使用这种新的部门运算符滥用行为时,我会保持谨慎。
Ruslan

任何新库都始终保证@Ruslan Caution。这就是为什么自2015年以来就对该产品进行免费和公开测试的原因。来自客户的反馈意见已重新纳入设计中。直到有多年积极的现场经验的坚实基础,才提出将其标准化。特别是,在设计运算符时,考虑了运算符的优先级,并进行了广泛的现场测试,并带有等效的“构造函数API”。请参阅star-history.t9t.io/#HowardHinnant/date&google/cctzyoutube.com/watch?v=tzyGjOm8AKo
Howard Hinnant
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.