Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Using C++20 chrono, how to compute various facts about a date

Tags:

https://www.timeanddate.com/date/weekday.html computes various facts about a day of the year, for example:

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

Given an arbitrary date, how can these numbers be computed with the C++20 chrono specification?

like image 380
Howard Hinnant Avatar asked Dec 20 '19 00:12

Howard Hinnant


People also ask

What is Chrono in C?

Chrono in C++ The unique thing about chrono is that it provides a precision-neutral concept by separating duration and point of time (“timepoint”) from specific clocks.

What is STD Chrono in C++?

C++ includes support for two types of time manipulation: The chrono library, a flexible collection of types that track time with varying degrees of precision (e.g. std::chrono::time_point).


1 Answers

This is remarkably easy with the C++20 chrono specification. Below I show a function which inputs an arbitrary date, and prints this information to cout. Though at the time of this writing, the C++20 chrono specification isn't yet shipping, it is approximated by a free, open-source library. So you can experiment with it today, and even include it in shipping applications as long as you adopt C++11 or later.

This answer will take the form of a function:

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

sys_days is a day-precision time_point in the system_clock family. That means it is simply a count of days since 1970-01-01 00:00:00 UTC. The type alias sys_days is new with C++20, but the underlying type has been available since C++11 (time_point<system_clock, duration<int, ratio<86400>>>). If you use the open-source C++20 preview library, sys_days is in namespace date.

The code below assumes function-local:

using namespace std;
using namespace std::chrono;

to reduce verbosity. If you are experimenting with the open-source C++20 preview library, also assume:

using namespace date;

Heading

To output the first two lines is simple:

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

Just take the date sd and use format with the familiar strftime/put_time flags to print out the date and text. The open-source C++20 preview library hasn't yet integrated the fmt library, and so uses the slightly altered format string "%d %B %Y is a %A\n".

This will output (for example):

26 December 2019 is a Thursday

Additional facts

Common intermediate results computed once

This section of the function is written last, because one doesn't yet know what computations will be needed multiple times. But once you know, here is how to compute them:

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;

We will need the year and month fields of sd, and the weekday (day of the week). It is efficient to compute them once and for all in this manner. We will also need (multiple times) the first and last days of the current year. It is hard to tell at this point, but it is efficient to store these values as type sys_days as their subsequent use is only with day-oriented arithmetic which sys_days is very efficient at (sub-nanosecond speeds).

Fact 1: day number of year, and number of days left in year

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

This prints out the day number of the year, with January 1 being day 1, and then also prints out the number of days remaining in the year, not including sd. The computation to do this is trivial. Dividing each result by days{1} is a way to extract the number of days in dn and dl into an integral type for formatting purposes.

Fact 2: Number of this weekday and total number of weekdays in year

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 is the day of the week (Monday thru Sunday) computed at the top of this article. To perform this computation we first need the dates of the first and last wd's in the year y. y/1/wd[1] is the first wd in January, and y/12/wd[last] is the last wd in December.

The total number of wds in the year is just the number of weeks between these two dates (plus 1). The sub-expression last_wd - first_wd is the number of days between the two dates. Dividing this result by 1 week results in an integral type holding the number of weeks between the two dates.

The week number is done the same way as the total number of weeks except one starts with the current day instead of the last wd of the year: sd - first_wd.

Fact 3: Number of this weekday and total number of weekdays in month

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

This works just like Fact 2, except we start with the first and last wds of the year-month pair y/m instead of the entire year.

Fact 4: Number of days in year

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

The code pretty much speaks for itself.

Fact 5 Number of days in month

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

The expression y/m/last is the last day of the year-month pair y/m, and of course y/m/1 is the first day of the month. Both are converted to sys_days so that they can be subtracted to get the number of days between them. Add 1 for the 1-based count.

Use

info can be used like this:

info(December/26/2019);

or like this:

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

Here is example output:

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.

Edit

For those who are not fond of the "conventional syntax", there is a complete "constructor syntax" that can be used instead.

For example:

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

can be replaced by:

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}};
like image 167
Howard Hinnant Avatar answered Sep 22 '22 14:09

Howard Hinnant