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.

17
mlissner 11 ago. 2011 a las 19:33

4 respuestas

La mejor respuesta

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)
16
4 revs 9 nov. 2012 a las 10:58

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.

0
Tiago Moutinho 11 ago. 2011 a las 16:58

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.

6
Community 23 may. 2017 a las 12:16

¿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

0
eyquem 11 ago. 2011 a las 19:13