Utilisation du chrono C ++ 20, comment calculer divers faits sur une date

19

https://www.timeanddate.com/date/weekday.html calcule divers faits concernant un jour de l'année, par exemple:

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

Étant donné une date arbitraire, comment ces nombres peuvent-ils être calculés avec la spécification chrono C ++ 20 ?

Howard Hinnant
la source
2
"... et nous savons tous quand est la semaine ISO 1, non? ..." - "Non, mais j'ai une bibliothèque" ... :-) - Bravo Howard!
Ted Lyngmo
Image prise à partir de stackoverflow.com/q/59391132/560648 (maintenant supprimée). Dommage qu'il ait été supprimé car cela aurait dû être une réponse à cette question.
Courses de légèreté en orbite
Correct. J'ai voté pour la rouvrir.
Howard Hinnant

Réponses:

22

C'est remarquablement facile avec la spécification chrono C ++ 20 . Ci-dessous, je montre une fonction qui entre une date arbitraire et imprime ces informations cout. Bien qu'au moment d'écrire ces lignes, la spécification de chrono C ++ 20 n'est pas encore disponible, elle est approximée par une bibliothèque open-source gratuite . Vous pouvez donc l'expérimenter aujourd'hui, et même l'inclure dans les applications d'expédition tant que vous adoptez C ++ 11 ou une version ultérieure.

Cette réponse prendra la forme d'une fonction:

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

sys_daysest une précision de jour time_pointdans la system_clockfamille. Cela signifie qu'il s'agit simplement d'un nombre de jours depuis le 1970-01-01 00:00:00 UTC. L'alias de type sys_daysest nouveau avec C ++ 20, mais le type sous-jacent est disponible depuis C ++ 11 ( time_point<system_clock, duration<int, ratio<86400>>>). Si vous utilisez la bibliothèque d'aperçu open-source C ++ 20 , sys_daysest dans namespace date.

Le code ci-dessous suppose une fonction locale:

using namespace std;
using namespace std::chrono;

pour réduire la verbosité. Si vous expérimentez avec la bibliothèque d'aperçu open-source C ++ 20 , supposez également:

using namespace date;

Titre

La sortie des deux premières lignes est simple:

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

Il suffit de prendre la date sdet de l'utiliser formatavec les drapeaux familiers strftime/ put_timepour imprimer la date et le texte. La bibliothèque d'aperçu open-source C ++ 20 n'a pas encore intégré la bibliothèque fmt , et utilise donc la chaîne de format légèrement modifiée "%d %B %Y is a %A\n".

Cela produira (par exemple):

26 December 2019 is a Thursday

Additional facts

Résultats intermédiaires communs calculés une fois

Cette section de la fonction est écrite en dernier, car on ne sait pas encore quels calculs seront nécessaires plusieurs fois. Mais une fois que vous savez, voici comment les calculer:

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;

Nous aurons besoin des champs année et mois de sdet du weekday(jour de la semaine). Il est efficace de les calculer une fois pour toutes de cette manière. Nous aurons également besoin (plusieurs fois) des premier et dernier jours de l'année en cours. Il est difficile de dire à ce stade, mais il est efficace de stocker ces valeurs en tant que type sys_dayscar leur utilisation ultérieure ne l'est qu'avec une arithmétique orientée vers le jour qui sys_daysest très efficace à (vitesses inférieures à la nanoseconde).

Fait 1: nombre de jours de l'année et nombre de jours restants dans l'année

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

Cela imprime le numéro de jour de l'année, le 1er janvier étant le jour 1, puis imprime également le nombre de jours restants dans l'année, sans inclure sd. Le calcul pour ce faire est trivial. La division de chaque résultat par days{1}permet d'extraire le nombre de jours dans dnet dlen un type intégral à des fins de formatage.

Fait 2: Numéro de ce jour de semaine et nombre total de jours de semaine dans l'année

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

wdest le jour de la semaine (du lundi au dimanche) calculé en haut de cet article. Pour effectuer ce calcul, nous avons d'abord besoin des dates du premier et du dernier wdde l'année y. y/1/wd[1]est le premier wden janvier et y/12/wd[last]le dernier wden décembre.

Le nombre total de wds dans l'année n'est que le nombre de semaines entre ces deux dates (plus 1). La sous-expression last_wd - first_wdest le nombre de jours entre les deux dates. En divisant ce résultat par 1 semaine, vous obtenez un type intégral contenant le nombre de semaines entre les deux dates.

Le numéro de la semaine se fait de la même manière que le nombre total de semaines , sauf un commence par le jour en cours au lieu de la dernière wdde l'année: sd - first_wd.

Fait 3: Numéro de ce jour de semaine et nombre total de jours de semaine dans le mois

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

Cela fonctionne exactement comme le fait 2, sauf que nous commençons par le premier et le dernier wds de la paire année-mois y/mau lieu de l'année entière.

Fait 4: Nombre de jours dans l'année

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

Le code parle à peu près de lui-même.

Fait 5 Nombre de jours dans le mois

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

L'expression y/m/lastest le dernier jour de la paire année-mois y/m, et bien sûr y/m/1le premier jour du mois. Les deux sont convertis en sys_daysafin qu'ils puissent être soustraits pour obtenir le nombre de jours entre eux. Ajoutez 1 pour le nombre basé sur 1.

Utilisation

info peut être utilisé comme ceci:

info(December/26/2019);

ou comme ça:

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

Voici un exemple de sortie:

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.

Éditer

Pour ceux qui ne sont pas friands de la "syntaxe conventionnelle", il existe une "syntaxe constructeur" complète qui peut être utilisée à la place.

Par exemple:

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

peut être remplacé par:

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}};
Howard Hinnant
la source
5
Ce nouvel abus de l'opérateur de division est encore pire que l'ancien abus des opérateurs de décalage de bits. Ça me rend triste :(
Dave
2
Plus sérieusement, puis-je suggérer de déplacer certaines de vos variables précalculées vers les sections qui les utilisent? C'est un peu gênant à suivre lorsqu'il faut faire défiler vers le haut et vers le bas pour voir d'où viennent les valeurs et comment elles ont été générées. Et vous pouvez désencombrer un peu vos affaires de jour de l'année en faisant d'abord la division, comme vous l'avez fait pendant des semaines.
Dave
1
Pas du tout d'accord. Il a l'air bien, il est facile à comprendre et, notamment, il est plus facile à lire que la version plus verbeuse.
Cássio Renan
@ CássioRenan pourrait l'être, mais rappelez-vous que l'abus de syntaxe s'accompagne souvent d'un comportement inattendu. Avec les décalages de bits susmentionnés, par exemple, notez le comportement de std::cout << "a*b = " << a*b << "; a^b = " << a^b << '\n';(qui, heureusement, est presque toujours pris au moment de la compilation, mais qui est toujours une gêne). Je serais donc prudent lors de l'utilisation de ce nouvel abus d'opérateur de division.
Ruslan
@Ruslan Caution est toujours garantie avec toute nouvelle bibliothèque. C'est pourquoi celui-ci a été testé librement et publiquement depuis 2015. Les commentaires des clients ont été réintégrés dans la conception. Il n'a pas été proposé pour la normalisation avant d'avoir une base solide d'années d'expérience positive sur le terrain. En particulier, l'utilisation d'opérateurs a été conçue en tenant compte de la priorité des opérateurs, largement testée sur le terrain et est livrée avec une "API constructeur" équivalente. Voir star-history.t9t.io/#HowardHinnant/date&google/cctz et youtube.com/watch?v=tzyGjOm8AKo .
Howard Hinnant