¿Fechas coincidentes con expresiones regulares en Python?

Sé que hay preguntas similares a las mías que han sido respondidas, pero después de leerlas todavía no tengo la solución que estoy buscando.

Utilizando Python 3.2.2, necesito hacer coincidir “Mes, Día, Año” con el Mes como una cadena, Día con dos dígitos que no superen los 30, 31 o 28 para febrero y 29 para febrero en un año bisiesto. (Básicamente una fecha REAL y Válida)

Esto es lo que tengo hasta ahora:

pattern = "(January|February|March|April|May|June|July|August|September|October|November|December)[,][ ](0[1-9]|[12][0-9]|3[01])[,][ ]((19|20)[0-9][0-9])" expression = re.compile(pattern) matches = expression.findall(sampleTextFile) 

Todavía no estoy muy familiarizado con la syntax de expresiones regulares, por lo que es posible que haya caracteres allí innecesarios (la [,] [] para la coma y los espacios parece una forma incorrecta de hacerlo), pero cuando trato de hacer coincidir ” El 26 de enero de 1991 “en mi archivo de texto de muestra, la impresión de los elementos en” coincidencias “es (‘Enero’, ’26’, ‘1991’, ’19’).

¿Por qué aparece el extra ’19’ al final?

Además, ¿qué cosas puedo agregar o cambiar en mi expresión regular que me permita validar las fechas correctamente? Mi plan en este momento es aceptar casi todas las fechas y eliminarlas más adelante utilizando construcciones de alto nivel comparando el grupo de días con el mes y el año para ver si el día debe ser <31,30,29,28

Cualquier ayuda sería muy apreciada, incluida una crítica constructiva sobre cómo voy a diseñar mi expresión regular.

