python - relativedelta - six pip install
Obtener el formato en dateutil.parse (6)
¿Hay una manera de obtener el "formato" después de analizar una fecha en dateutil. Por ejemplo algo como:
>>> x = parse("2014-01-01 00:12:12")
datetime.datetime(2014, 1, 1, 0, 12, 12)
x.get_original_string_format()
YYYY-MM-DD HH:MM:SS # %Y-%m-%d %H:%M:%S
# Or, passing the date-string directly
get_original_string_format("2014-01-01 00:12:12")
YYYY-MM-DD HH:MM:SS # %Y-%m-%d %H:%M:%S
Actualización: Me gustaría agregar una recompensa a esta pregunta para ver si alguien podría agregar una respuesta que haría el equivalente para obtener el formato de cadena de una cadena de fecha común. Puede usar dateutil
si lo desea, pero no tiene que hacerlo. Esperemos que tengamos algunas soluciones creativas aquí.
¿Hay alguna forma de obtener el "formato" después de analizar una fecha en dateutil?
No es posible con dateutil
. El problema es que dateutil
nunca tiene el formato como un resultado intermedio en ningún momento durante el análisis, ya que detecta componentes separados de la fecha y hora por separado. Eche un vistazo a este código fuente que no es fácil de leer.
Idea:
- Inspeccione la cadena de fecha de entrada del usuario y cree un posible conjunto de formatos de fecha
- Recorra el conjunto de formatos, use
datetime.strptime
analizar la cadena de fecha con el formato de fecha posible individual. - Formatee la fecha del paso 2 con
datetime.strftime
, si el resultado es igual a la cadena de fecha de origen, este formato es un formato de fecha posible.
Implementación de algoritmos
from datetime import datetime
import itertools
import re
FORMAT_CODES = (
r''%a'', r''%A'', r''%w'', r''%d'', r''%b'', r''%B'', r''%m'', r''%y'', r''%Y'',
r''%H'', r''%I'', r''%p'', r''%M'', r''%S'', r''%f'', r''%z'', r''%Z'', r''%j'',
r''%U'', r''%W'',
)
TWO_LETTERS_FORMATS = (
r''%p'',
)
THREE_LETTERS_FORMATS = (
r''%a'', r''%b''
)
LONG_LETTERS_FORMATS = (
r''%A'', r''%B'', r''%z'', r''%Z'',
)
SINGLE_DIGITS_FORMATS = (
r''w'',
)
TWO_DIGITS_FORMATS = (
r''%d'', r''%m'', r''%y'', r''%H'', r''%I'', r''%M'', r''%S'', r''%U'', r''%W'',
)
THREE_DIGITS_FORMATS = (
r''%j'',
)
FOUR_DIGITS_FORMATS = (
r''%Y'',
)
LONG_DIGITS_FORMATS = (
r''%f'',
)
# Non format code symbols
SYMBOLS = (
''-'',
'':'',
''+'',
''Z'',
'','',
'' '',
)
if __name__ == ''__main__'':
date_str = input(''Please input a date: '')
# Split with non format code symbols
pattern = r''[^{}]+''.format(''''.join(SYMBOLS))
components = re.findall(pattern, date_str)
# Create a format placeholder, eg. ''{}-{}-{} {}:{}:{}+{}''
placeholder = re.sub(pattern, ''{}'', date_str)
formats = []
for comp in components:
if re.match(r''^/d{1}$'', comp):
formats.append(SINGLE_DIGITS_FORMATS)
elif re.match(r''^/d{2}$'', comp):
formats.append(TWO_DIGITS_FORMATS)
elif re.match(r''^/d{3}$'', comp):
formats.append(THREE_DIGITS_FORMATS)
elif re.match(r''^/d{4}$'', comp):
formats.append(FOUR_DIGITS_FORMATS)
elif re.match(r''^/d{5,}$'', comp):
formats.append(LONG_DIGITS_FORMATS)
elif re.match(r''^[a-zA-Z]{2}$'', comp):
formats.append(TWO_LETTERS_FORMATS)
elif re.match(r''^[a-zA-Z]{3}$'', comp):
formats.append(THREE_LETTERS_FORMATS)
elif re.match(r''^[a-zA-Z]{4,}$'', comp):
formats.append(LONG_LETTERS_FORMATS)
else:
formats.append(FORMAT_CODES)
# Create a possible format set
possible_set = itertools.product(*formats)
found = 0
for possible_format in possible_set:
# Create a format with possible format combination
dt_format = placeholder.format(*possible_format)
try:
dt = datetime.strptime(date_str, dt_format)
# Use the format to parse the date, and format the
# date back to string and compare with the origin one
if dt.strftime(dt_format) == date_str:
print(''Possible result: {}''.format(dt_format))
found += 1
except Exception:
continue
if found == 0:
print(''No pattern found'')
Uso:
$ python3 reverse.py
Please input a date: 2018-12-31 10:26 PM
Possible result: %Y-%d-%M %I:%S %p
Possible result: %Y-%d-%S %I:%M %p
Possible result: %Y-%m-%d %I:%M %p
Possible result: %Y-%m-%d %I:%S %p
Possible result: %Y-%m-%M %I:%d %p
Possible result: %Y-%m-%M %I:%S %p
Possible result: %Y-%m-%S %I:%d %p
Possible result: %Y-%m-%S %I:%M %p
Possible result: %Y-%H-%d %m:%M %p
Possible result: %Y-%H-%d %m:%S %p
Possible result: %Y-%H-%d %M:%S %p
Possible result: %Y-%H-%d %S:%M %p
Possible result: %Y-%H-%M %d:%S %p
Possible result: %Y-%H-%M %m:%d %p
Possible result: %Y-%H-%M %m:%S %p
Possible result: %Y-%H-%M %S:%d %p
Possible result: %Y-%H-%S %d:%M %p
Possible result: %Y-%H-%S %m:%d %p
Possible result: %Y-%H-%S %m:%M %p
Possible result: %Y-%H-%S %M:%d %p
Possible result: %Y-%I-%d %m:%M %p
Possible result: %Y-%I-%d %m:%S %p
Possible result: %Y-%I-%d %M:%S %p
Possible result: %Y-%I-%d %S:%M %p
Possible result: %Y-%I-%M %d:%S %p
Possible result: %Y-%I-%M %m:%d %p
Possible result: %Y-%I-%M %m:%S %p
Possible result: %Y-%I-%M %S:%d %p
Possible result: %Y-%I-%S %d:%M %p
Possible result: %Y-%I-%S %m:%d %p
Possible result: %Y-%I-%S %m:%M %p
Possible result: %Y-%I-%S %M:%d %p
Possible result: %Y-%M-%d %I:%S %p
Possible result: %Y-%M-%S %I:%d %p
Possible result: %Y-%S-%d %I:%M %p
Possible result: %Y-%S-%M %I:%d %p
Mi idea era crear una clase como esta, podría no ser precisa
from datetime import datetime
import re
class DateTime(object):
dateFormat = {"%d": "dd", "%Y": "YYYY", "%a": "Day", "%A": "DAY", "%w": "ww", "%b": "Mon", "%B": "MON", "%m": "mm",
"%H": "HH", "%I": "II", "%p": "pp", "%M": "MM", "%S": "SS"} # wil contain all format equivalent
def __init__(self, date_str, format):
self.dateobj = datetime.strptime(date_str, format)
self.format = format
def parse_format(self):
output=None
reg = re.compile("%[A-Z a-z]")
fmts = None
if self.format is not None:
fmts = re.findall(reg, self.format)
if fmts is not None:
output = self.format
for f in fmts:
output = output.replace(f, DateTime.dateFormat[f])
return output
nDate = DateTime("12 January, 2018", "%d %B, %Y")
print(nDate.parse_format())
Mi idea fue:
- Cree un objeto que tenga una lista de especificadores candidatos que cree que podrían estar en el patrón de fecha (cuanto más agregue, más posibilidades obtendrá del otro extremo)
- Analizar la cadena de fecha
- Cree una lista de posibles especificadores para cada elemento de la cadena, en función de la fecha y la lista de candidatos que proporcionó.
- Recombínalos para producir una lista de ''posibles''.
Si solo obtiene un candidato, puede estar seguro de que es el formato correcto. Pero a menudo obtendrá muchas posibilidades (especialmente con fechas, meses, minutos y horas, todo en el rango de 0-10).
Ejemplo de clase:
import re
from itertools import product
from dateutil.parser import parse
from collections import defaultdict, Counter
COMMON_SPECIFIERS = [
''%a'', ''%A'', ''%d'', ''%b'', ''%B'', ''%m'',
''%Y'', ''%H'', ''%p'', ''%M'', ''%S'', ''%Z'',
]
class FormatFinder:
def __init__(self,
valid_specifiers=COMMON_SPECIFIERS,
date_element=r''([/w]+)'',
delimiter_element=r''([/W]+)'',
ignore_case=False):
self.specifiers = valid_specifiers
joined = (r'''' + date_element + r"|" + delimiter_element)
self.pattern = re.compile(joined)
self.ignore_case = ignore_case
def find_candidate_patterns(self, date_string):
date = parse(date_string)
tokens = self.pattern.findall(date_string)
candidate_specifiers = defaultdict(list)
for specifier in self.specifiers:
token = date.strftime(specifier)
candidate_specifiers[token].append(specifier)
if self.ignore_case:
candidate_specifiers[token.
upper()] = candidate_specifiers[token]
candidate_specifiers[token.
lower()] = candidate_specifiers[token]
options_for_each_element = []
for (token, delimiter) in tokens:
if token:
if token not in candidate_specifiers:
options_for_each_element.append(
[token]) # just use this verbatim?
else:
options_for_each_element.append(
candidate_specifiers[token])
else:
options_for_each_element.append([delimiter])
for parts in product(*options_for_each_element):
counts = Counter(parts)
max_count = max(counts[specifier] for specifier in self.specifiers)
if max_count > 1:
# this is a candidate with the same item used more than once
continue
yield "".join(parts)
Y algunas pruebas de muestra:
def test_it_returns_value_from_question_1():
s = "2014-01-01 00:12:12"
candidates = FormatFinder().find_candidate_patterns(s)
sut = FormatFinder()
candidates = sut.find_candidate_patterns(s)
assert "%Y-%m-%d %H:%M:%S" in candidates
def test_it_returns_value_from_question_2():
s = ''Jan. 04, 2017''
sut = FormatFinder()
candidates = sut.find_candidate_patterns(s)
candidates = list(candidates)
assert "%b. %d, %Y" in candidates
assert len(candidates) == 1
def test_it_can_ignore_case():
# NB: apparently the ''AM/PM'' is meant to be capitalised in my locale!
# News to me!
s = "JANUARY 12, 2018 02:12 am"
sut = FormatFinder(ignore_case=True)
candidates = sut.find_candidate_patterns(s)
assert "%B %d, %Y %H:%M %p" in candidates
def test_it_returns_parts_that_have_no_date_component_verbatim():
# In this string, the ''at'' is considered as a ''date'' element,
# but there is no specifier that produces a candidate for it
s = "January 12, 2018 at 02:12 AM"
sut = FormatFinder()
candidates = sut.find_candidate_patterns(s)
assert "%B %d, %Y at %H:%M %p" in candidates
Para hacerlo un poco más claro, aquí hay algunos ejemplos de cómo usar este código en una shell de iPython:
In [2]: ff = FormatFinder()
In [3]: list(ff.find_candidate_patterns("2014-01-01 00:12:12"))
Out[3]:
[''%Y-%d-%m %H:%M:%S'',
''%Y-%d-%m %H:%S:%M'',
''%Y-%m-%d %H:%M:%S'',
''%Y-%m-%d %H:%S:%M'']
In [4]: list(ff.find_candidate_patterns("Jan. 04, 2017"))
Out[4]: [''%b. %d, %Y'']
In [5]: list(ff.find_candidate_patterns("January 12, 2018 at 02:12 AM"))
Out[5]: [''%B %d, %Y at %H:%M %p'', ''%B %M, %Y at %H:%d %p'']
In [6]: ff_without_case = FormatFinder(ignore_case=True)
In [7]: list(ff_without_case.find_candidate_patterns("JANUARY 12, 2018 02:12 am"))
Out[7]: [''%B %d, %Y %H:%M %p'', ''%B %M, %Y %H:%d %p'']
No sé de qué manera puede devolver el formato analizado desde dateutil
(o cualquier otro analizador de marca de tiempo de python que yo sepa).
Implementar su propia función de análisis de marca de tiempo que devuelve el formato junto con el objeto datetime es bastante trivial utilizando datetime.strptime()
pero hacerlo de manera eficiente contra una lista muy útil de posibles formatos de marca de tiempo no lo es.
El siguiente ejemplo utiliza una lista de más de 50 formatos adaptados de uno de los principales éxitos de una búsqueda rápida de formatos de marca de tiempo. Ni siquiera roza la superficie de la amplia variedad de formatos analizados por dateutil
. Prueba cada formato en secuencia hasta que encuentra una coincidencia o agota todos los formatos en la lista (es probable que sea mucho menos eficiente que el enfoque de dateutil
para ubicar las distintas partes de la fecha y hora independientemente como se indica en la respuesta de ).
Además, he incluido algunos ejemplos de formatos de marca de tiempo que incluyen nombres de zonas horarias (en lugar de compensaciones). Si ejecuta la función de ejemplo a continuación con esas cadenas de fecha y hora particulares, puede encontrar que devuelve "No se puede analizar el formato" aunque he incluido formatos coincidentes con la directiva %Z
Puede encontrar una explicación de los desafíos con el uso de %Z
para manejar los nombres de zona horaria en el problema 22377 en bugs.python.org (solo para resaltar otro aspecto no trivial de la implementación de su propia función de análisis de fecha y hora).
Con todas esas advertencias, si se trata de un conjunto manejable de formatos posibles, la implementación de algo simple como el siguiente puede proporcionarle lo que necesita.
Ejemplo de función que intenta hacer coincidir una cadena de fecha y hora con una lista de formatos y devolver el objeto de fecha y hora junto con el formato coincidente:
from datetime import datetime
def parse_timestamp(datestring, formats):
for f in formats:
try:
d = datetime.strptime(datestring, f)
except:
continue
return (d, f)
return (datestring, ''Unable to parse format'')
Formatos de ejemplo y cadenas de fecha y hora adaptados de marcas de tiempo, zonas horarias, rangos de tiempo y formatos de fecha :
formats = [''%Y-%m-%dT%H:%M:%S*%f%z'',''%Y %b %d %H:%M:%S.%f %Z'',''%b %d %H:%M:%S %z %Y'',''%d/%b/%Y:%H:%M:%S %z'',''%b %d, %Y %I:%M:%S %p'',''%b %d %Y %H:%M:%S'',''%b %d %H:%M:%S %Y'',''%b %d %H:%M:%S %z'',''%b %d %H:%M:%S'',''%Y-%m-%dT%H:%M:%S%z'',''%Y-%m-%dT%H:%M:%S.%f%z'',''%Y-%m-%d %H:%M:%S %z'',''%Y-%m-%d %H:%M:%S%z'',''%Y-%m-%d %H:%M:%S,%f'',''%Y/%m/%d*%H:%M:%S'',''%Y %b %d %H:%M:%S.%f*%Z'',''%Y %b %d %H:%M:%S.%f'',''%Y-%m-%d %H:%M:%S,%f%z'',''%Y-%m-%d %H:%M:%S.%f'',''%Y-%m-%d %H:%M:%S.%f%z'',''%Y-%m-%dT%H:%M:%S.%f'',''%Y-%m-%dT%H:%M:%S'',''%Y-%m-%dT%H:%M:%S%Z'',''%Y-%m-%dT%H:%M:%S.%f'',''%Y-%m-%dT%H:%M:%S'',''%Y-%m-%d*%H:%M:%S:%f'',''%Y-%m-%d*%H:%M:%S'',''%y-%m-%d %H:%M:%S,%f %z'',''%y-%m-%d %H:%M:%S,%f'',''%y-%m-%d %H:%M:%S'',''%y/%m/%d %H:%M:%S'',''%y%m%d %H:%M:%S'',''%Y%m%d %H:%M:%S.%f'',''%m/%d/%y*%H:%M:%S'',''%m/%d/%Y*%H:%M:%S'',''%m/%d/%Y*%H:%M:%S*%f'',''%m/%d/%y %H:%M:%S %z'',''%m/%d/%Y %H:%M:%S %z'',''%H:%M:%S'',''%H:%M:%S.%f'',''%H:%M:%S,%f'',''%d/%b %H:%M:%S,%f'',''%d/%b/%Y:%H:%M:%S'',''%d/%b/%Y %H:%M:%S'',''%d-%b-%Y %H:%M:%S'',''%d-%b-%Y %H:%M:%S.%f'',''%d %b %Y %H:%M:%S'',''%d %b %Y %H:%M:%S*%f'',''%m%d_%H:%M:%S'',''%m%d_%H:%M:%S.%f'',''%m/%d/%Y %I:%M:%S %p:%f'',''%m/%d/%Y %H:%M:%S %p'']
datestrings = [''2018-08-20T13:20:10*633+0000'',''2017 Mar 03 05:12:41.211 PDT'',''Jan 21 18:20:11 +0000 2017'',''19/Apr/2017:06:36:15 -0700'',''Dec 2, 2017 2:39:58 AM'',''Jun 09 2018 15:28:14'',''Apr 20 00:00:35 2010'',''Sep 28 19:00:00 +0000'',''Mar 16 08:12:04'',''2017-10-14T22:11:20+0000'',''2017-07-01T14:59:55.711+0000'',''2017-08-19 12:17:55 -0400'',''2017-08-19 12:17:55-0400'',''2017-06-26 02:31:29,573'',''2017/04/12*19:37:50'',''2018 Apr 13 22:08:13.211*PDT'',''2017 Mar 10 01:44:20.392'',''2017-03-10 14:30:12,655+0000'',''2018-02-27 15:35:20.311'',''2017-03-12 13:11:34.222-0700'',''2017-07-22T16:28:55.444'',''2017-09-08T03:13:10'',''2017-03-12T17:56:22-0700'',''2017-11-22T10:10:15.455'',''2017-02-11T18:31:44'',''2017-10-30*02:47:33:899'',''2017-07-04*13:23:55'',''11-02-11 16:47:35,985 +0000'',''10-06-26 02:31:29,573'',''10-04-19 12:00:17'',''06/01/22 04:11:05'',''150423 11:42:35'',''20150423 11:42:35.173'',''08/10/11*13:33:56'',''11/22/2017*05:13:11'',''05/09/2017*08:22:14*612'',''04/23/17 04:34:22 +0000'',''10/03/2017 07:29:46 -0700'',''11:42:35'',''11:42:35.173'',''11:42:35,173'',''23/Apr 11:42:35,173'',''23/Apr/2017:11:42:35'',''23/Apr/2017 11:42:35'',''23-Apr-2017 11:42:35'',''23-Apr-2017 11:42:35.883'',''23 Apr 2017 11:42:35'',''23 Apr 2017 10:32:35*311'',''0423_11:42:35'',''0423_11:42:35.883'',''8/5/2011 3:31:18 AM:234'',''9/28/2011 2:23:15 PM'']
Ejemplo de uso:
print(parse_timestamp(datestrings[0], formats))
# OUTPUT
# (datetime.datetime(2018, 8, 20, 13, 20, 10, 633000, tzinfo=datetime.timezone.utc), ''%Y-%m-%dT%H:%M:%S*%f%z'')
Puede envolver la función para almacenar los argumentos junto con el resultado cada vez que llame a la versión envuelta:
from dateutil.parser import parse
from functools import wraps
def parse_wrapper(function):
@wraps(function)
def wrapper(*args):
return {''datetime'': function(*args), ''args'': args}
return wrapper
wrapped_parse = parse_wrapper(parse)
x = wrapped_parse("2014-01-01 00:12:12")
# {''datetime'': datetime.datetime(2014, 1, 1, 0, 12, 12),
# ''args'': (''2014-01-01 00:12:12'',)}