Expresión regular para coincidir con fechas válidas


Estoy tratando de escribir una expresión regular que valide una fecha. La expresión regular debe coincidir con lo siguiente

  • M/D / AAAA
  • MM/DD / AAAA
  • Los meses de un solo dígito pueden comenzar con un cero inicial (eg: 03/12/2008)
  • Los días de un solo dígito pueden comenzar con un cero inicial (por ejemplo: 3/02/2008)
  • NO PUEDE incluir Febrero 30 o febrero 31 (por ejemplo: 2/31/2008)

Hasta ahora tengo

^(([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)$

Esto coincide correctamente, EXCEPTO que todavía incluye 2/30/2008 & 2/31/2008.

¿Alguien tiene una mejor sugerencia?

Edit: Encontré la respuesta en 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}$

Coincide con todos los meses válidos que siguen el formato MM/DD/AAAA.

Gracias a todos por la ayuda.

 54
Author: stema, 2008-09-09

15 answers

Este no es un uso apropiado de expresiones regulares. Sería mejor usar

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

Y luego comprobar rangos en un idioma de nivel superior.

 125
Author: Chris Conway,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2008-09-09 04:37:15

Aquí está el Reg ex que coincide con todas las fechas válidas, incluidos los años bisiestos. Formatos aceptados mm / dd / aaaa o mm-dd-aaaa o mm.dd.formato 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})$

cortesía Asiq Ahamed

 43
Author: Varun Achar,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2016-04-13 23:10:19

Se puede mantener la versión de Perl 5.10

/
  (?:
      (?<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

Puede recuperar los elementos por nombre en esta versión.

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

( No se ha intentado restringir los valores para el año. )

 12
Author: Brad Gilbert,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2015-05-01 01:01:25

Llegué aquí porque el título de esta pregunta es amplio y estaba buscando una expresión regular que pudiera usar para coincidir con un formato de fecha específico (como el OP). Pero luego descubrí, como muchas de las respuestas y comentarios han destacado exhaustivamente, hay muchas trampas que hacen que la construcción de un patrón efectivo sea muy difícil al extraer fechas que se mezclan con datos de origen de mala calidad o no estructurados.

En mi exploración de los problemas, se me ocurrió un sistema esto le permite crear una expresión regular organizando cuatro subexpresiones más simples que coincidan en el delimitador y rangos válidos para los campos año, mes y día en el orden que necesite.

Estos son: -

Delimitadores

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

Esto coincidirá con cualquier cosa que no sea un carácter de palabra, carácter de dígito, retorno de carro, nueva línea o dos puntos. Los dos puntos tienen que estar allí para evitar que coincidan en tiempos que parecen fechas (ver mis datos de prueba)

Puede optimizar esta parte del patrón para acelerar la coincidencia, pero esta es una buena base que detecta la mayoría de los delimitadores válidos.

Tenga en cuenta sin embargo; Coincidirá con una cadena con delimitadores mixtos como este 2/12-73 que puede no ser realmente una fecha válida.

Valores del año

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

Esto coincide con un grupo de dos o 4 dígitos, en la mayoría de los casos esto es aceptable, pero si está tratando con datos de los años 0-999 o más allá de 9999, debe decidir cómo maneje eso porque en la mayoría de los casos un año de 1, 3 o >4 dígitos es basura.

Valores mensuales

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

Coincide con cualquier número entre 1 y 12 con o sin un cero inicial - nota: 0 y 00 no coincide.

Valores de fecha

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

Coincide con cualquier número entre 1 y 31 con o sin un cero inicial - nota: 0 y 00 no coincide.

Esta expresión coincide con Fecha, Mes, Año fechas formateadas

(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})

, Pero también coinciden con algunos del Año, Fecha del Mes. También debe ser bookended con los operadores de límite para asegurar que toda la cadena de fecha está seleccionada y evitar que sub-fechas válidas que se extraen de los datos que no están bien formados, es decir, sin etiquetas de límite 20/12/194 coincidencias como 20/12/19 y 101/12/1974 coincidencias como 01/12/1974

Compare los resultados de la siguiente expresión con la anterior con los datos de prueba en la sección sin sentido (abajo)

\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

No hay validación en esta expresión regular fecha bien formada pero no válida como 31/02/2001 sería coincidente. Ese es un problema de calidad de los datos, y como otros han dicho, su expresión regular no debería necesitar validar los datos.

Debido a que usted (como desarrollador) no puede garantizar la calidad de los datos de origen que necesita para realizar y manejar la validación adicional en su código, si intenta coincidir con y validar los datos en la expresión regular, se vuelve muy desordenado y difícil de soportar sin muy conciso documentación.

Basura adentro, basura afuera.

Dicho esto, si tiene formatos mixtos donde los valores de fecha varían, y tiene que extraer tanto como pueda; puede combinar un par de expresiones juntas como así;

Esta (desastrosa) expresión coincide con las fechas DMY y 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)

PERO no podrás saber si fechas como el 6/9/1973 son el 6 de septiembre o el 9 de junio. Estoy luchando para pensar en un escenario donde eso no es va a causar un problema en algún lugar en el futuro, es una mala práctica y no debería tener que lidiar con ello de esa manera: encontrar al propietario de los datos y golpearlo con el martillo de gobernanza.

Finalmente, si desea hacer coincidir una cadena AAAAMMDD sin delimitadores, puede eliminar parte de la incertidumbre y la expresión se ve así

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

