Estudamos o calendário

Este artigo saiu de uma pergunta que me fiz ontem.
"Existe um ano em que nem um mês começa na segunda-feira?"
À primeira vista, sim. Um ano pode começar a partir de qualquer dia da semana, e meses também começam em diferentes dias da semana. Existem muitas opções, provavelmente, haverá mais de um ano.

Então pensei no primeiro minuto depois que me perguntei. Isso deve ser provado. Passe por todos os anos, por exemplo. Uma maneira simples e rápida, mas não interessante. Provar matematicamente era uma ideia muito mais tentadora, mas eu não entendia completamente como abordar isso. Portanto, comecei a escrever a duração de cada mês no papel.

Aqui vale ressaltar que falaremos mais sobre o calendário gregoriano , segundo o qual vivemos desde 1918. No entanto, parte do raciocínio será verdadeiro para Julian .

De fato, esse ano não existe. Vamos descobrir o porquê.

Parte 1. Meses


Primeiro, lembre-se de quantos dias em cada mês:
JanFevereiroMarçoAbrMaioJunhoJulhoAgostoSetOutNovDez
3128/2931303130313130313031
Agora vamos ver quantos dias em cada mês são mais de quatro semanas.
JanFevereiroMarçoAbrMaioJunhoJulhoAgostoSetOutNovDez
30/13232332323
Nesse ponto, surge a seguinte idéia. Se você adicionar 7 dias à data, o dia da semana não será alterado. Trabalhos aritméticos modulares. A partir daqui, é fácil entender que, se houver mais dois dias em um mês do que em quatro semanas, o primeiro dia do próximo mês será alterado em dois dias da semana em relação ao primeiro dia do mês atual. Enfim,
se houver (28 + N) dias em um mês, o primeiro dia do próximo mês será alterado em N dias em relação ao dia da semana no primeiro dia do mês atual.
Por exemplo, este ano janeiro começou na terça-feira, e fevereiro começou na sexta-feira. Ter + 3 = sex

Quanto custa o dia da semana no primeiro dia de um determinado mês? Para encontrar isso, você precisa somar os dias "excedentes" ao longo de quatro semanas em todos os meses anteriores. A tabela mostra os turnos relativos ao dia da semana em primeiro de janeiro. A primeira linha é para um ano sem salto, a segunda é para um ano bissexto.
JanFevereiroMarçoAbrMaioJunhoJulhoAgostoSetOutNovDez
0 0336811131619212426
0 0347912141720222527
Mas isso não parece muito revelador, e sabemos que um turno de sete dias não muda o dia da semana. Portanto, agora escrevemos na tabela os resíduos da divisão do total de turnos por 7.
JanFevereiroMarçoAbrMaioJunhoJulhoAgostoSetOutNovDez
0 0336146250 035
0 0340 0250 036146
Agora outra coisa! É claramente visto como determinar o dia da semana no primeiro dia de qualquer mês se o dia da semana em primeiro de janeiro for conhecido. Você só precisa adicionar um turno para o mês de interesse. Conheço o padrão de fevereiro a março e novembro desde o ensino médio, mas não percebi os outros.

Temos a resposta para a pergunta no início do artigo.
Como para as duas variantes do ano na tabela há todos os turnos de 0 a 6, em qualquer ano há um mês que começa em um dia específico da semana.
Mas agora você pode fazer outras perguntas. Por exemplo, "em que anos há apenas um mês?" ou "em que anos desses meses é o número máximo desses meses?" Para fazer isso, você deve poder determinar o dia da semana em primeiro de janeiro de qualquer ano.

Parte 2. Anos


Quando aprendi a programar, e isso foi na 10ª série da escola em PascalABC, uma das primeiras tarefas sérias foi implementar um procedimento que imprima um calendário para o ano, que foi aprovado como argumento. Tivemos dicas sobre quais funções implementar. Em geral, resumia-se a contar os dias entre duas datas: a referência e a atual, para determinar o dia da semana em 1º de janeiro do ano desejado.

Essa abordagem funcionou, mas a velocidade dependia de quão próximo o ano exigido da referência. Isso me chateou, mas não consegui encontrar algo melhor então. Agora, chegou o momento perfeito para entender isso completamente.

Os anos bissextos no calendário gregoriano são atribuídos da seguinte forma:
  • um ano cujo número é múltiplo de 400 é um ano bissexto
  • os anos restantes, cujo número é um múltiplo de 100, são sem salto
  • os anos restantes, cujo número é múltiplo de 4, são anos bissextos
  • o resto dos anos são sem salto
