Pergunta

Estou tentando escrever uma expressão regular que valide uma data.A regex precisa corresponder ao seguinte

  • M/D/AAAA
  • MM/DD/AAAA
  • Meses de um dígito podem começar com zero à esquerda (por exemplo:12/03/2008)
  • Dias de um dígito podem começar com zero à esquerda (por exemplo:3/02/2008)
  • NÃO PODE incluir 30 ou 31 de fevereiro (por exemplo:31/02/2008)

Até agora eu tenho

^(([1-9]|1[012])[-/.]([1-9]|[12][0-9]|3[01])[-/.](19|20)\d\d)|((1[012]|0[1-9])(3[01]|2\d|1\d|0[1-9])(19|20)\d\d)|((1[012]|0[1-9])[-/.](3[01]|2\d|1\d|0[1-9])[-/.](19|20)\d\d)$

Isso corresponde corretamente, EXCETO que ainda inclui 30/02/2008 e 31/02/2008.

Alguém tem uma sugestão melhor?

Editar: eu encontrei a resposta no RegExLib

^((((0[13578])|([13578])|(1[02]))[\/](([1-9])|([0-2][0-9])|(3[01])))|(((0[469])|([469])|(11))[\/](([1-9])|([0-2][0-9])|(30)))|((2|02)[\/](([1-9])|([0-2][0-9]))))[\/]\d{4}$|^\d{4}$

Corresponde a todos os meses válidos que seguem o formato MM/DD/AAAA.

Obrigado a todos pela ajuda.

Foi útil?

Solução

Este não é um uso apropriado de expressões regulares.Seria melhor você usar

[0-9]{2}/[0-9]{2}/[0-9]{4}

e depois verificar os intervalos em uma linguagem de nível superior.

Outras dicas

Aqui está o Reg ex que corresponde a todas as datas válidas, incluindo anos bissextos.Formatos aceitos formato mm/dd/aaaa ou mm-dd-aaaa ou mm.dd.aaaa

^(?:(?:(?:0?[13578]|1[02])(\/|-|\.)31)\1|(?:(?:0?[1,3-9]|1[0-2])(\/|-|\.)(?:29|30)\2))(?:(?:1[6-9]|[2-9]\d)?\d{2})$|^(?:0?2(\/|-|\.)29\3(?:(?:(?:1[6-9]|[2-9]\d)?(?:0[48]|[2468][048]|[13579][26])|(?:(?:16|[2468][048]|[3579][26])00))))$|^(?:(?:0?[1-9])|(?:1[0-2]))(\/|-|\.)(?:0?[1-9]|1\d|2[0-8])\4(?:(?:1[6-9]|[2-9]\d)?\d{2})$

cortesia Asiq Ahamed

Cheguei aqui porque o título desta pergunta é amplo e eu estava procurando uma regex que pudesse usar para corresponder a um formato de data específico (como o OP).Mas então descobri, como muitas das respostas e comentários destacaram de forma abrangente, que há muitas armadilhas que tornam a construção de um padrão eficaz muito complicada ao extrair datas que estão misturadas com dados de origem não estruturados ou de baixa qualidade.

Na minha exploração das questões, criei um sistema que permite construir uma expressão regular, organizando quatro subexpressões mais simples que correspondem ao delimitador e intervalos válidos para os campos de ano, mês e dia na ordem voce requere.

Estes são :-

Delimetros

[^\w\d\r\n:] 

Isso corresponderá a qualquer coisa que não seja um caractere de palavra, dígito, retorno de carro, nova linha ou dois pontos.Os dois pontos devem estar presentes para evitar correspondência em horários que parecem datas (veja meus dados de teste)

Você pode otimizar esta parte do padrão para acelerar a correspondência, mas esta é uma boa base que detecta a maioria dos delimitadores válidos.

Observe, entretanto;Ele corresponderá a uma string com delimitadores mistos como este 2/12-73 que pode não ser uma data válida.

Valores do ano

(\d{4}|\d{2})

