python - español - ¿Cómo determinar el formato de tiempo de ejecución adecuado de una cadena de fecha?
django tutorial (3)
El analizador de dateutil
hace un gran trabajo de adivinar correctamente la fecha y la hora a partir de una amplia variedad de fuentes.
Estamos procesando archivos en los que cada archivo utiliza solo un formato de fecha / hora, pero el formato varía entre los archivos. La creación de perfiles muestra mucho tiempo utilizado por dateutil.parser.parse
. Dado que solo se debe determinar una vez por archivo, implementar algo que no adivine el formato cada vez podría acelerar las cosas.
Realmente no conozco los formatos de antemano y todavía debo inferir el formato. Algo como:
from MysteryPackage import date_string_to_format_string
import datetime
# e.g. mystring = ''1 Jan 2016''
myformat = None
...
# somewhere in a loop reading from a file or connection:
if myformat is None:
myformat = date_string_to_format_string(mystring)
# do the usual checks to see if that worked, then:
mydatetime = datetime.strptime(mystring, myformat)
¿Existe tal función?
Esta es una pregunta difícil. Mi enfoque hace uso de expresiones regulares y la sintaxis (?(DEFINE)...)
que solo es compatible con el módulo de expresiones regulares más nuevo.
DEFINE
nos permite definir subrutinas antes de emparejarlas, así que en primer lugar definimos todos los ladrillos necesarios para nuestra función de adivinar fechas: (?(DEFINE)
(?P<year_def>[12]/d{3})
(?P<year_short_def>/d{2})
(?P<month_def>January|February|March|April|May|June|
July|August|September|October|November|December)
(?P<month_short_def>Jan|Feb|Mar|Apr|May|Jun|Jul|Aug|Sep|Oct|Nov|Dec)
(?P<day_def>(?:0[1-9]|[1-9]|[12][0-9]|3[01]))
(?P<weekday_def>(?:Mon|Tue|Wednes|Thurs|Fri|Satur|Sun)day)
(?P<weekday_short_def>Mon|Tue|Wed|Thu|Fri|Sat|Sun)
(?P<hms_def>/d{2}:/d{2}:/d{2})
(?P<hm_def>/d{2}:/d{2})
(?P<ms_def>/d{5,6})
(?P<delim_def>([-/., ]+|(?<=/d|^)T))
)
# actually match them
(?P<hms>^(?&hms_def)$)|(?P<year>^(?&year_def)$)|(?P<month>^(?&month_def)$)|(?P<month_short>^(?&month_short_def)$)|(?P<day>^(?&day_def)$)|
(?P<weekday>^(?&weekday_def)$)|(?P<weekday_short>^(?&weekday_short_def)$)|(?P<hm>^(?&hm_def)$)|(?P<delim>^(?&delim_def)$)|(?P<ms>^(?&ms_def)$)
""", re.VERBOSE)
Después de esto, tenemos que pensar en posibles delimitadores:
# delim
delim = re.compile(r''([-/., ]+|(?<=/d)T)'')
Formato de mapeo:
# formats
formats = {''ms'': ''%f'', ''year'': ''%Y'', ''month'': ''%B'', ''month_dec'': ''%m'', ''day'': ''%d'', ''weekday'': ''%A'', ''hms'': ''%H:%M:%S'', ''weekday_short'': ''%a'', ''month_short'': ''%b'', ''hm'': ''%H:%M'', ''delim'': ''''}
La función GuessFormat()
divide las partes con la ayuda de los delimitadores, intenta hacer coincidirlas y genera el código correspondiente para strftime()
:
def GuessFormat(datestring):
# define the bricks
bricks = re.compile(r"""
(?(DEFINE)
(?P<year_def>[12]/d{3})
(?P<year_short_def>/d{2})
(?P<month_def>January|February|March|April|May|June|
July|August|September|October|November|December)
(?P<month_short_def>Jan|Feb|Mar|Apr|May|Jun|Jul|Aug|Sep|Oct|Nov|Dec)
(?P<day_def>(?:0[1-9]|[1-9]|[12][0-9]|3[01]))
(?P<weekday_def>(?:Mon|Tue|Wednes|Thurs|Fri|Satur|Sun)day)
(?P<weekday_short_def>Mon|Tue|Wed|Thu|Fri|Sat|Sun)
(?P<hms_def>T?/d{2}:/d{2}:/d{2})
(?P<hm_def>T?/d{2}:/d{2})
(?P<ms_def>/d{5,6})
(?P<delim_def>([-/., ]+|(?<=/d|^)T))
)
# actually match them
(?P<hms>^(?&hms_def)$)|(?P<year>^(?&year_def)$)|(?P<month>^(?&month_def)$)|(?P<month_short>^(?&month_short_def)$)|(?P<day>^(?&day_def)$)|
(?P<weekday>^(?&weekday_def)$)|(?P<weekday_short>^(?&weekday_short_def)$)|(?P<hm>^(?&hm_def)$)|(?P<delim>^(?&delim_def)$)|(?P<ms>^(?&ms_def)$)
""", re.VERBOSE)
# delim
delim = re.compile(r''([-/., ]+|(?<=/d)T)'')
# formats
formats = {''ms'': ''%f'', ''year'': ''%Y'', ''month'': ''%B'', ''month_dec'': ''%m'', ''day'': ''%d'', ''weekday'': ''%A'', ''hms'': ''%H:%M:%S'', ''weekday_short'': ''%a'', ''month_short'': ''%b'', ''hm'': ''%H:%M'', ''delim'': ''''}
parts = delim.split(datestring)
out = []
for index, part in enumerate(parts):
try:
brick = dict(filter(lambda x: x[1] is not None, bricks.match(part).groupdict().items()))
key = next(iter(brick))
# ambiguities
if key == ''day'' and index == 2:
key = ''month_dec''
item = part if key == ''delim'' else formats[key]
out.append(item)
except AttributeError:
out.append(part)
return "".join(out)
Una prueba al final:
import regex as re
datestrings = [datetime.now().isoformat(), ''2006-11-02'', ''Thursday, 10 August 2006 08:42:51'', ''August 9, 1995'', ''Aug 9, 1995'', ''Thu, 01 Jan 1970 00:00:00'', ''21/11/06 16:30'',
''06 Jun 2017 20:33:10'']
# test
for dt in datestrings:
print("Date: {}, Format: {}".format(dt, GuessFormat(dt)))
Esto produce:
Date: 2017-06-07T22:02:05.001811, Format: %Y-%m-%dT%H:%M:%S.%f
Date: 2006-11-02, Format: %Y-%m-%d
Date: Thursday, 10 August 2006 08:42:51, Format: %A, %m %B %Y %H:%M:%S
Date: August 9, 1995, Format: %B %m, %Y
Date: Aug 9, 1995, Format: %b %m, %Y
Date: Thu, 01 Jan 1970 00:00:00, Format: %a, %m %b %Y %H:%M:%S
Date: 21/11/06 16:30, Format: %d/%m/%d %H:%M
Date: 06 Jun 2017 20:33:10, Format: %d %b %Y %H:%M:%S
No tengo una solución preparada, pero este es un problema muy difícil y dado que ya se han gastado demasiadas horas cerebrales en dateutil , en lugar de intentar reemplazarlo, propondré un enfoque que lo incorpore:
- Lee los primeros registros N y analiza cada fecha usando dateutil
- Para cada parte de la fecha, observe dónde en la cadena aparece el valor
- Si todas las ubicaciones de las partes de la fecha (o> 90%) coinciden (como "YYYY es siempre después de DD, separados por una coma y un espacio"), convierta esa información en una cadena de formato strptime
- Cambie a usar datetime.strptime () con un nivel de confianza relativamente bueno de que funcionará con el resto del archivo
Ya que declaró que "cada archivo usa solo un formato de fecha / hora", este enfoque debería funcionar (suponiendo que tenga fechas diferentes en cada archivo para que la ambigüedad mm / dd pueda resolverse comparando varios valores de fecha).
Puedes escribir tu propio analizador:
import datetime
class DateFormatFinder:
def __init__(self):
self.fmts = []
def add(self,fmt):
self.fmts.append(fmt)
def find(self, ss):
for fmt in self.fmts:
try:
datetime.datetime.strptime(ss, fmt)
return fmt
except:
pass
return None
Puedes usarlo de la siguiente manera:
>>> df = DateFormatFinder()
>>> df.add(''%m/%d/%y %H:%M'')
>>> df.add(''%m/%d/%y'')
>>> df.add(''%H:%M'')
>>> df.find("01/02/06 16:30")
''%m/%d/%y %H:%M''
>>> df.find("01/02/06")
''%m/%d/%y''
>>> df.find("16:30")
''%H:%M''
>>> df.find("01/02/06 16:30")
''%m/%d/%y %H:%M''
>>> df.find("01/02/2006")
Sin embargo, no es tan simple como las fechas pueden ser ambiguas y su formato no se puede determinar sin algún contexto.
>>> datetime.strptime("01/02/06 16:30", "%m/%d/%y %H:%M") # us format
datetime.datetime(2006, 1, 2, 16, 30)
>>> datetime.strptime("01/02/06 16:30", "%d/%m/%y %H:%M") # european format
datetime.datetime(2006, 2, 1, 16, 30)