Używając chronografu C ++ 20, jak obliczyć różne fakty dotyczące daty

19

https://www.timeanddate.com/date/weekday.html oblicza różne fakty dotyczące dnia roku, na przykład:

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

Biorąc pod uwagę dowolną datę, w jaki sposób można obliczyć te liczby ze specyfikacją chronografu C ++ 20 ?

Howard Hinnant
źródło
2
„... i wszyscy wiemy, kiedy jest pierwszy tydzień ISO, prawda? ...” - „Nie, ale mam bibliotekę” ... :-) - Bravo Howard!
Ted Lyngmo,
Zdjęcie zrobione ze stackoverflow.com/q/59391132/560648 (teraz usunięte). Szkoda, że ​​został usunięty, ponieważ powinna to być odpowiedź na to pytanie.
Wyścigi lekkości na orbicie
Poprawny. Głosowałem za ponownym otwarciem tego.
Howard Hinnant,

Odpowiedzi:

22

Jest to niezwykle łatwe dzięki specyfikacji chronografu C ++ 20 . Poniżej pokazuję funkcję, która wprowadza dowolną datę i wypisuje tę informację na cout. Chociaż w chwili pisania tego tekstu specyfikacja chronografu C ++ 20 nie jest jeszcze dostępna, jest ona aproksymowana przez bezpłatną bibliotekę typu open source . Możesz więc eksperymentować z nim już dziś, a nawet uwzględnić go w aplikacjach wysyłkowych, o ile zastosujesz C ++ 11 lub nowszy.

Ta odpowiedź przybierze postać funkcji:

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

sys_daysto precyzja dnia time_pointw system_clockrodzinie. Oznacza to, że jest to po prostu liczba dni od 1970-01-01 00:00:00 UTC. Alias ​​typu sys_daysjest nowy w C ++ 20, ale typ podstawowy jest dostępny od C ++ 11 ( time_point<system_clock, duration<int, ratio<86400>>>). Jeśli używasz biblioteki podglądu C ++ 20 typu open source , sys_daysjest w namespace date.

Poniższy kod zakłada lokalną funkcję:

using namespace std;
using namespace std::chrono;

w celu zmniejszenia gadatliwości. Jeśli eksperymentujesz z biblioteką podglądu C ++ 20 typu open source , załóż również:

using namespace date;

Nagłówek

Wyprowadzenie pierwszych dwóch wierszy jest proste:

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

Po prostu weź datę sdi użyj formatze znanymi strftime/ put_timeflagami, aby wydrukować datę i tekst. C ++ 20 podgląd biblioteki open-source nie został jeszcze zintegrowany z biblioteki FMT , a więc korzysta z lekko zmienioną ciąg formatu "%d %B %Y is a %A\n".

Spowoduje to wyświetlenie (na przykład):

26 December 2019 is a Thursday

Additional facts

Wspólne wyniki pośrednie obliczane raz

Ta sekcja funkcji jest zapisywana jako ostatnia, ponieważ nie wiadomo jeszcze, jakie obliczenia będą potrzebne wiele razy. Ale kiedy już wiesz, oto jak je obliczyć:

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;

Będziemy potrzebować pól roku i miesiąca sdoraz weekday(dnia tygodnia). Skuteczne jest obliczenie ich raz na zawsze w ten sposób. Będziemy także potrzebować (wiele razy) pierwszy i ostatni dzień bieżącego roku. Trudno w tym momencie powiedzieć, ale efektywne jest przechowywanie tych wartości jako typu, sys_daysponieważ ich późniejsze użycie jest tylko z arytmetyką zorientowaną na dzień, która sys_daysjest bardzo wydajna przy (prędkościach poniżej nanosekundowych).

Fakt 1: liczba dni w roku i liczba dni pozostałych w roku

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

Spowoduje to wydrukowanie numeru dnia roku, przy czym 1 stycznia jest dniem 1, a następnie wydrukowanie liczby dni pozostałych w roku, bez uwzględnienia sd. Obliczenia tego są trywialne. Podzielenie wyniku przez każdy days{1}jest sposób wydobycia liczby dni dni dlw integralny typu dla celów formatowania.