Isso corresponde a um grupo de dois ou 4 dígitos, na maioria dos casos isso é aceitável, mas se você estiver lidando com dados dos anos 0 a 999 ou além de 9999, precisará decidir como lidar com isso, porque na maioria dos casos 1, 3 ou> ano de 4 dígitos é lixo.

Valores do mês

(0?[1-9]|1[0-2])

Corresponde a qualquer número entre 1 e 12 com ou sem zero à esquerda - observação:0 e 00 não correspondem.

Valores de data

(0?[1-9]|[12]\d|30|31)

Corresponde a qualquer número entre 1 e 31 com ou sem zero à esquerda - observação:0 e 00 não correspondem.

Esta expressão corresponde às datas formatadas como Data, Mês e Ano

(0?[1-9]|[12]\d|30|31)[^\w\d\r\n:](0?[1-9]|1[0-2])[^\w\d\r\n:](\d{4}|\d{2})

Mas também corresponderá a algumas datas do ano e do mês.Ele também deve ser anexado aos operadores de limite para garantir que toda a sequência de datas seja selecionada e evitar que subdatas válidas sejam extraídas de dados que não estejam bem formados, ou seja,sem etiquetas de limite 20/12/194 corresponde a 20/12/19 e 101/12/1974 corresponde a 01/12/1974

Compare os resultados da próxima expressão com a acima com os dados de teste na seção sem sentido (abaixo)

\b(0?[1-9]|[12]\d|30|31)[^\w\d\r\n:](0?[1-9]|1[0-2])[^\w\d\r\n:](\d{4}|\d{2})\b

Não há validação neste regex, portanto, uma data bem formada, mas inválida, como 31/02/2001, seria correspondida.Esse é um problema de qualidade de dados e, como já foi dito, seu regex não deveria precisar validar os dados.

Como você (como desenvolvedor) não pode garantir a qualidade dos dados de origem necessários para executar e lidar com validações adicionais em seu código, se tentar combinar e validar os dados no RegEx fica muito confuso e difícil de suportar sem muito documentação concisa.

Entra lixo, sai lixo.

Dito isto, se você tiver formatos mistos onde os valores de data variam e precisar extrair o máximo que puder;Você pode combinar algumas expressões assim;

Esta expressão (desastrosa) corresponde às datas DMY e YMD

(\b(0?[1-9]|[12]\d|30|31)[^\w\d\r\n:](0?[1-9]|1[0-2])[^\w\d\r\n:](\d{4}|\d{2})\b)|(\b(0?[1-9]|1[0-2])[^\w\d\r\n:](0?[1-9]|[12]\d|30|31)[^\w\d\r\n:](\d{4}|\d{2})\b)

MAS você não saberá se datas como 09/06/1973 são 6 de setembro ou 9 de junho.Estou lutando para pensar em um cenário em que isso não vá causar um problema em algum momento, é uma prática ruim e você não deveria ter que lidar com isso assim - encontre o proprietário dos dados e acerte-o com o martelo de governança .

Finalmente, se você quiser corresponder uma string AAAAMMDD sem delimitadores, você pode eliminar um pouco da incerteza e a expressão ficará assim

\b(\d{4})(0[1-9]|1[0-2])(0[1-9]|[12]\d|30|31)\b

Mas observe novamente, ele corresponderá a valores bem formados, mas inválidos, como 20010231 (31 de fevereiro!) :)

Dados de teste

Ao experimentar as soluções neste tópico, acabei com um conjunto de dados de teste que inclui uma variedade de datas válidas e inválidas e algumas situações complicadas onde você pode ou não querer corresponder, ou seja,Horários que podem corresponder a datas e datas em várias linhas.

Espero que isso seja útil para alguém.

Valid Dates in various formats

Day, month, year
2/11/73
02/11/1973
2/1/73
02/01/73
31/1/1973
02/1/1973
31.1.2011
31-1-2001
29/2/1973
29/02/1976 
03/06/2010
12/6/90

month, day, year
02/24/1975 
06/19/66 
03.31.1991
2.29.2003
02-29-55
03-13-55
03-13-1955
12\24\1974
12\30\1974
1\31\1974
03/31/2001
01/21/2001
12/13/2001

