Estudiamos el calendario

Este artículo surgió de una pregunta que me hice ayer.
"¿Hay un año en el que ni un solo mes comience el lunes?"
A primera vista, sí. Un año puede comenzar desde cualquier día de la semana, los meses también comienzan cada vez en diferentes días de la semana. Hay muchas opciones, lo más probable es que haya más de un año.

Así que pensé el primer minuto después de que me preguntara. Esto debe ser probado. Ir a través de todos los años, por ejemplo. Una manera simple y rápida, pero no interesante. Probar matemáticamente era una idea mucho más tentadora, pero no entendía completamente cómo abordar esto. Por lo tanto, comencé a escribir en papel la duración de cada mes.

Aquí vale la pena mencionar que hablaremos más sobre el calendario gregoriano , según el cual hemos estado viviendo desde 1918. Sin embargo, parte del razonamiento será cierto para Julian .

De hecho, ese año no existe. Averigüemos por qué.

Parte 1. Meses


Primero, recuerde cuántos días en cada mes:
EneFebreroMarzoAbrMayoJunioJulioAgostoSepOctNovDic
3128/2931303130313130313031
Ahora veamos cuántos días en cada mes son más de cuatro semanas.
EneFebreroMarzoAbrMayoJunioJulioAgostoSepOctNovDic
30/13232332323
En este punto, surge la siguiente idea. Si agrega 7 días a la fecha, el día de la semana no cambiará. Obras aritméticas modulares. A partir de aquí, es fácil comprender que si hay dos días más en un mes que en cuatro semanas, entonces el primer día del mes siguiente cambiará en dos días de la semana en relación con el primer día del mes actual. Como sea
Si hay (28 + N) días en un mes, el primer día del mes siguiente cambiará en N días en relación con el día de la semana del primer día del mes actual.
Por ejemplo, este año enero comenzó el martes, así que febrero comenzó el viernes. Martes + 3 = viernes

¿Cuánto cuesta el día de la semana el primer día de un mes determinado? Para encontrar esto, debe sumar los días de "excedente" durante cuatro semanas en todos los meses anteriores. La tabla muestra los turnos relativos al día de la semana el primero de enero. La primera línea es para un año no bisiesto, la segunda es para un año bisiesto.
EneFebreroMarzoAbrMayoJunioJulioAgostoSepOctNovDic
0 0336 6811131619212426
0 034 47 79 912141720222527
Pero esto no parece muy revelador, y sabemos que un turno de siete días no cambia el día de la semana. Por lo tanto, ahora escribimos en la tabla los residuos de dividir los turnos totales por 7.
EneFebreroMarzoAbrMayoJunioJulioAgostoSepOctNovDic
0 0336 614 46 625 50 035 5
0 034 40 025 50 036 614 46 6
Ahora otra cosa! Se ve claramente cómo determinar el día de la semana el primer día de cualquier mes si se conoce el día de la semana el primero de enero. Solo necesita agregar un turno para el mes de interés. Conozco el patrón de febrero-marzo-noviembre desde la secundaria, pero no noté a los demás.

Obtuvimos la respuesta a la pregunta al comienzo del artículo.
Dado que para ambas variantes del año en la tabla hay todos los cambios de 0 a 6, entonces en cualquier año hay un mes que comienza en algún día específico de la semana.
Pero ahora puedes hacer otras preguntas. Por ejemplo, "¿en qué años solo hay uno de esos meses?" o "¿en qué años de tales meses es el número máximo de tales meses?" Para hacer esto, debe poder determinar el día de la semana el primero de enero de cualquier año.

Parte 2. Años


Cuando aprendí a programar, y esto fue en el décimo grado de la escuela en PascalABC, una de las primeras tareas serias fue implementar un procedimiento que imprima un calendario para el año, que se aprobó como argumento. Teníamos consejos sobre qué funciones implementar. En general, se redujo a contar los días entre dos fechas: la referencia y la actual, para determinar el día de la semana el 1 de enero del año deseado.

Este enfoque funcionó, pero la velocidad dependía de qué tan cerca el año requerido de la referencia. Me molestó, pero no pude encontrar algo mejor entonces. Ahora, el momento perfecto ha llegado a comprender completamente esto.

Los años bisiestos en el calendario gregoriano se asignan de la siguiente manera:
  • un año cuyo número es un múltiplo de 400 es un año bisiesto
  • los años restantes, cuyo número es un múltiplo de 100, no son salto
  • los años restantes, cuyo número es múltiplo de 4, son años bisiestos
  • el resto de los años son sin salto
Esta descripción muestra que el ciclo de salto tiene un período de 400 años. Pero no está claro si esos ciclos de cuatrocientos años comenzarán el mismo día de la semana.

Tenga en cuenta que el primero de enero de año en año se desplaza uno o dos días de la semana, y escriba
Algún código.
bool is_leap_year(int year) { if ((year % 400) == 0) return true; if ((year % 100) == 0) return false; if ((year % 4) == 0) return true; return false; } void first_weekdays_table() { ofstream file("weekdays.txt", ios_base::out); int weekday = 3; for (int i = 1801; i <= 3000; ++i) { file << weekday; if ((i % 100) != 0) { file << " "; } else { file << endl; } weekday += is_leap_year(i) ? 2 : 1; weekday %= 7; } file.close(); } 


