Verwenden von C ++ 20 chrono, wie verschiedene Fakten zu einem Datum berechnet werden

19

https://www.timeanddate.com/date/weekday.html berechnet verschiedene Fakten zu einem Tag des Jahres, zum Beispiel:

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

Wie können diese Zahlen bei einem beliebigen Datum mit der C ++ 20-Chronospezifikation berechnet werden ?

Howard Hinnant
quelle
2
"... und wir alle wissen, wann ISO Woche 1 ist, oder? ..." - "Nein, aber ich habe eine Bibliothek" ... :-) - Bravo Howard!
Ted Lyngmo
Bild von stackoverflow.com/q/59391132/560648 (jetzt gelöscht). Schade, dass es gestrichen wurde, da dies eine Antwort auf diese Frage sein sollte.
Leichtigkeitsrennen im Orbit
Richtig. Ich habe dafür gestimmt, diesen wieder zu eröffnen.
Howard Hinnant

Antworten:

22

Dies ist mit der C ++ 20 Chrono-Spezifikation bemerkenswert einfach . Unten zeige ich eine Funktion, die ein beliebiges Datum eingibt und diese Informationen an druckt cout. Obwohl zum Zeitpunkt dieses Schreibens die C ++ 20-Chronospezifikation noch nicht ausgeliefert wurde, wird sie durch eine kostenlose Open-Source-Bibliothek angenähert . Sie können also heute damit experimentieren und es sogar in Versandanwendungen aufnehmen, solange Sie C ++ 11 oder höher verwenden.

Diese Antwort hat die Form einer Funktion:

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

sys_daysist eine Tagespräzision time_pointin der system_clockFamilie. Das heißt, es ist einfach eine Anzahl von Tagen seit 1970-01-01 00:00:00 UTC. Der Typalias sys_daysist neu in C ++ 20, aber der zugrunde liegende Typ ist seit C ++ 11 ( time_point<system_clock, duration<int, ratio<86400>>>) verfügbar . Wenn Sie die Open-Source-Vorschau-Bibliothek C ++ 20 verwenden , sys_daysist in namespace date.

Der folgende Code setzt funktionslokal voraus:

using namespace std;
using namespace std::chrono;

Ausführlichkeit zu reduzieren. Wenn Sie mit der Open-Source-Vorschau-Bibliothek C ++ 20 experimentieren , nehmen Sie außerdem Folgendes an:

using namespace date;

Überschrift

Die ersten beiden Zeilen auszugeben ist einfach:

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

Nehmen Sie einfach das Datum sdund verwenden Sie es formatmit den bekannten strftime/ put_timeFlags, um das Datum und den Text auszudrucken. Die Open-Source-Vorschau-Bibliothek C ++ 20 hat die fmt-Bibliothek noch nicht integriert und verwendet daher die leicht geänderte Formatzeichenfolge "%d %B %Y is a %A\n".

Dies gibt (zum Beispiel) Folgendes aus:

26 December 2019 is a Thursday

Additional facts

Gemeinsame Zwischenergebnisse einmal berechnet

Dieser Abschnitt der Funktion wird zuletzt geschrieben, da man noch nicht weiß, welche Berechnungen mehrmals benötigt werden. Aber sobald Sie es wissen, können Sie sie wie folgt berechnen:

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;

Wir benötigen die Jahres- und Monatsfelder von sdund den weekday(Wochentag). Es ist effizient, sie auf diese Weise ein für alle Mal zu berechnen. Wir werden auch (mehrmals) den ersten und den letzten Tag des laufenden Jahres benötigen. Es ist schwer , an diesem Punkt zu sagen, aber es ist effizient diese Werte als Typ speichern sys_daysals ihre spätere Verwendung nur mit Tag orientierte Arithmetik ist , die sys_daysist sehr an (Subnanosekundenbereich Geschwindigkeiten) effizient.

Fakt 1: Anzahl der Tage des Jahres und Anzahl der verbleibenden Tage im Jahr

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

Dies druckt die Tagesnummer des Jahres aus, wobei der 1. Januar der 1. Tag ist, und druckt dann auch die Anzahl der verbleibenden Tage im Jahr aus, ohne Berücksichtigung sd. Die Berechnung dazu ist trivial. Durch Teilen jedes Ergebnisses durch days{1}können Sie die Anzahl der Tage in dnund dlin einen ganzzahligen Typ für Formatierungszwecke extrahieren .