Aquí hay una forma de hacer una expresión regular que coincida con cualquier fecha del formato deseado (aunque obviamente podría modificar si las comas son opcionales, agregar abreviaturas de meses, etc.):

 years = r'((?:19|20)\d\d)' pattern = r'(%%s) +(%%s), *%s' % years thirties = pattern % ( "September|April|June|November", r'0?[1-9]|[12]\d|30') thirtyones = pattern % ( "January|March|May|July|August|October|December", r'0?[1-9]|[12]\d|3[01]') fours = '(?:%s)' % '|'.join('%02d' % x for x in range(4, 100, 4)) feb = r'(February) +(?:%s|%s)' % ( r'(?:(0?[1-9]|1\d|2[0-8])), *%s' % years, # 1-28 any year r'(?:(29), *((?:(?:19|20)%s)|2000))' % fours) # 29 leap years only result = '|'.join('(?:%s)' % x for x in (thirties, thirtyones, feb)) r = re.compile(result) print result 

Entonces nosotros tenemos:

 >>> r.match('January 30, 2001') is not None True >>> r.match('January 31, 2001') is not None True >>> r.match('January 32, 2001') is not None False >>> r.match('February 32, 2001') is not None False >>> r.match('February 29, 2001') is not None False >>> r.match('February 28, 2001') is not None True >>> r.match('February 29, 2000') is not None True >>> r.match('April 30, 1908') is not None True >>> r.match('April 31, 1908') is not None False 

¿Y qué es esta gloriosa expresión regular, puede preguntar?

 >>> print result (?:(September|April|June|November) +(0?[1-9]|[12]\d|30), *((?:19|20)\d\d))|(?:(January|March|May|July|August|October|December) +(0?[1-9]|[12]\d|3[01]), *((?:19|20)\d\d))|(?:February +(?:(?:(0?[1-9]|1\d|2[0-8]), *((?:19|20)\d\d))|(?:(29), *((?:(?:19|20)(?:04|08|12|16|20|24|28|32|36|40|44|48|52|56|60|64|68|72|76|80|84|88|92|96))|2000)))) 

(Inicialmente tenía la intención de hacer una enumeración irónica de las fechas posibles, pero básicamente terminé escribiendo a mano todo eso en bruto, excepto los múltiplos de cuatro, de todos modos).

Aquí hay algunos pensamientos rápidos:

Todos los que sugieren que uses algo distinto a la expresión regular te están dando muy buenos consejos. Por otro lado, siempre es un buen momento para aprender más sobre la syntax de expresiones regulares …

Una expresión entre corchetes: [...] coincide con cualquier carácter individual dentro de esos corchetes. Por lo tanto, escribir [,] , que solo contiene un único carácter, es exactamente idéntico a escribir una simple coma sin adornos: , .

El método .findall devuelve una lista de todos los grupos coincidentes en la cadena. Un grupo es identificado por parenthese – (...) – y cuentan de izquierda a derecha, primero por fuera. Tu expresión final se ve así:

 ((19|20)[0-9][0-9]) 

Los paréntesis más externos coinciden con todo el año, y los paréntesis internos coinciden con los dos primeros dígitos. Por lo tanto, para una fecha como “1989”, los dos últimos grupos de partidos serán 1989 y 19 .

Un grupo se identifica con paréntesis (...) y cuentan de izquierda a derecha, primero por fuera. Tu expresión final se ve así:

((19 | 20) [0-9] [0-9])

Los paréntesis más externos coinciden con todo el año, y los paréntesis internos coinciden con los dos primeros dígitos. Por lo tanto, para una fecha como “1989”, los dos grupos de coincidencia serán 1989 y 19. Como no quieres el grupo interno (los dos primeros dígitos), debes usar un grupo que no capture. Los grupos que no capturan comienzan con ?: , Utilizados de esta forma: (?:a|b|c)

Por cierto, hay una buena documentación sobre cómo usar expresiones regulares aquí .

Python tiene un analizador de fecha como parte del módulo de time :

 import time time.strptime("December 31, 2012", "%B %d, %Y") 

Lo anterior es todo lo que necesita si el formato de fecha es siempre el mismo.

Por lo tanto, en el código de producción real, escribiría una expresión regular que analiza la fecha y luego usaría los resultados de la expresión regular para crear una cadena de fecha que siempre tenga el mismo formato.

Ahora que dijiste, en los comentarios, que esto es tarea, publicaré otra respuesta con consejos sobre expresiones regulares.

Tienes esta expresión regular:

 pattern = "(January|February|March|April|May|June|July|August|September|October|November|December)[,][ ](0[1-9]|[12][0-9]|3[01])[,][ ]((19|20)[0-9][0-9])" 

Una característica de las expresiones regulares es una “clase de caracteres”. Los caracteres entre corchetes forman una clase de personaje. Por lo tanto [,] es una clase de caracteres que coincide con un solo carácter , (una coma). Bien podrías poner la coma.

¿Tal vez quisiste hacer la coma opcional? Puedes hacerlo poniendo un signo de interrogación después de esto: ,?

Cualquier cosa que pongas entre paréntesis hace un “grupo de coincidencias”. Creo que el misterioso extra “19” provino de un grupo de encuentros que no quisiste tener. Puedes hacer un grupo no coincidente usando esta syntax: (?:

Así por ejemplo:

 r'(?:red|blue) socks' 

Esto coincidiría con “calcetines rojos” o “calcetines azules”, pero no conforma un grupo de coincidencias. Si luego pones eso entre paréntesis simples:

 r'((?:red|blue) socks)' 

Eso haría un grupo de coincidencia, cuyo valor sería "red socks" o "blue socks"

Creo que si aplica estos comentarios a su expresión regular, funcionará. Es sobre todo correcto ahora.

En cuanto a la validación de la fecha con respecto al mes, eso va más allá del scope de una expresión regular. Tu patrón coincidirá con el "February 31" y no hay una manera fácil de solucionarlo.

En primer lugar como otro como dije, no creo que la expresión regular sea la mejor opción para resolver este problema sino para responder a su pregunta. Al usar paréntesis, está diseccionando la cadena en varios subgrupos y cuando llama a la función findall, creará una lista con todo el grupo que creó y la cadena correspondiente.

 ((19|20)[0-9][0-9]) 

Aquí está su problema, la expresión regular coincidirá con todo el año y 19 o 20, dependiendo de si el año comienza con 19 o 20.