Match both DMY and MDY
12/12/1978
6/6/78
06/6/1978
6/06/1978

using whitespace as a delimiter

13 11 2001
11 13 2001
11 13 01 
13 11 01
1 1 01
1 1 2001

Year Month Day order
76/02/02
1976/02/29
1976/2/13
76/09/31

YYYYMMDD sortable format
19741213
19750101

Valid dates before Epoch
12/1/10
12/01/660
12/01/00
12/01/0000

Valid date after 2038

01/01/2039
01/01/39

Valid date beyond the year 9999

01/01/10000

Dates with leading or trailing characters

12/31/21/
31/12/1921AD
31/12/1921.10:55
12/10/2016  8:26:00.39
wfuwdf12/11/74iuhwf
fwefew13/11/1974
01/12/1974vdwdfwe
01/01/99werwer
12321301/01/99

Times that look like dates

12:13:56
13:12:01
1:12:01PM
1:12:01 AM

Dates that runs across two lines

1/12/19
74

01/12/19
74/13/1946

31/12/20
08:13

Invalid, corrupted or nonsense dates

0/1/2001
1/0/2001
00/01/2100
01/0/2001
0101/2001
01/131/2001
31/31/2001
101/12/1974
56/56/56
00/00/0000
0/0/1999
12/01/0
12/10/-100
74/2/29
12/32/45
20/12/194

2/12-73

Versão Perl 5.10 sustentável

/
  (?:
      (?<month> (?&mon_29)) [\/] (?<day>(?&day_29))
    | (?<month> (?&mon_30)) [\/] (?<day>(?&day_30))
    | (?<month> (?&mon_31)) [\/] (?<day>(?&day_31))
  )
  [\/]
  (?<year> [0-9]{4})

  (?(DEFINE)
    (?<mon_29> 0?2 )
    (?<mon_30> 0?[469]   | (11) )
    (?<mon_31> 0?[13578] | 1[02] )

    (?<day_29> 0?[1-9] | [1-2]?[0-9] )
    (?<day_30> 0?[1-9] | [1-2]?[0-9] | 30 )
    (?<day_31> 0?[1-9] | [1-2]?[0-9] | 3[01] )
  )
/x

Você pode recuperar os elementos por nome nesta versão.

say "Month=$+{month} Day=$+{day} Year=$+{year}";

(Não foi feita nenhuma tentativa de restringir os valores do ano.)

Para controlar uma validade de data no seguinte formato:

AAAA/MM/DD ou AAAA-MM-DD

Eu recomendo que você use a seguinte expressão regular:

(((19|20)([2468][048]|[13579][26]|0[48])|2000)[/-]02[/-]29|((19|20)[0-9]{2}[/-](0[4678]|1[02])[/-](0[1-9]|[12][0-9]|30)|(19|20)[0-9]{2}[/-](0[1359]|11)[/-](0[1-9]|[12][0-9]|3[01])|(19|20)[0-9]{2}[/-]02[/-](0[1-9]|1[0-9]|2[0-8])))

Partidas

2016-02-29 | 2012-04-30 | 2019/09/31

Não-correspondências

2016-02-30 | 2012-04-31 | 2019/09/35

Você pode personalizá-lo se quiser permitir apenas separadores '/' ou '-'.Este RegEx controla rigorosamente a validade da data e verifica meses de 28,30 e 31 dias, até mesmo anos bissextos com 29/02 mês.

Experimente, funciona muito bem e evita muitos bugs no seu código!

PARA SUA INFORMAÇÃO :Fiz uma variante para o SQL datetime.Você encontrará lá (procure meu nome): Expressão regular para validar um carimbo de data/hora

Comentários são bem-vindos :)

Parece que você está estendendo demais o regex para essa finalidade.O que eu faria é usar um regex para corresponder a alguns formatos de data e, em seguida, usar uma função separada para validar os valores dos campos de data extraídos.

Versão expandida Perl

Observe o uso de /x modificador.