Fakt 2: Liczba dni tygodnia i całkowita liczba dni tygodnia w roku

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

wdto dzień tygodnia (od poniedziałku do niedzieli) obliczony na początku tego artykułu. Aby wykonać to obliczenie, najpierw potrzebujemy daty pierwszego i ostatniego wdroku y. y/1/wd[1]jest pierwszy wdw styczniu, a y/12/wd[last]ostatni wdw grudniu.

Całkowita liczba wds w ciągu roku to tylko liczba tygodni między tymi dwiema datami (plus 1). Podwyrażenie last_wd - first_wdto liczba dni między dwiema datami. Dzielenie tego wyniku przez 1 tydzień powoduje, że typem integralnym jest liczba tygodni między dwiema datami.

Liczba tygodni odbywa się tak samo, jak w ogólnej liczbie tygodni wyjątkiem jednego startów z bieżącego dnia zamiast ostatniego wdroku: sd - first_wd.

Fakt 3: Liczba dni tygodnia i całkowita liczba dni tygodnia w miesiącu

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

Działa to tak, jak w Fact 2, tyle że zaczynamy od pierwszej i ostatniej wdpary pary rok-miesiąc y/mzamiast przez cały rok.

Fakt 4: Liczba dni w roku

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

Kod mówi sam za siebie.

Fakt 5 Liczba dni w miesiącu

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

Wyrażenie y/m/lastto ostatni dzień pary rok-miesiąc y/mi oczywiście y/m/1pierwszy dzień miesiąca. Oba są konwertowane na sys_days, aby można je było odjąć, aby uzyskać liczbę dni między nimi. Dodaj 1 dla liczby opartej na 1.

Posługiwać się

info można użyć w następujący sposób:

info(December/26/2019);

lub tak:

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

Oto przykładowy wynik:

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.

Edytować

Dla tych, którzy nie lubią „konwencjonalnej składni”, dostępna jest pełna „składnia konstruktora”.

Na przykład:

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

można zastąpić:

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
źródło
5
To nowe nadużycie operatora podziału jest nawet gorsze niż stare nadużycie operatorów bitshift. Zasmuca mnie to :(
Dave
2
Mówiąc poważniej, czy mogę zasugerować przeniesienie niektórych wstępnie obliczonych zmiennych do sekcji, które ich używają? Jest trochę niewygodne, gdy trzeba przewijać w górę i w dół, aby zobaczyć, skąd pochodzą wartości i jak zostały wygenerowane. I możesz trochę zaśmiecać swoje rzeczy w ciągu roku, najpierw wykonując podział, tak jak robiłeś to przez tygodnie.
Dave
1
Całkowicie się nie zgadzam. Wygląda dobrze, jest łatwy do zrozumienia, a zwłaszcza łatwiejszy do odczytania niż wersja bardziej szczegółowa.
Cássio Renan
@ CássioRenan może być, ale pamiętaj, że nadużycie składni dość często wiąże się z nieoczekiwanym zachowaniem. Przy wyżej wspomnianych przesunięciach bitowych np. Zwróć uwagę na zachowanie std::cout << "a*b = " << a*b << "; a^b = " << a^b << '\n';(które na szczęście prawie zawsze jest łapane w czasie kompilacji, ale nadal jest uciążliwe). Byłbym więc ostrożny przy użyciu tego nowego nadużycia operatora oddziału.
Ruslan
@ Ruslan Ostrożność jest zawsze uzasadniona przy każdej nowej bibliotece. Właśnie dlatego ten został przetestowany publicznie od 2015 roku. Informacje zwrotne od klientów zostały ponownie włączone do projektu. Nie został zaproponowany do standaryzacji, dopóki nie miał solidnych podstaw lat pozytywnych doświadczeń w terenie. W szczególności użycie operatorów zostało zaprojektowane z myślą o pierwszeństwie operatorów, szeroko przetestowane w terenie i zawiera równoważne „API konstruktora”. Zobacz star-history.t9t.io/#HowardHinnant/date&google/cctz i youtube.com/watch?v=tzyGjOm8AKo .
Howard Hinnant