Fakt 2: Anzahl dieser Wochentage und Gesamtzahl der Wochentage im Jahr

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

wdist der Wochentag (Montag bis Sonntag), der oben in diesem Artikel berechnet wird. Um diese Berechnung durchzuführen, benötigen wir zuerst die Daten der ersten und letzten wdim Jahr y. y/1/wd[1]ist der erste wdim Januar und y/12/wd[last]der letzte wdim Dezember.

Die Gesamtzahl der wds im Jahr ist nur die Anzahl der Wochen zwischen diesen beiden Daten (plus 1). Der Unterausdruck last_wd - first_wdist die Anzahl der Tage zwischen den beiden Daten. Das Teilen dieses Ergebnisses durch 1 Woche führt zu einem integralen Typ, der die Anzahl der Wochen zwischen den beiden Daten enthält.

Die Wochennummer wird auf die gleiche Weise wie die Gesamtzahl der Wochen angegeben, außer dass eine mit dem aktuellen Tag anstelle des letzten wddes Jahres beginnt : sd - first_wd.

Fakt 3: Anzahl dieser Wochentage und Gesamtzahl der Wochentage im Monat

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

Dies funktioniert genau wie Fakt 2, außer dass wir mit dem ersten und letzten wds des Jahr-Monat-Paares y/manstelle des gesamten Jahres beginnen.

Fakt 4: Anzahl der Tage im Jahr

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

Der Code spricht so ziemlich für sich.

Fakt 5 Anzahl der Tage im Monat

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

Der Ausdruck y/m/lastist der letzte Tag des Jahr-Monat-Paares y/mund natürlich y/m/1der erste Tag des Monats. Beide werden so konvertiert, sys_daysdass sie subtrahiert werden können, um die Anzahl der Tage zwischen ihnen zu erhalten. Addiere 1 für die 1-basierte Anzahl.

Verwenden

info kann wie folgt verwendet werden:

info(December/26/2019);

oder so:

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

Hier ist eine Beispielausgabe:

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.

Bearbeiten

Für diejenigen, die die "konventionelle Syntax" nicht mögen, gibt es eine vollständige "Konstruktorsyntax", die stattdessen verwendet werden kann.

Zum Beispiel:

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

kann ersetzt werden durch:

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
quelle
5
Dieser neue Missbrauch des Divisionsoperators ist noch schlimmer als der alte Missbrauch der Bitshift-Operatoren. Es macht mich traurig :(
Dave
2
Kann ich im Ernst vorschlagen, dass Sie einige Ihrer vorberechneten Variablen in die Abschnitte verschieben, in denen sie verwendet werden? Es ist etwas umständlich zu folgen, wenn Sie nach oben und unten scrollen müssen, um zu sehen, woher Werte kommen und wie sie generiert wurden. Und Sie können Ihre Sachen für den Tag des Jahres ein wenig aufräumen, indem Sie zuerst die Teilung durchführen, wie Sie es für die Wochen getan haben.
Dave
1
Stimme überhaupt nicht zu. Es sieht gut aus, ist leicht zu verstehen und insbesondere leichter zu lesen als die ausführlichere Version.
Cássio Renan
@ CássioRenan mag sein, aber denken Sie daran, dass Syntaxmissbrauch ziemlich oft mit unerwartetem Verhalten einhergeht. Beachten Sie bei den oben genannten Bitverschiebungen z. B. das Verhalten von std::cout << "a*b = " << a*b << "; a^b = " << a^b << '\n';(das glücklicherweise fast immer zur Kompilierungszeit abgefangen wird, aber dennoch ärgerlich ist). Daher wäre ich vorsichtig, wenn ich diesen neuen Missbrauch von Abteilungsbetreibern verwende.
Ruslan
@ Ruslan Vorsicht ist bei jeder neuen Bibliothek immer geboten. Aus diesem Grund wurde dieser seit 2015 frei und öffentlich getestet. Das Feedback der Kunden wurde wieder in das Design einbezogen. Es wurde nicht zur Standardisierung vorgeschlagen, bis es eine solide Grundlage jahrelanger positiver Felderfahrung hatte. Insbesondere wurde die Verwendung von Operatoren unter Berücksichtigung der Priorität von Operatoren entwickelt, in großem Umfang vor Ort getestet und mit einer äquivalenten "Konstruktor-API" geliefert. Siehe star-history.t9t.io/#HowardHinnant/date&google/cctz und youtube.com/watch?v=tzyGjOm8AKo .
Howard Hinnant