Expressão Regular | Anos bissextos e mais

Recentemente, tenho procurado por uma expressão regular para fazer uma verificação da data do lado do cliente e não consegui encontrar uma que satisfaça os seguintes critérios:

  • Tem um intervalo de 1800 – agora
  • Executa a verificação adequada de datas com anos bissextos
  • Formulário MM / DD / AAAA
  • Verificação de data inválida

(Essas restrições estavam fora do meu escopo e são um requisito de acordo com o cliente, apesar dos meus esforços para convencê-los de que essa não era a melhor rota)

Código atual:

$('input').keyup(function() { var regex = /^(?:(0[1-9]|1[012])[\/.](0[1-9]|[12][0-9]|3[01])[\/.](18|19|20)[0-9]{2})$/; $(this).toggleClass('invalid',!regex.test($(this).val())); }); 

Atualizar:

Devo observar que isso é principalmente para ver se uma expressão regular como essa seria possível ( já que o uso de um Regex não é minha escolha neste assunto ). Estou ciente das outras ( e melhores ) opções para validar uma data, no entanto, como mencionado anteriormente – isto é para ver se era possível através de uma expressão regular.

Como é mencionado em outros lugares, expressões regulares quase certamente não são o que você deseja. Mas, tendo dito isso, se você realmente quer uma expressão regular, aqui está como ela é construída:

31 dias meses

 (0[13578]|1[02])[\/.](0[1-9]|[12][0-9]|3[01])[\/.](18|19|20)[0-9]{2} 

30 dias por dia

 (0[469]|11)[\/.](0[1-9]|[12][0-9]|30)[\/.](18|19|20)[0-9]{2} 

1 a 28 de fevereiro sempre válido

 (02)[\/.](0[1-9]|1[0-9]|2[0-8])[\/.](18|19|20)[0-9]{2} 

29 de fevereiro também válido em anos bissextos

 (02)[\/.]29[\/.](((18|19|20)(04|08|[2468][048]|[13579][26]))|2000) 

o que significa que seria isso se você juntar tudo:

 ((0[13578]|1[02])[\/.](0[1-9]|[12][0-9]|3[01])[\/.](18|19|20)[0-9]{2})|((0[469]|11)[\/.](0[1-9]|[12][0-9]|30)[\/.](18|19|20)[0-9]{2})|((02)[\/.](0[1-9]|1[0-9]|2[0-8])[\/.](18|19|20)[0-9]{2})|((02)[\/.]29[\/.](((18|19|20)(04|08|[2468][048]|[13579][26]))|2000)) 

Esta versão é um pouco mais curta, mas um pouco mais difícil de entender.

 ((0[13578]|1[02])[\/.]31[\/.](18|19|20)[0-9]{2})|((01|0[3-9]|1[1-2])[\/.](29|30)[\/.](18|19|20)[0-9]{2})|((0[1-9]|1[0-2])[\/.](0[1-9]|1[0-9]|2[0-8])[\/.](18|19|20)[0-9]{2})|((02)[\/.]29[\/.](((18|19|20)(04|08|[2468][048]|[13579][26]))|2000)) 

Esses scripts são longos e inamovíveis. Deve ficar claro que isso não é uma boa ideia, mas é possível.

Ressalvas:

  • faixa de 1800-2099 (mais pode ser adicionado sem muita dificuldade, mas requer mudanças em 4-6 lugares diferentes)
  • requer dois meses e dias de dígitos (o rigor pode ser removido da expressão em ~ 8 lugares)
  • [\/.] como seperators (8 lugares)
  • Não foi testado (poderíamos verificar isso em todas as combinações de dígitos e comparar com a function de data do javascript? [Prova de que estamos reinventando a roda])

Eu sugiro que você abandone a tentativa de usar expressões regulares para isso. Você é muito melhor analisar a data em suas partes constituintes (mês, dia, ano) e, em seguida, usando comparações numéricas para se certificar de que está no intervalo adequado.

Melhor ainda, veja se a function JavaScript Date.parse fará o que você quer.

A análise de datas com expressões regulares é possível, mas frustrante. É difícil acertar, a expressão é difícil para os magos não-regex entenderem (o que significa que é difícil provar que a coisa está correta), e é lenta em comparação com outras opções.

