Desafio
Encontre o menor regex que
- valida, ou seja, combina, todas as datas possíveis no calendário gregoriano prolético (que também se aplica a todas as datas anteriores à sua primeira adoção em 1582) e
- não corresponde a nenhuma data inválida.
Resultado
A saída é, portanto, verdadeira ou falsa.
Entrada
A entrada está em qualquer um dos três formatos de data ISO 8601 expandidos - sem hora.
Os dois primeiros são ±YYYY-MM-DD
(ano, mês, dia) e ±YYYY-DDD
(ano, dia). Ambos precisam de revestimento especial para o dia do salto. Eles são ingenuamente correspondidos separadamente por esses RXs estendidos:
(?<year>[+-]?\d{4,})-(?<month>\d\d)-(?<day>\d\d)
(?<year>[+-]?\d{4,})-(?<doy>\d{3})
O terceiro formato de entrada é ±YYYY-wWW-D
(ano, semana, dia). É o complicado por causa do padrão complexo da semana dos saltos.
(?<year>-?\d{4,})-W(?<week>\d\d)-(?<dow>\d)
Uma verificação de validade básica, mas insuficiente, para os três combinados, seria mais ou menos assim:
[+-]?\d{4,}-((0\d|1[0-2])-([0-2]\d|3[01]) ↩
|([0-2]\d\d|3[0-5]\d|36[0-6]) ↩
|(W([0-4]\d|5[0-3])-[1-7]))
Condições
Um ano bissexto no calendário gregoriano proléptico contém o dia bissexto …-02-29
e, portanto, tem 366 dias, portanto, …-366
existe. Isso acontece em qualquer ano cujo número ordinal é divisível por 4, mas não por 100, a menos que também seja divisível por 400. O
ano zero existe neste calendário e é um ano bissexto.
Um longo ano no calendário semanal da ISO contém uma 53ª semana, que pode ser denominada " semana bissexta ". Isso acontece em todos os anos em que 1º de janeiro é quinta-feira e, adicionalmente, em todos os anos bissextos em que é quarta-feira. Ocorre geralmente a cada 5 ou 6 anos, em um padrão aparentemente irregular.
Um ano tem pelo menos 4 dígitos. Anos com mais de 10 dígitos não precisam ser suportados, porque isso é próximo o suficiente da idade do universo (cerca de 14 bilhões de anos). O sinal de adição inicial é opcional, embora o padrão real sugira que seja necessário por anos com mais de 4 dígitos.
Datas parciais ou truncadas, ou seja, com precisão menor que o dia, não devem ser aceitas.
As partes da notação de data, por exemplo, o mês, não precisam ser correspondidas por um grupo que possa ser referenciado.
Regras
Isso é código-golfe. A regex mais curta sem o código executado vence. Atualização: Você pode usar recursos como grupos recursivos e equilibrados, mas será multado por um fator de 10, com o qual a contagem de caracteres será multiplicada! Agora, isso é diferente das regras do código hard golf: Regex para divisibilidade por 7 . A resposta anterior vence um empate.
Casos de teste
Testes válidos
2015-08-10
2015-10-08
12015-08-10
-2015-08-10
+2015-08-10
0015-08-10
1582-10-10
2015-02-28
2016-02-29
2000-02-29
0000-02-29
-2000-02-29
-2016-02-29
200000-02-29
2016-366
2000-366
0000-366
-2016-366
-2000-366
2015-081
2015-W33-1
2015-W53-7
2015-08-10
O último é opcionalmente válido, ou seja, os espaços à esquerda e à direita nas sequências de entrada podem ser cortados.
Formatos inválidos
-0000-08-10 # that's an arbitrary decision
15-08-10 # year is at least 4 digits long
2015-8-10 # month (and day) is exactly two digits long, i.e. leading zero is required
015-08-10 # year is at least 4 digits long
20150810 # though a valid ISO format, we require separators; could also be interpreted as a 8-digit year
2015 08 10 # separator must be hyphen-minus
2015.08.10 # separator must be hyphen-minus
2015–08–10 # separator must be hyphen-minus
2015-0810
201508-10 # could be October in the year 201508
2015 - 08 - 10 # no internal spaces allowed
2015-w33-1 # letter ‘W’ must be uppercase
2015W33-1 # it would be unambiguous to omit the separator in front of a letter, but not in the standard
2015W331 # though a valid ISO format we require separators
2015-W331
2015-W33 # a valid ISO date, but we require day-precision
2015W33
Datas inválidas
2015 # a valid ISO format, but we require day-precision
2015-08 # a valid ISO format, but we require day-precision
2015-00-10 # month range is 1–12
2015-13-10 # month range is 1–12
2015-08-00 # day range is 1–28 through 31
2015-08-32 # max. day range is 1–31
2015-04-31 # day range for April is 1–30
2015-02-30 # day range for February is 1–28 or 29
2015-02-29 # day range for common February is 1–28
2100-02-29 # most century years are non-leap
-2100-02-29 # most century years are non-leap
2015-000 # day range is 1–365 or 366
2015-366 # day range is 1–365 in common years
2016-367 # day range is 1–366 in leap years
2100-366 # most century years are non-leap
-2100-366 # most century years are non-leap
2015-W00-1 # week range is 1–52 or 53
2015-W54-1 # week range is 1–53 in long years
2016-W53-1 # week range is 1–52 in short years
2015-W33-0 # day range is 1–7
2015-W33-8 # day range is 1–7
fonte
Respostas:
PCRE (também Perl), 778 bytes
Incluí os delimitadores na contagem de bytes para mostrar que ele não depende de nenhum sinalizador.
Ele não coincidir com datas válidas dentro de outras cadeias, como
1234-56-89 2016-02-29 9876-54-32
. A regex é mais curta, não verificando um máximo de 10 dígitos para o ano.Ampliado com comentários:
fonte
(?!…)
expressões em comparação à minha solução.(?!…)
expressões salvam apenas alguns bytes cada. Reduzi muitos bytes combinando três dos padrões de ano positivo / negativo de semana do ano / dia da semana em um cada. Os últimos não correspondem um ao outro. Então, eu tenho 8 sub-padrões longos até 5. Além disso, uma vez que|20|25|
é o mesmo comprimento que|2[05]|
eu fui para a opção mais legível.-0000-08-10
e não corresponde␠2015-08-10␠
ao espaço em branco à esquerda e à direita, mas como ambas foram decisões arbitrárias ou recursos opcionais, deixarei isso para lá.W(?!00)([0-4]\d|51|52)-[1-7]
deve ser algo equivalente aW(?!00)([0-4]\d|5[0-2])-[1-7]
. Isso adiciona um caractere ao comprimento. 779PCRE:
603940947949956 bytesNota: Alguns pares de parênteses podem ser descartados.
Divisibilidade por 4
Os múltiplos de 4 se repetem em um padrão simples:
20, 24, 28, 32, 36,
40, 44, 48, 52, 56,
60, 64, 68, 72, 76,
80, 84, 88, 92, 96, ...
Isso, ou o inverso, poderia ser correspondido por uma expressão regular igualmente simples para todos os números de dois dígitos com zero à esquerda:
Ele poderia economizar alguns bytes se houvesse classes de caracteres para dígitos pares e ímpares (como
\o
e\e
), mas não existem até onde eu saiba.Anos
Essa expressão seria suficiente para o calendário juliano, mas a detecção gregoriana do ano bissexto precisa seguir um caso especial,
00
com divisibilidade do século em 4:Isso precisaria de algumas alterações para ilegalizar
-0000-…
(junto com-00000-…
etc.) ou para aplicar o sinal de mais para números positivos de ano com mais de 4 dígitos. O último seria bastante simples, mas não é necessário:Dia do ano
As datas ordinais de três dígitos são bastante simples, basta restringir
-366
a anos bissextos (e não permitir-000
).Dia do mês do ano
Os sete meses com 31 dias são
01
janeiro,03
março,05
maio,07
julho,08
agosto,10
outubro e12
dezembro. Apenas quatro meses têm exatamente 30 dias,04
abril,06
junho,09
setembro e11
novembro. Finalmente,02
fevereiro tem 28 dias em anos comuns e 29 em anos bissextos. Podemos primeiro construir uma expressão regular para os dias sempre válidos01
através de28
e, em seguida, adicionar casos especiais.Não deve ser mês nem dia
00
que não foi coberto por uma versão anterior.Dia da semana do ano
Todos os anos incluem 52 semanas
Anos longos que incluem
-W53
repetição em um ciclo de 400 anos, por exemplo, adicione 2000 para o ciclo atual e encontre o ano atual na terceira entrada:Cada um dos quatro séculos tem um padrão único. Provavelmente não há muito espaço para otimização.
04|09|15|20|26|32|37|43|48|54|60|65|71|76|82|88|93|99
05|11|16|22|28|33|39|44|50|56|61|67|72|78|84|89|95
01|07|12|18|24|29|35|40|46|52|57|63|68|74|80|85|91|96
03|08|14|20|25|31|36|42|48|53|59|64|70|76|81|87|92|98
Podemos agrupar por um dígito para descobrir que podemos salvar dois bytes ou mais:
0[49]|15|2[06]|3[27]|4[38]|54|6[05]|7[16]|8[28]|9[39]
05|1[16]|2[28]|3[39]|44|5[06]|6[17]|7[28]|8[49]|95
0[17]|1[28]|2[49]|35|4[06]|5[27]|6[38]|74|8[05]|9[16]
0[38]|14|2[05]|3[16]|4[28]|5[39]|64|7[06]|8[17]|9[28]
[26]0|71|[38]2|[49]3|[05]4|15|[27]6|37|[48]8|[09]9
50|[16]1|[27]2|33|[48]4|[09]5|[15]6|67|[27]8|[38]9
[48]0|[09]1|[15]2|63|[27]4|[38]5|[49]6|[05]7|[16]8|29
[27]0|[38]1|[49]2|[05]3|[16]4|25|[37]6|87|[049]8|[5]9
O número do século é facilmente correspondido novamente por uma variação da expressão da divisibilidade.
[02468][048]|[13579][26]
[02468][159]|[13579][37]
[02468][26]|[13579][048]
[02468][37]|[13579][159]
Até agora, isso funciona apenas para anos positivos, incluindo o ano zero. Para anos negativos, temos que subtrair os valores da lista acima de 400 e fazer o resto novamente, porque o padrão não é simétrico.
02|08|13|19|24|30|36|41|47|52|58|64|69|75|80|86|92|97
04|09|15|20|26|32|37|43|48|54|60|65|71|76|82|88|93|99
05|11|16|22|28|33|39|44|50|56|61|67|72|78|84|89|95
01|07|12|18|24|29|35|40|46|52|57|63|68|74|80|85|91|96
ou
0[28]|1[39]|24|3[06]|4[17]|5[28]|6[49]|75|8[06]|9[27]
0[49]|15|2[06]|3[27]|4[38]|54|6[05]|7[16]|8[28]|9[39]
0[51]|16|2[28]|3[39]|44|5[06]|6[17]|7[28]|8[49]|95
0[17]|1[28]|2[49]|35|4[06]|5[27]|6[38]|74|8[05]|9[16]
Juntando tudo
Qualquer ano
Adições de ano bissexto
Adições de ano de semana bissexta
fonte
\s*
.