Los días de la semana se muestran el primero de enero de cada año, de 1801 a 3000. El lunes se designa como "0", el martes como "1", etc. Presentaremos todo en forma de una tabla de dos ciclos completos de cuatrocientos años y dos mitades. Los siglos van horizontalmente, la vertical del año en estos siglos. En las celdas en la intersección del siglo y el año, se escribe el día de la semana en que comenzó este año. Por ejemplo, el día de la semana en que comenzó 1997 está en la intersección de la columna "1900" y la línea "97". Este es miercoles. Versión completa de la tabla: parte 1 , parte 2 .



En la tabla, puede notar de inmediato dos cosas: los ciclos de cuatrocientos años realmente comienzan el mismo día de la semana (2001, 2401 y 2801; lunes), y en lugar de 2000 hay "mil novecientos." Esto último se hizo a propósito, para mayor comodidad. El primer hecho nos permite avanzar sin obstáculos.
En el calendario gregoriano, los ciclos de cuatrocientos años comienzan el lunes.
Pero lo más interesante radica en la versión completa de la tabla. Puede encontrar que cada siglo dentro de un ciclo de cuatrocientos años consiste en un ciclo repetitivo de veintiocho años:
0 01235 56 60 0134 45 56 61234 46 60 0124 45 56 60 0234 45 5

El primer siglo comienza con un cambio en el ciclo igual a 0, el segundo con un cambio de 4, el tercero con un cambio de 8 y el cuarto con un cambio de 12. Para esto, la tabla se presenta en la forma en que hay "centésimas" de siglo y no hay cero. Vale la pena decir que en total hay 14 opciones diferentes para el año. En un ciclo de veintiocho años, una vez por cada día de la semana, el comienzo de un año bisiesto cae y tres veces el comienzo de un año no bisiesto.

Ahora podemos determinar el día de la semana para cualquier fecha sin usar fechas de referencia. Para hacer esto, necesitamos entender en qué siglo, dentro de un ciclo de cuatrocientos años, es un año, y cuál es su explicación en este siglo. Según la tabla, determinamos el día de la semana el primero de enero del año y, con la ayuda de la primera parte del artículo, el día de la semana en el día específico del mes deseado. En lugar de mil palabras
escribiremos más código.
 int get_weekday(int year, int month, int day) { int weekdays[] = {0, 1, 2, 3, 5, 6, 0, 1, 3, 4, 5, 6, 1, 2, 3, 4, 6, 0, 1, 2, 4, 5, 6, 0, 2, 3, 4, 5}; int shift_not_leap[] = {0, 3, 3, 6, 1, 4, 6, 2, 5, 0, 3, 5}; int shift_leap[] = {0, 3, 4, 0, 2, 5, 0, 3, 6, 1, 4, 6}; bool is_leap = is_leap_year(year); year -= 1; year %= 400; int century = year / 100; year %= 100; int index = (year + (4 * century)) % 28; int weekday = weekdays[index]; weekday += is_leap ? shift_leap[month - 1] : shift_not_leap[month - 1]; weekday += (day - 1); weekday %= 7; return weekday; } 


Actualización del 03/07/2019


Si presentamos el ciclo de veintiocho años en forma de tabla,

 0, 1, 2, 3, 5, 6, 0, 1, 3, 4, 5, 6, 1, 2, 3, 4, 6, 0, 1, 2, 4, 5, 6, 0, 2, 3, 4, 5 

queda claro cómo puede calcular el cambio del día de la semana al 1 de enero:

 weekday = (index + (index / 4)) % 7; 

Dado esto, así como el hecho de que las compensaciones para meses en un año bisiesto pueden calcularse a través de las compensaciones en un año no bisiesto, escribimos
siguiente función
 int get_weekday_c(int year, int month, int day) { int shifts[] = {0, 3, 3, 6, 1, 4, 6, 2, 5, 0, 3, 5}; int shift = shifts[month - 1]; if (is_leap_year(year) and (month > 2)) { shift += 1; }; year = (year - 1) % 400; int century = year / 100; int index = ((4 * century) + (year % 100)) % 28; int weekday = (index + (index / 4)) + shift + (day - 1); return (weekday % 7); } 


Por lo tanto, puede calcular el día de la semana para cualquier fecha, conociendo solo 12 números: el cambio de los días de la semana al primer día de cada mes.

Parte 3. Resumen


Con solo dos tablas, puede determinar el día de la semana para cualquier fecha sin usar fechas de referencia.

La secuencia de días de la semana el 1 de enero en un ciclo de veintiocho años:
0 01235 56 60 0134 45 56 61234 46 60 0124 45 56 60 0234 45 5

Y una tabla de compensaciones entre semana por el primer día de cada mes para los años no bisiestos y bisiestos:
EneFebreroMarzoAbrMayoJunioJulioAgostoSepOctNovDic
0 0336 614 46 625 50 035 5
0 034 40 025 50 036 614 46 6
Al momento de escribir el artículo, encontré en Habré dos temas similares: uno y dos . El autor del primero, usando una tabla especial, muestra cómo encontrar en la mente el día de la semana para las fechas en los siglos XX y XXI. La tabla que presentó contiene 56 números. El algoritmo propuesto en el artículo utiliza la tabla de los días de la semana y dos tablas de desplazamiento que contienen (28 + 2 * 12) = 52 números que debe recordar. Todo el código fuente está en GitHub .

Un hecho interesante: del 1 al 13 de febrero de 1918, ni una sola persona nació en la Rusia soviética.

Hágase preguntas los domingos por la mañana =)

Source: https://habr.com/ru/post/458356/


All Articles