É assim que eu faria:

 function validate( input ) { var date = new Date( input ); input = input.split( '/' ); return date.getMonth() + 1 === +input[0] && date.getDate() === +input[1] && date.getFullYear() === +input[2]; } 

Uso:

 validate( '2/1/1983' ) // true validate( '2/29/1983' ) // false validate( '2/29/1984' ) // true (1984 is a leap year) 

Demonstração ao vivo: http://jsfiddle.net/9QNRx/

Obviamente, as expressões regulares não são a maneira ideal de fazer isso. Além disso, é muito mais seguro trabalhar com o YYYY-MM-DD (ISO 8601), não com MM/DD/YYYY .

Dito isso, aqui está a expressão regular mais completa para datas entre 01/01/1800 e 31/12/2099:

 ^(((0[1-9]|1[012])\/(?!00|29)([012]\d)|(0[13-9]|1[012])\/(29|30)|(0[13578]|1[02])\/31)\/(18|19|20)\d{2}|02\/29\/((18|19|20)(0[48]|[2468][048]|[13579][26])|2000))$ 

Comprimento: 162 caracteres.

Demolir:

 ^ # start ( ( # non-leap months & days (0[1-9]|1[012])/(?!00|29)([012]\\d) # all months, days 01-28, uses negative lookahead | (0[13-9]|1[012])/(29|30) # all months except feb, days 29,30 | (0[13578]|1[02])/31 # all 31 day months, day 31 only ) / (18|19|20)\\d{2} # all years | 02/29 # leap day / ( (18|19|20)(0[48]|[2468][048]|[13579][26]) # leap years not divisible by 100 | 2000 # leap years divisible by 100 ) ) $ # end 

Aqui está um violino que testa todos os casos de uso de 00/00/1800 a 99/99/2099.

Além disso, para mais diversão, aqui está outro violino que gera a expressão regular mais improvável que ainda funciona, 1205306 caracteres. Parece algo como isto:

 ^(01/01/1800|01/02/1800|01/03/1800|...|12/29/2099|12/30/2099|12/31/2099)$ 

esta expressão regular para o formato AAAA-MM-DD

 ((18|19|20)[0-9]{2}[\-.](0[13578]|1[02])[\-.](0[1-9]|[12][0-9]|3[01]))|(18|19|20)[0-9]{2}[\-.](0[469]|11)[\-.](0[1-9]|[12][0-9]|30)|(18|19|20)[0-9]{2}[\-.](02)[\-.](0[1-9]|1[0-9]|2[0-8])|(((18|19|20)(04|08|[2468][048]|[13579][26]))|2000)[\-.](02)[\-.]29 

Usando o momento (não regex) eu fiz o seguinte:

