Tengo una cadena que tiene varios valores de fecha y quiero analizarlos todos. La cadena es un lenguaje natural, así que lo mejor que he encontrado hasta ahora es dateutil.
Desafortunadamente, si una cadena tiene múltiples valores de fecha, dateutil arroja un error:
>>> s = "I like peas on 2011-04-23, and I also like them on easter and my birthday, the 29th of July, 1928"
>>> parse(s, fuzzy=True)
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "/usr/lib/pymodules/python2.7/dateutil/parser.py", line 697, in parse
return DEFAULTPARSER.parse(timestr, **kwargs)
File "/usr/lib/pymodules/python2.7/dateutil/parser.py", line 303, in parse
raise ValueError, "unknown string format"
ValueError: unknown string format
¿Alguna idea sobre cómo analizar todas las fechas de una cadena larga? Idealmente, se crearía una lista, pero puedo manejarlo yo mismo si lo necesito.
Estoy usando Python, pero en este punto, probablemente otros idiomas estén bien, si hacen el trabajo.
PD: supongo que podría dividir recursivamente el archivo de entrada en el medio e intentar, intentar de nuevo hasta que funcione, pero es un gran truco.
4 respuestas
Mirándolo, la forma menos hacky sería modificar dateutil analizador para tener una opción múltiple difusa.
parser._parse
toma su cadena, la tokeniza con _timelex
y luego compara las fichas con los datos definidos en parserinfo
.
Aquí, si es un token no coincide con nada en parserinfo
, el análisis fallará a menos que fuzzy
sea verdadero.
Lo que sugiero es que permita no coincidencias mientras no tenga fichas de tiempo procesadas, luego, cuando llegue a una no coincidencia, procese los datos analizados en ese punto y comience a buscar fichas de tiempo nuevamente.
No debería tomar demasiado esfuerzo.
Actualizar
Mientras espera a que su parche se enrolle ...
Esto es un poco hacky, utiliza funciones no públicas en la biblioteca, pero no requiere modificar la biblioteca y no es prueba y error. Puede tener falsos positivos si tiene tokens solitarios que se pueden convertir en flotadores. Es posible que deba filtrar los resultados un poco más.
from dateutil.parser import _timelex, parser
a = "I like peas on 2011-04-23, and I also like them on easter and my birthday, the 29th of July, 1928"
p = parser()
info = p.info
def timetoken(token):
try:
float(token)
return True
except ValueError:
pass
return any(f(token) for f in (info.jump,info.weekday,info.month,info.hms,info.ampm,info.pertain,info.utczone,info.tzoffset))
def timesplit(input_string):
batch = []
for token in _timelex(input_string):
if timetoken(token):
if info.jump(token):
continue
batch.append(token)
else:
if batch:
yield " ".join(batch)
batch = []
if batch:
yield " ".join(batch)
for item in timesplit(a):
print "Found:", item
print "Parsed:", p.parse(item)
Rendimientos:
Found: 2011 04 23 Parsed: 2011-04-23 00:00:00 Found: 29 July 1928 Parsed: 1928-07-29 00:00:00
Actualización para Dieter
Dateutil 2.1 parece estar escrito para compatibilidad con python3 y utiliza una biblioteca de "compatibilidad" llamada six
. Algo no está bien y no trata los objetos str
como texto.
Esta solución funciona con dateutil 2.1 si pasa cadenas como unicode o como objetos de archivo:
from cStringIO import StringIO
for item in timesplit(StringIO(a)):
print "Found:", item
print "Parsed:", p.parse(StringIO(item))
Si desea establecer la opción en la información del analizador, cree una instancia de la información del analizador y páselo al objeto del analizador. P.ej:
from dateutil.parser import _timelex, parser, parserinfo
info = parserinfo(dayfirst=True)
p = parser(info)
Creo que si pones las "palabras" en una matriz, debería funcionar. Con eso puedes verificar si es una fecha o no, y poner una variable.
Una vez que tenga la fecha, debe usar la biblioteca datetime.
Mientras estaba desconectado, me molestó la respuesta que publiqué aquí ayer. Sí, hizo el trabajo, pero fue innecesariamente complicado y extremadamente ineficiente.
¡Aquí está la edición al final del sobre que debería hacer un trabajo mucho mejor!
import itertools
from dateutil import parser
jumpwords = set(parser.parserinfo.JUMP)
keywords = set(kw.lower() for kw in itertools.chain(
parser.parserinfo.UTCZONE,
parser.parserinfo.PERTAIN,
(x for s in parser.parserinfo.WEEKDAYS for x in s),
(x for s in parser.parserinfo.MONTHS for x in s),
(x for s in parser.parserinfo.HMS for x in s),
(x for s in parser.parserinfo.AMPM for x in s),
))
def parse_multiple(s):
def is_valid_kw(s):
try: # is it a number?
float(s)
return True
except ValueError:
return s.lower() in keywords
def _split(s):
kw_found = False
tokens = parser._timelex.split(s)
for i in xrange(len(tokens)):
if tokens[i] in jumpwords:
continue
if not kw_found and is_valid_kw(tokens[i]):
kw_found = True
start = i
elif kw_found and not is_valid_kw(tokens[i]):
kw_found = False
yield "".join(tokens[start:i])
# handle date at end of input str
if kw_found:
yield "".join(tokens[start:])
return [parser.parse(x) for x in _split(s)]
Ejemplo de uso:
>>> parse_multiple("I like peas on 2011-04-23, and I also like them on easter and my birthday, the 29th of July, 1928")
[datetime.datetime(2011, 4, 23, 0, 0), datetime.datetime(1928, 7, 29, 0, 0)]
Probablemente valga la pena señalar que su comportamiento se desvía ligeramente de dateutil.parser.parse
cuando se trata de cadenas vacías / desconocidas. Dateutil devolverá el día actual, mientras que parse_multiple
devuelve una lista vacía que, en mi humilde opinión, es lo que uno esperaría.
>>> from dateutil import parser
>>> parser.parse("")
datetime.datetime(2011, 8, 12, 0, 0)
>>> parse_multiple("")
[]
P.S. Recién visto Respuesta actualizada de MattH que hace algo muy similar.
¿Por qué no escribir un patrón de expresiones regulares que cubra todas las formas posibles en las que puede aparecer una fecha, y luego iniciar la expresión regular para explorar el texto? Supongo que no hay docenas de docenas de modales para expresar una fecha en una cadena.
El único problema es reunir el máximo de expresiones de fecha
Preguntas relacionadas
Preguntas vinculadas
Nuevas preguntas
python
Python es un lenguaje de programación multipropósito, de tipificación dinámica y de múltiples paradigmas. Está diseñado para ser rápido de aprender, comprender y usar, y hacer cumplir una sintaxis limpia y uniforme. Tenga en cuenta que Python 2 está oficialmente fuera de soporte a partir del 01-01-2020. Aún así, para preguntas de Python específicas de la versión, agregue la etiqueta [python-2.7] o [python-3.x]. Cuando utilice una variante de Python (por ejemplo, Jython, PyPy) o una biblioteca (por ejemplo, Pandas y NumPy), inclúyala en las etiquetas.