Esta descrição mostra que o ciclo de salto tem um período de 400 anos. Mas não está claro se esses ciclos de quatrocentos anos começarão no mesmo dia da semana.

Observe que o primeiro de janeiro de um ano para o outro é alterado em um ou dois dias da semana e escreva
algum 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(); } 


Os dias da semana são exibidos no dia primeiro de janeiro de cada ano, de 1801 a 3000. Segunda-feira é designada como "0", terça-feira como "1", etc. Apresentaremos tudo na forma de uma tabela de dois ciclos completos de quatrocentos anos e duas metades. Séculos vão horizontalmente, a vertical do ano nesses séculos. Nas células na interseção do século e do ano, está escrito o dia da semana em que este ano começou. Por exemplo, o dia da semana que 1997 começou é no cruzamento da coluna "1900" e da linha "97". É quarta-feira. Versão completa da tabela: parte 1 , parte 2 .



Na tabela, você pode notar imediatamente duas coisas: os ciclos de quatrocentos anos realmente começam no mesmo dia da semana (2001, 2401 e 2801; segunda-feira) e, em vez de 2000, existem "mil e novecentos". O último foi feito de propósito, para maior comodidade. O primeiro fato nos permite seguir em frente sem obstáculos.
No calendário gregoriano, todos os quatrocentos anos começam na segunda-feira.
Mas o mais interessante está na versão completa da tabela. Você pode descobrir que todo século em um ciclo de quatrocentos anos consiste em um ciclo repetido de vinte e oito anos:
0 0123560 013456123460 0124560 02345

O primeiro século começa com uma mudança no ciclo igual a 0, o segundo com uma mudança de 4, o terceiro com uma mudança de 8 e o quarto com uma mudança de 12. Para isso, a tabela é apresentada na forma em que existem "centésimos" de um século e não há zero. Vale dizer que no total existem 14 opções diferentes para o ano. Em um ciclo de vinte e oito anos, uma vez para cada dia da semana, o início de um ano bissexto cai e três vezes o início de um ano não bissexto.

Agora podemos determinar o dia da semana para qualquer data sem usar datas de referência. Para fazer isso, precisamos entender em que século, dentro de um ciclo de quatrocentos anos, é um ano, e qual é a sua conta neste século. De acordo com a tabela, determinamos o dia da semana em primeiro de janeiro do ano e com a ajuda da primeira parte do artigo - o dia da semana no dia específico do mês desejado. Em vez de mil palavras
escreveremos mais um pouco de 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; } 


Atualização de 03/03/2019


Se você imaginar um ciclo de vinte e oito anos em uma tabela,

 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 

fica claro como você pode calcular a mudança do dia da semana para 1º de janeiro:

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

Dado isso, além do fato de que as compensações por meses em um ano bissexto podem ser calculadas através das compensações em um ano não bissexto, escrevemos
próxima função
 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); } 


Assim, você pode calcular o dia da semana para qualquer data, sabendo apenas 12 números: a mudança dos dias da semana para o primeiro dia de cada mês.

Parte 3. Resumo


Com apenas duas tabelas, você pode determinar o dia da semana para qualquer data sem usar datas de referência.

A sequência dos dias da semana em 1º de janeiro em um ciclo de vinte e oito anos:
0 0123560 013456123460 0124560 02345

E uma tabela de compensações nos dias úteis durante o primeiro dia de cada mês, para os anos não bissextos e bissextos:
JanFevereiroMarçoAbrMaioJunhoJulhoAgostoSetOutNovDez
0 0336146250 035
0 0340 0250 036146
No momento em que escrevi o artigo, encontrei em Habré dois tópicos semelhantes: um e dois . O autor do primeiro, usando uma tabela especial, mostra como encontrar na mente o dia da semana para datas nos séculos XX e XXI. A tabela que ele apresentou contém 56 números. O algoritmo proposto no artigo usa a tabela dos dias da semana e duas tabelas de deslocamento contendo (28 + 2 * 12) = 52 números que você precisa lembrar. Todo o código fonte está no GitHub .

Um fato interessante: de 1 a 13 de fevereiro de 1918, nenhuma pessoa nasceu na Rússia soviética.

Faça perguntas a si mesmo aos domingos pela manhã =)

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


All Articles