Utilizzando C ++ 20 chrono, come calcolare vari fatti su una data


19

https://www.timeanddate.com/date/weekday.html calcola vari fatti su un giorno dell'anno, ad esempio:

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

Data una data arbitraria, come possono essere calcolati questi numeri con la specifica cronografo C ++ 20 ?


2
"... e sappiamo tutti quando la settimana ISO 1 è, vero? ..." - "No, ma ho una libreria" ... :-) - Bravo Howard!
Ted Lyngmo,

Immagine tratta da stackoverflow.com/q/59391132/560648 (ora eliminata). Peccato che sia stato cancellato in quanto avrebbe dovuto essere una risposta a quella domanda.
Razze di leggerezza in orbita

Corretta. Ho votato per riaprirlo.
Howard Hinnant,

Risposte:


22

Questo è straordinariamente facile con la specifica cronografo C ++ 20 . Di seguito mostro una funzione che inserisce una data arbitraria e stampa queste informazioni su cout. Anche se al momento della stesura di questo documento, la specifica cronografo C ++ 20 non è ancora disponibile, è approssimata da una libreria open source gratuita . Quindi puoi sperimentarlo oggi e persino includerlo nelle applicazioni di spedizione purché adotti C ++ 11 o versioni successive.

Questa risposta assumerà la forma di una funzione:

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

sys_daysè una precisione diurna time_pointin system_clockfamiglia. Ciò significa che è semplicemente un conteggio di giorni dal 1970-01-01 00:00:00 UTC. L'alias del tipo sys_daysè nuovo con C ++ 20, ma il tipo sottostante è disponibile da C ++ 11 ( time_point<system_clock, duration<int, ratio<86400>>>). Se si utilizza la libreria di anteprima open source C ++ 20 , sys_daysè in namespace date.

Il codice seguente assume funzione locale:

using namespace std;
using namespace std::chrono;

per ridurre la verbosità. Se stai sperimentando la libreria di anteprima open source C ++ 20 , supponi anche:

using namespace date;

Intestazione

L'output delle prime due righe è semplice:

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

Basta prendere la data sde utilizzare formatcon i familiari strftime/ put_timeflag per stampare la data e il testo. La libreria di anteprima C ++ 20 open source non ha ancora integrato la libreria fmt e quindi utilizza la stringa di formato leggermente modificata "%d %B %Y is a %A\n".

Questo produrrà (per esempio):

26 December 2019 is a Thursday

Additional facts

Risultati intermedi comuni calcolati una volta

Questa sezione della funzione è scritta per ultima, perché non si sa ancora quali calcoli saranno necessari più volte. Ma una volta che sai, ecco come calcolarli:

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;

Avremo bisogno dei campi dell'anno e del mese sde del weekday(giorno della settimana). È efficiente calcolarli una volta per tutte in questo modo. Avremo anche bisogno (più volte) del primo e dell'ultimo giorno dell'anno in corso. È difficile dirlo a questo punto, ma è efficiente memorizzare questi valori come tipo sys_dayspoiché il loro uso successivo è solo con l'aritmetica orientata al giorno che sys_daysè molto efficiente a (velocità al di sotto dei nanosecondi).

Fatto 1: numero di giorni dell'anno e numero di giorni rimasti nell'anno

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

Ciò stampa il numero del giorno dell'anno, con il 1 ° gennaio come giorno 1, quindi stampa anche il numero di giorni rimanenti nell'anno, escluso sd. Il calcolo per farlo è banale. Dividere ogni risultato per days{1}è un modo per estrarre il numero di giorni in dne dlin un tipo integrale ai fini della formattazione.

Fatto 2: numero di questo giorno feriale e numero totale di giorni feriali nell'anno

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è il giorno della settimana (dal lunedì alla domenica) calcolato all'inizio di questo articolo. Per eseguire questo calcolo abbiamo prima bisogno delle date del primo e dell'ultimo wddell'anno y. y/1/wd[1]è il primo wda gennaio ed y/12/wd[last]è l'ultimo wda dicembre.

Il numero totale di wds nell'anno è solo il numero di settimane tra queste due date (più 1). La sottoespressione last_wd - first_wdè il numero di giorni tra le due date. Dividendo questo risultato per 1 settimana si ottiene un tipo integrale che tiene il numero di settimane tra le due date.

Il numero della settimana viene fatto allo stesso modo come il numero totale di settimane, tranne uno inizia con il giorno corrente invece che l'ultimo wddell'anno: sd - first_wd.

Fatto 3: numero di questo giorno feriale e numero totale di giorni feriali nel mese

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

Funziona proprio come Fact 2, tranne per il fatto che iniziamo con la prima e l'ultima wds della coppia anno-mese y/manziché l'intero anno.

Fatto 4: numero di giorni dell'anno

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

Il codice parla praticamente da solo.

Fatto 5 Numero di giorni del mese

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'espressione y/m/lastè l'ultimo giorno della coppia anno-mese y/me ovviamente y/m/1è il primo giorno del mese. Entrambi vengono convertiti in sys_daysmodo che possano essere sottratti per ottenere il numero di giorni tra di loro. Aggiungi 1 per il conteggio basato su 1.

Uso

info può essere usato in questo modo:

info(December/26/2019);

o in questo modo:

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

Ecco un esempio di 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.

modificare

Per coloro che non amano la "sintassi convenzionale", esiste invece una "sintassi del costruttore" che può essere utilizzata.

Per esempio:

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

può essere sostituito da:

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
Questo nuovo abuso dell'operatore di divisione è persino peggiore del vecchio abuso degli operatori bitrate. Mi rende triste :(
Dave il

2
Su una nota più seria, posso suggerire di spostare alcune delle variabili precalcolate verso il basso nelle sezioni che le usano? È un po 'imbarazzante da seguire quando si deve scorrere su e giù per vedere da dove provengono i valori e come sono stati generati. E puoi disordinare un po 'le tue cose del giorno dell'anno facendo prima la divisione, come hai fatto per le settimane.
Dave,

1
Non sono completamente d'accordo. Sembra buono, è facile da capire e, in particolare, è più facile da leggere rispetto alla versione più dettagliata.
Cássio Renan,

@ CássioRenan potrebbe essere, ma ricorda che l'abuso di sintassi abbastanza spesso viene fornito con un comportamento imprevisto. Con i suddetti spostamenti di bit, ad esempio, notare il comportamento di std::cout << "a*b = " << a*b << "; a^b = " << a^b << '\n';(che, per fortuna, è quasi sempre catturato al momento della compilazione, ma è comunque un fastidio). Quindi sarei cauto quando usi questo nuovo abuso da operatore di divisione.
Ruslan,

@Ruslan Attenzione è sempre garantita con qualsiasi nuova libreria. Ecco perché questo è stato testato pubblicamente e liberamente dal 2015. Il feedback dei clienti è stato incorporato nel design. Non è stato proposto per la standardizzazione fino a quando non ha avuto una solida base di anni di esperienza sul campo positiva. In particolare, l'uso degli operatori è stato progettato tenendo presente la precedenza degli operatori, ampiamente testato sul campo e viene fornito con una "API costruttore" equivalente. Vedi star-history.t9t.io/#HowardHinnant/date&google/cctz e youtube.com/watch?v=tzyGjOm8AKo .
Howard Hinnant,
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.