Usando C ++ 20 chrono, como calcular vários fatos sobre uma data

19

https://www.timeanddate.com/date/weekday.html calcula vários fatos sobre um dia do ano, por exemplo:

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

Dada uma data arbitrária, como esses números podem ser calculados com a especificação crono C ++ 20 ?

Howard Hinnant
fonte
2
"... e todos sabemos quando a ISO 1 é a semana 1, certo? ..." - "Não, mas eu tenho uma biblioteca" ... :-) - Bravo Howard!
Ted Lyngmo
Imagem tirada de stackoverflow.com/q/59391132/560648 (agora excluída). Pena que foi excluído, pois isso deveria ter sido uma resposta para essa pergunta.
Lightness Races em órbita
Corrigir. Votei em reabrir essa.
Howard Hinnant

Respostas:

22

Isso é notavelmente fácil com a especificação C ++ 20 chrono . Abaixo, mostro uma função que insere uma data arbitrária e imprime essas informações em cout. Embora no momento da redação deste artigo, a especificação do cronógrafo C ++ 20 ainda não estivesse sendo fornecida, ela é aproximada por uma biblioteca de código aberto e gratuita . Para que você possa experimentar hoje e incluí-lo em aplicativos de remessa, desde que adote o C ++ 11 ou posterior.

Esta resposta assumirá a forma de uma função:

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

sys_days é uma precisão do dia time_point na system_clockfamília. Isso significa que é simplesmente uma contagem de dias desde 1970-01-01 00:00:00 UTC. O alias de tipo sys_daysé novo no C ++ 20, mas o tipo subjacente está disponível desde o C ++ 11 ( time_point<system_clock, duration<int, ratio<86400>>>). Se você usar a biblioteca de visualização C ++ 20 de código aberto , sys_daysestá em namespace date.

O código abaixo assume a função local:

using namespace std;
using namespace std::chrono;

para reduzir a verbosidade. Se você estiver experimentando a biblioteca de visualização C ++ 20 de código aberto , também assuma:

using namespace date;

Título

Para imprimir as duas primeiras linhas é simples:

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

Basta pegar a data sde usar formatcom os familiares strftime/ put_timesinalizadores para imprimir a data e o texto. A biblioteca de visualização C ++ 20 de código aberto ainda não integrou o biblioteca fmt e, portanto, usa a sequência de formatos levemente alterada "%d %B %Y is a %A\n".

Isso produzirá (por exemplo):

26 December 2019 is a Thursday

Additional facts

Resultados intermediários comuns calculados uma vez

Esta seção da função foi escrita por último, porque ainda não se sabe quais cálculos serão necessários várias vezes. Mas depois que você souber, aqui está como calculá-los:

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;

Precisamos dos campos de ano e mês de sde weekday(dia da semana). É eficiente calculá-los de uma vez por todas dessa maneira. Também precisaremos (várias vezes) do primeiro e do último dia do ano atual. É difícil dizer neste momento, mas é eficiente armazenar esses valores como tipo, sys_dayspois seu uso subsequente é apenas com aritmética orientada para o dia, que sys_daysé muito eficiente em (velocidades abaixo de nanossegundos).

Fato 1: número do dia do ano e número de dias restantes no ano

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

Isso imprime o número do dia do ano, com 1º de janeiro sendo o dia 1 e, em seguida, também imprime o número de dias restantes no ano, sem incluir sd. O cálculo para fazer isso é trivial. Dividir cada resultado por days{1}é uma maneira de extrair o número de dias dentro dne dlem um tipo integral para fins de formatação.

Fato 2: Número deste dia da semana e número total de dias da semana no ano

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é o dia da semana (segunda a domingo) computado na parte superior deste artigo. Para realizar esse cálculo, precisamos primeiro das datas do primeiro e do último wdno ano y. y/1/wd[1]é o primeiro wdde janeiro e y/12/wd[last]o último wdde dezembro.

O número total de wds no ano é apenas o número de semanas entre essas duas datas (mais 1). A subexpressãolast_wd - first_wd é o número de dias entre as duas datas. Dividir esse resultado por 1 semana resulta em um tipo integral, mantendo o número de semanas entre as duas datas.

O número da semana é feito da mesma maneira que o número total de semanas, exceto um começa com o dia atual em vez do último wddo ano: sd - first_wd.

Fato 3: número deste dia da semana e número total de dias da semana no mês

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

Isso funciona exatamente como o Fato 2, exceto que começamos com o primeiro e o último wds do par ano-mês em y/mvez do ano inteiro.

Fato 4: Número de dias no ano

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

O código praticamente fala por si.

Fato 5 Número de dias no mês

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

A expressão y/m/lasté o último dia do par ano-mês y/me, claro, y/m/1é o primeiro dia do mês. Ambos são convertidos emsys_days para que possam ser subtraídos para obter o número de dias entre eles. Adicione 1 para a contagem baseada em 1.

Usar

info pode ser usado assim:

info(December/26/2019);

ou assim:

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

Aqui está o exemplo de saída:

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.

Editar

Para aqueles que não gostam da "sintaxe convencional", existe uma "sintaxe de construtor" completa que pode ser usada.

Por exemplo:

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

pode ser substituído por:

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
fonte
5
Esse novo abuso do operador de divisão é ainda pior do que o antigo abuso dos operadores de deslocamento de bits. Isso me deixa triste :(
Dave
2
Em uma observação mais séria, posso sugerir que você mova algumas de suas variáveis ​​pré-calculadas para as seções que as utilizam? É um pouco estranho seguir quando é preciso rolar para cima e para baixo para ver de onde vêm os valores e como eles foram gerados. E você pode desorganizar um pouco as coisas do dia do ano fazendo a divisão primeiro, como fez durante as semanas.
21419 Dave
11
Discordo completamente. Parece bom, é fácil de entender e, principalmente, é mais fácil de ler do que a versão mais detalhada.
Cássio Renan
@ CássioRenan pode ser, mas lembre-se de que o abuso de sintaxe geralmente vem com um comportamento inesperado. Com as mudanças de bits acima mencionadas, por exemplo, observe o comportamento de std::cout << "a*b = " << a*b << "; a^b = " << a^b << '\n';(que, felizmente, quase sempre é capturado em tempo de compilação, mas ainda é um aborrecimento). Portanto, eu seria cauteloso ao usar esse novo abuso de operador de divisão.
Ruslan
@Ruslan Cuidado sempre é garantido com qualquer nova biblioteca. É por isso que este foi testado gratuitamente e publicamente desde 2015. O feedback dos clientes foi incorporado novamente ao design. Não foi proposto para padronização até ter uma base sólida de anos de experiência de campo positiva. Em particular, o uso de operadores foi projetado com a precedência do operador em mente, amplamente testado em campo e vem com uma "API construtora" equivalente. Veja star-history.t9t.io/#HowardHinnant/date&google/cctz e youtube.com/watch?v=tzyGjOm8AKo .
Howard Hinnant 13/01