Supondo que você tenha uma data ISO como um valor de string:

 var isoDate = '2016-11-10'; var parsedIsoDate = moment(isoDate, ['YYYY-MM-DD'], true).format('YYYY-MM-DD'); if (parsedIsoDate !== isoDate) { // Invalid date. } 
 ^(((?:(?:1[6-9]|[2-9]\d)?\d{2})(-)(?:(?:(?:0?[13578]|1[02])(-)31)|(?:(?:0?[1,3-9]|1[0-2])(-)(?:29|30))))|(((?:(?:(?:1[6-9]|[2-9]\d)?(?:0[48]|[2468][048]|[13579][26])|(?:(?:16|[2468][048]|[3579][26])00))))(-)(?:0?2(-)29))|((?:(?:(?:1[6-9]|[2-9]\d)?\d{2})(-)(?:(?:0?[1-9])|(?:1[0-2]))(-)(?:0[1-9]|1\d|2[0-8]))))$ 

Por favor, tente a expressão Reg anterior. Eu tentei várias combinações e encontrei a trabalhar.

Por favor, verifique se isso funciona para você também.

Formato aceito: AAAA-MM-DD

Ano aceite a partir de 1600

Este é o RegEx que uso para validação de data no lado do cliente. Tem um intervalo de 1000 a 2999, valida anos bissextos e, opcionalmente, a parte do tempo. Não é lindo 🙂

 var r = /^(0[1-9]|1\d|2[0-8]|29(?=-\d\d-(?!1[01345789]00|2[1235679]00)\d\d(?:[02468][048]|[13579][26]))|30(?!-02)|31(?=-0[13578]|-1[02]))-(0[1-9]|1[0-2])-([12]\d{3})(\s([01]\d|2[0-3]):([0-5]\d):([0-5]\d))?$/gm; r.test('20-02-2013 10:01:07'); // true r.test('29-02-1700'); // false r.test('29-02-1604 14:01:45'); // true r.test('29-02-1900 20:10:50'); // false r.test('31-12-2000'); // true r.test('31-11-2008 05:05:05'); // false r.test('29-02-2004 05:01:23'); // true r.test('24-06-2014 24:10:05'); // false 

Eu estava tentando validar YYYY-MM-DD, onde YYYY pode ser dois dígitos e MM e DD podem ser um. Isso é o que eu criei. Trata todos os séculos como anos bissextos.

 ((\d\d)?\d\d-((0?(1|3|5|7|8)|10|12)-(31|30|[21]\d|0?[1-9])|(0?(4|6|9)|11)-(31|30|[21]\d|0?[1-9])|0?2-((2[0-8]|1\d)|0?[1-9]))|(\d\d)?((0|2|4|6|8)(0|4|8)|(1|3|5|7|9)(2|6))-0?2-29) 

Adicionando minha resposta apenas para o esporte – caso contrário eu concordo plenamente com @Jim.

Isso corresponderá a anos bissextos, incluindo aqueles com dígitos menores ou maiores que 4.

 ^\d*((((^|0|[2468])[048])|[13579][26])00$)|((0[48]|(^0*|[2468])[048]|[13579][26]))$ 

Um mini caso de teste em Ruby ( ^ substituído por \A e $ por \Z , porque Ruby):

 r = /\A\d*((((\A|0|[2468])[048])|[13579][26])00\Z)|((0[48]|(\A0*|[2468])[048]|[13579][26]))\Z/ 100000.times do |year| leap = year % 4 == 0 && ((year % 100 != 0) || (year % 400 == 0)) leap_regex = !year.to_s[r].nil? if leap != leap_regex print 'Assertion broken:', year, leap, leap_regex, "\n" end end 

((0 [13578] | 1 [02]) [/.] 31 /. [0-9] {2}) | ((01 | 0 [3-9] | 1 [1-2]) /./ [0-9] {2}) | ((0 [1-9] | 1 [0-2]) /./. [0-9] {2}) | ((02) [/.] 29 /.)

A resposta de versão curta não funciona para 10/29 e 10/30 a qualquer ano a versão longa funciona abaixo é um programa de script java simples que escrevi para testar

 import java.util.Date; import java.util.regex.Matcher; import java.util.regex.Pattern; import org.joda.time.LocalDate; import org.joda.time.format.DateTimeFormat; import org.joda.time.format.DateTimeFormatter; public class RegxDateTest { public static void main(String[] args) { // String to be scanned to find the pattern. String line = "This order was placed for QT3000! OK?"; String pattern ="((0[13578]|1[02])[\\/.]31[\\/.](18|19|20)[0-9]{2})|((01|0[3-9]|1[1-2])[\\/.](29|30)[\\/.](18|19|20)[0-9]{2})|((0[1-9]|1[0-2])[\\/.](0[1-9]|1[0-9]|2[0-8])[\\/.](18|19|20)[0-9]{2})|((02)[\\/.]29[\\/.](((18|19|20)(04|08|[2468][048]|[13579][26]))|2000))"; // Create a Pattern object Pattern r = Pattern.compile(pattern); LocalDate startDate = new LocalDate("1950-01-01"); LocalDate endDate = new LocalDate("2020-01-01"); for (LocalDate date = startDate; date.isBefore(endDate); date = date.plusDays(1)) { if (date.toString("MM/dd/yyyy").matches(pattern)) { // System.out.println("This date does match: " + date.toString("MM/dd/yyyy") ); }else{ System.out.println("This date does not match: " + date.toString("MM/dd/yyyy") ); } } String baddate1="02/29/2016"; if (baddate1.matches(pattern)) { System.out.println("This date does match: " + baddate1 ); }else{ System.out.println("This date does not match: " + baddate1 ); } System.out.println("alldone: " ); } 

}

Intereting Posts