Используя C ++ 20 chrono, как вычислить различные факты о дате

19

https://www.timeanddate.com/date/weekday.html вычисляет различные факты о дне года, например:

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

Учитывая произвольную дату, как эти числа могут быть вычислены с хронологической спецификацией C ++ 20 ?

Говард Хиннант
источник
2
«... и мы все знаем, когда ISO 1 неделя, верно? ...» - «Нет, но у меня есть библиотека» ... :-) - Браво Говард!
Тед Люнгмо
Изображение взято с stackoverflow.com/q/59391132/560648 (сейчас удалено). Позор, это было удалено, поскольку это должно было быть ответом на тот вопрос.
Гонки
Правильный. Я проголосовал, чтобы открыть это.
Говард

Ответы:

22

Это очень просто с хронологической спецификацией C ++ 20 . Ниже я показываю функцию, которая вводит произвольную дату и печатает эту информацию cout. Хотя на момент написания этой статьи спецификация C ++ 20 chrono еще не поставлялась, она приближается к бесплатной библиотеке с открытым исходным кодом . Таким образом, вы можете поэкспериментировать с ним сегодня и даже включить его в поставку приложений, если вы используете C ++ 11 или более позднюю версию.

Этот ответ примет форму функции:

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

sys_daysТочность дня time_pointв system_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;

уменьшить многословие. Если вы экспериментируете с библиотекой предварительного просмотра C ++ 20 с открытым исходным кодом , также предположите:

using namespace date;

Heading

Вывести первые две строки просто:

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

Просто возьмите дату sdи используйте formatзнакомые 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, а затем также выводит количество дней, оставшихся в году, не включая 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в году y. y/1/wd[1]первый wdв январе и y/12/wd[last]последний wdв декабре.

Общее количество wds в году - это количество недель между этими двумя датами (плюс 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
Это новое злоупотребление оператором деления даже хуже, чем прежнее злоупотребление операторами битового сдвига. Это меня огорчает :(
Дэйв
2
Если говорить более серьезно, могу ли я предложить вам переместить некоторые из ваших предварительно вычисленных переменных в разделы, в которых они используются? Немного неловко следовать, когда приходится прокручивать вверх и вниз, чтобы увидеть, откуда берутся значения и как они были сгенерированы. И вы можете немного разгромить свои дневные дела, выполнив сначала разделение, как вы делали в течение нескольких недель.
Дейв
1
Не согласен полностью. Он выглядит хорошо, его легко понять и, что примечательно, его легче читать, чем более многословную версию.
Кассио Ренан
@ CássioRenan может быть, но помните, что злоупотребление синтаксисом довольно часто сопровождается неожиданным поведением. С вышеупомянутыми битовыми сдвигами, например, обратите внимание на поведение std::cout << "a*b = " << a*b << "; a^b = " << a^b << '\n';(которое, к счастью, почти всегда ловится во время компиляции, но все же вызывает раздражение). Так что я буду осторожен при использовании нового злоупотребления оператором подразделения.
Руслан
@Ruslan Внимание всегда гарантировано с любой новой библиотекой. Вот почему это было свободно и публично протестировано с 2015 года. Отзывы клиентов были включены обратно в дизайн. Он не был предложен для стандартизации, пока у него не было прочной основы многолетнего положительного полевого опыта. В частности, использование операторов было разработано с учетом приоритета операторов, широко протестировано на местах и ​​поставляется с эквивалентным «конструктором API». См. Star-history.t9t.io/#HowardHinnant/date&google/cctz и youtube.com/watch?v=tzyGjOm8AKo .
Говард Хиннант