/^(
      (
        ( # 31 day months
            (0[13578])
          | ([13578])
          | (1[02])
        )
        [\/]
        (
            ([1-9])
          | ([0-2][0-9])
          | (3[01])
        )
      )
    | (
        ( # 30 day months
            (0[469])
          | ([469])
          | (11)
        )
        [\/]
        (
            ([1-9])
          | ([0-2][0-9])
          | (30)
        )
      )
    | ( # 29 day month (Feb)
        (2|02)
        [\/]
        (
            ([1-9])
          | ([0-2][0-9])
        )
      )
    )
    [\/]
    # year
    \d{4}$

  | ^\d{4}$ # year only
/x

Original

^((((0[13578])|([13578])|(1[02]))[\/](([1-9])|([0-2][0-9])|(3[01])))|(((0[469])|([469])|(11))[\/](([1-9])|([0-2][0-9])|(30)))|((2|02)[\/](([1-9])|([0-2][0-9]))))[\/]\d{4}$|^\d{4}$

se você não conseguiu fazer com que as sugestões acima funcionassem, eu uso isso, pois obtém qualquer data. Executei essa expressão em 50 links e obteve todas as datas em cada página.

^20\d\d-(Jan|Feb|Mar|Apr|May|Jun|Jul|Aug|Sep|Oct|Nov|Dec)-(0[1-9]|[1-2][0-9]|3[01])$ 
    var dtRegex = new RegExp(/[1-9\-]{4}[0-9\-]{2}[0-9\-]{2}/);
    if(dtRegex.test(date) == true){
        var evalDate = date.split('-');
        if(evalDate[0] != '0000' && evalDate[1] != '00' && evalDate[2] != '00'){
            return true;
        }
    }

Este regex valida datas entre 01/01/2000 e 31/12/2099 com separadores correspondentes.

^(0[1-9]|1[012])([- /.])(0[1-9]|[12][0-9]|3[01])\2(19|20)\d\d$

Regex não foi criado para validar intervalos de números (esse número deve ser de 1 a 5 quando o número anterior for 2 e o número anterior for inferior a 6).Basta procurar o padrão de colocação dos números na regex.Se você precisar validar as qualidades de uma data, coloque-a em um objeto de data js/c#/vb e interogue os números lá.

Sei que isso não responde à sua pergunta, mas por que você não usa uma rotina de tratamento de datas para verificar se é uma data válida?Mesmo se você modificar o regexp com uma afirmação antecipada negativa como (?!31/0?2) (ou seja, não corresponde a 31/2 ou 31/02), você ainda terá o problema de aceitar 29 02 em anos não bissextos e sobre um único formato de data separador.

O problema não é fácil se você quer realmente validar uma data, verifique isto tópico do fórum.

Para um exemplo ou uma maneira melhor, em C#, verifique esse link

Se você estiver usando outra plataforma/linguagem, avise-nos

Versão Perl 6

Depois de usar isso para verificar a entrada, os valores estão disponíveis em $/ ou individualmente como $<month>, $<day>, $<year>.(essas são apenas sintaxe para acessar valores em $/ )

Nenhuma tentativa foi feita para verificar o ano ou se ele não corresponde a 29 de fevereiro em anos não bissextos.

Se você insistir em fazer isso com uma expressão regular, recomendo algo como:

( (0?1|0?3| <...> |10|11|12) / (0?1| <...> |30|31) |
  0?2 / (0?1| <...> |28|29) ) 
/ (19|20)[0-9]{2}

Esse poder tornar possível ler e compreender.

Uma abordagem ligeiramente diferente que pode ou não ser útil para você.

Estou em php.

O projecto a que se refere nunca terá data anterior a 1 de Janeiro de 2008.Então, pego a 'data' inserida e uso strtotime().Se a resposta for >= 1199167200 então tenho uma data que é útil para mim.Se algo que não se parece com uma data for inserido, -1 será retornado.Se nulo for inserido, ele retornará o número da data de hoje, portanto, primeiro você precisa verificar se há uma entrada não nula.

Funciona para a minha situação, talvez a sua também?

Licenciado em: CC-BY-SA com atribuição
Não afiliado a StackOverflow
scroll top