Pero tenga en cuenta de nuevo, coincidirá con valores bien formados pero no válidos como 20010231 (31 de febrero!) :)

Datos de ensayo

En experimentando con las soluciones en este hilo terminé con un conjunto de datos de prueba que incluye una variedad de fechas válidas y no válidas y algunas situaciones complicadas donde puede o no querer coincidir, es decir, Tiempos que podrían coincidir como fechas y fechas en múltiples líneas.

Espero que esto sea útil para alguien.

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
 12
Author: Bob,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2016-10-28 16:50:46

Para controlar una fecha de validez con el siguiente formato:

AAAA / MM / DD o AAAA-MM-DD

Te recomiendo que uses la siguiente expresión 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])))

Coincidencias

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

No coincidencias

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

Puede personalizarlo si desea permitir solo los separadores '/' o' -'. Esta expresión regular controla estrictamente la validez de la fecha y verificar 28,30 y 31 días, meses, incluso años bisiestos con 29/02 mes.

Pruébelo, funciona muy bien y evitar que su código de muchos errores !

FYI : Hice una variante para el datetime SQL. Lo encontrarás allí (busca mi nombre): Expresión regular para validar una marca de tiempo

Los comentarios son bienvenidos:)

 6
Author: Okipa,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2017-05-23 12:10:45

Parece que estás extendiendo demasiado la expresión regular para este propósito. Lo que haría es usar una expresión regular para coincidir con algunos formatos de fecha y luego usar una función separada para validar los valores de los campos de fecha así extraídos.

 4
Author: Wedge,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2008-09-09 04:34:14

Versión expandida de Perl

Observe el uso del modificador /x.

/^(
      (
        ( # 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}$
 3
Author: Brad Gilbert,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2008-09-13 20:56:42

Si no lograste que las sugerencias anteriores funcionaran, utilizo esto, ya que obtiene cualquier fecha Corrí esta expresión a través de 50 enlaces, y obtuvo todas las fechas en 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])$ 
 3
Author: chuck akers,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2012-01-21 01:38:06
    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;
        }
    }
 2
Author: ALinnD,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2012-11-23 18:00:48

Esta expresión regular valida las fechas entre 01-01-2000 y 12-31-2099 con separadores coincidentes.

^(0[1-9]|1[012])([- /.])(0[1-9]|[12][0-9]|3[01])\2(19|20)\d\d$
 2
Author: Enrique,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2013-04-29 18:15:39

Perl 6 version

rx{
  ^

  $<month> = (\d ** 1..2)
  { $<month> <= 12 or fail }

  '/'

  $<day> = (\d ** 1..2)
  {
    given( +$<month> ){
      when 1|3|5|7|8|10|12 {
        $<day> <= 31 or fail
      }
      when 4|6|9|11 {
        $<day> <= 30 or fail
      }
      when 2 {
        $<day> <= 29 or fail
      }
      default { fail }
    }
  }

  '/'

  $<year> = (\d ** 4)

  $
}

Después de usar esto para verificar la entrada, los valores están disponibles en $/ o individualmente como $<month>, $<day>, $<year>. (esos son solo sintaxis para acceder a los valores en $/)

No se ha hecho ningún intento de comprobar el año, o que no coincide con el 29 de Febrero en años no bisiestos.

 2
Author: Brad Gilbert,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2015-12-27 04:53:24

Regex no estaba destinado a validar rangos numéricos(este número debe ser de 1 a 5 cuando el número que lo precede pasa a ser un 2 y el número anterior que pasa a ser inferior a 6). Simplemente busque el patrón de colocación de números en expresiones regulares. Si necesita validar las cualidades is de una fecha, póngala en un objeto date js/c#/vb, e interogate los números allí.

 1
Author: DevelopingChris,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2008-09-09 04:36:22

Sé que esto no responde a su pregunta, pero ¿por qué no usa una rutina de manejo de fechas para verificar si es una fecha válida? Incluso si modifica la expresión regular con una aserción negativa como (?!31/0?2) (es decir, no coincida con 31/2 o 31/02) todavía tendrá el problema de aceptar 29 02 en años no bisiestos y alrededor de un formato de fecha separador único.

El problema no es fácil si realmente quieres validar una fecha, revisa este hilo del foro .

Para un ejemplo o un mejor manera, en C#, marque este enlace

Si está utilizando otra plataforma/idioma, háganoslo saber

 1
Author: Vinko Vrsalovic,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2008-09-09 04:43:17

Si vas a insistir en hacer esto con una expresión regular, te recomendaría algo como:

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

Esto podría hacer posible leer y entender.

 0
Author: Chris Conway,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2008-09-09 04:57:04

Un enfoque ligeramente diferente que puede o no ser útil para usted.

Estoy en php.

El proyecto al que se refiere nunca tendrá una fecha anterior al 1 de enero de 2008. Por lo tanto, tomo la 'fecha' introducida y uso strtotime(). Si la respuesta es >= 1199167200 entonces tengo una fecha que me es útil. Si se introduce algo que no parece una fecha, se devuelve -1. Si se ingresa null, devuelve el número de fecha de hoy, por lo que necesita verificar una entrada no nula primero.

Funciona para mi situación, tal vez la tuya también?

 -1
Author: Humpton,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2008-10-21 12:56:45