Usando C ++ 20 chrono, cómo calcular varios hechos sobre una fecha

19

https://www.timeanddate.com/date/weekday.html calcula varios datos sobre un día del año, por ejemplo:

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

Dada una fecha arbitraria, ¿cómo se pueden calcular estos números con la especificación cronológica C ++ 20 ?

Howard Hinnant
fuente
2
"... y todos sabemos cuándo es ISO semana 1, ¿verdad? ..." - "No, pero tengo una biblioteca" ... :-) - ¡Bravo Howard!
Ted Lyngmo
Imagen tomada de stackoverflow.com/q/59391132/560648 (ahora eliminada). Es una pena que se haya eliminado ya que esto debería haber sido una respuesta a esa pregunta.
Carreras de ligereza en órbita el
Correcto. Voté para reabrir ese.
Howard Hinnant

Respuestas:

22

Esto es notablemente fácil con la especificación cronológica C ++ 20 . A continuación muestro una función que ingresa una fecha arbitraria e imprime esta información cout. Aunque en el momento de escribir este artículo, la especificación cronológica C ++ 20 aún no se envía, se aproxima a una biblioteca gratuita de código abierto . Por lo tanto, puede experimentar con él hoy e incluso incluirlo en las aplicaciones de envío siempre que adopte C ++ 11 o posterior.

Esta respuesta tomará la forma de una función:

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

sys_daysEs un día de precisión time_pointen la system_clockfamilia. Eso significa que es simplemente un recuento de días desde 1970-01-01 00:00:00 UTC. El alias de tipo sys_dayses nuevo con C ++ 20, pero el tipo subyacente ha estado disponible desde C ++ 11 ( time_point<system_clock, duration<int, ratio<86400>>>). Si usa la biblioteca de vista previa de código abierto C ++ 20 , sys_daysestá en namespace date.

El siguiente código asume la función local:

using namespace std;
using namespace std::chrono;

para reducir la verbosidad Si está experimentando con la biblioteca de vista previa de código abierto C ++ 20 , también asuma:

using namespace date;

Bóveda

Para generar las dos primeras líneas es simple:

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

Simplemente tome la fecha sdy úsela formatcon las marcas familiares strftime/ put_timepara imprimir la fecha y el texto. La biblioteca de vista previa de código abierto C ++ 20 aún no ha integrado la biblioteca fmt , por lo que utiliza la cadena de formato ligeramente alterada "%d %B %Y is a %A\n".

Esto generará (por ejemplo):

26 December 2019 is a Thursday

Additional facts

Resultados intermedios comunes calculados una vez

Esta sección de la función se escribe en último lugar, porque todavía no se sabe qué cálculos se necesitarán varias veces. Pero una vez que sepa, aquí es cómo calcularlos:

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;

Necesitaremos los campos de año y mes de sd, y el weekday(día de la semana). Es eficiente calcularlos de una vez por todas de esta manera. También necesitaremos (varias veces) el primer y último día del año actual. Es difícil saberlo en este momento, pero es eficiente almacenar estos valores como tipo, sys_daysya que su uso posterior es solo con aritmética orientada al día, que sys_dayses muy eficiente a (velocidades inferiores a nanosegundos).

Dato 1: número de días del año y días restantes del año

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

Esto imprime el número de día del año, siendo el 1 de enero el día 1, y luego también imprime el número de días restantes en el año, sin incluir sd. El cálculo para hacer esto es trivial. Dividiendo cada resultado por days{1}es una manera de extraer el número de días en dny dlen un tipo integral para formatear propósitos.

Dato 2: número de este día de la semana y número total de días de la semana en el año

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

wdes el día de la semana (de lunes a domingo) calculado en la parte superior de este artículo. Para realizar este cálculo, primero necesitamos las fechas de la primera y la última wddel año y. y/1/wd[1]es el primero wden enero y y/12/wd[last]es el último wden diciembre.

El número total de wds en el año es solo el número de semanas entre estas dos fechas (más 1). La subexpresión last_wd - first_wdes el número de días entre las dos fechas. Dividir este resultado por 1 semana da como resultado un tipo integral que contiene el número de semanas entre las dos fechas.

El número de la semana se realiza del mismo modo que el número total de semanas, excepto uno comienza con el día actual en lugar de la última wdparte del año: sd - first_wd.

Hecho 3: Número de este día de la semana y número total de días de la semana en el mes

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

Esto funciona igual que el Hecho 2, excepto que comenzamos con el primer y último wds del par año-mes en y/mlugar de todo el año.

Dato 4: número de días en el año

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

El código habla más o menos por sí mismo.

Hecho 5 Número de días en el mes

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

La expresión y/m/lastes el último día del par año-mes y/my, por supuesto, y/m/1es el primer día del mes. Ambos se convierten para sys_daysque puedan restarse para obtener el número de días entre ellos. Agregue 1 para el recuento basado en 1.

Utilizar

info se puede usar así:

info(December/26/2019);

o así:

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

Aquí hay un ejemplo de salida:

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 aquellos que no son aficionados a la "sintaxis convencional", existe una "sintaxis de constructor" completa que se puede utilizar en su lugar.

Por ejemplo:

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

puede ser reemplazado 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
fuente
55
Este nuevo abuso del operador de división es aún peor que el antiguo abuso de los operadores de bithift. Me pone triste :(
Dave
2
En una nota más seria, ¿puedo sugerir que mueva algunas de sus variables precalculadas a las secciones que las usan? Es un poco difícil de seguir cuando se tiene que desplazarse hacia arriba y hacia abajo para ver de dónde provienen los valores y cómo se generaron. Y puedes desordenar un poco tus cosas del día del año haciendo primero la división, como hiciste durante las semanas.
Dave
1
No estoy de acuerdo por completo. Se ve bien, es fácil de entender y, en particular, es más fácil de leer que la versión más detallada.
Cássio Renan
@ CássioRenan podría serlo, pero recuerde que el abuso de sintaxis a menudo viene con un comportamiento inesperado. Con los cambios de bits antes mencionados, por ejemplo, tenga en cuenta el comportamiento de std::cout << "a*b = " << a*b << "; a^b = " << a^b << '\n';(que, afortunadamente, casi siempre se detecta en el momento de la compilación, pero sigue siendo una molestia). Por lo tanto, sería cauteloso al usar este nuevo abuso de operador de división.
Ruslan
@Ruslan Caution siempre está garantizado con cualquier biblioteca nueva. Es por eso que este ha sido probado pública y libremente desde 2015. Los comentarios de los clientes se han incorporado nuevamente al diseño. No se propuso para la estandarización hasta que tuviera una base sólida de años de experiencia de campo positiva. En particular, el uso de operadores se ha diseñado teniendo en cuenta la precedencia del operador, probado ampliamente en el campo y viene con una "API de constructor" equivalente. Ver star-history.t9t.io/#HowardHinnant/date&google/cctz y youtube.com/watch?v=tzyGjOm8AKo .
Howard Hinnant