Existe-t-il un moyen d’obtenir le "format" après avoir analysé une date dans dateutil. Par exemple, quelque chose comme:
>>> 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
Mise à jour: Je voudrais ajouter une prime à cette question pour voir si quelqu'un pourrait ajouter une réponse qui ferait l'équivalent pour obtenir le format de chaîne d'une chaîne de date commune passée. Il peut utiliser dateutil
si vous voulez, mais ce n’est pas obligé. J'espère que nous aurons des solutions créatives ici.
Mon idée était de:
Si vous n'obtenez qu'un seul candidat, vous pouvez être certain que le format choisi est le bon. Mais vous aurez souvent beaucoup de possibilités (surtout avec des dates, des mois, des minutes et des heures comprises entre 0 et 10).
Exemple de classe:
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)
Et quelques exemples de tests:
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
Pour clarifier un peu, voici un exemple d'utilisation de ce code dans un shell 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']
Existe-t-il un moyen d'obtenir le "format" après l'analyse d'une date dans dateutil?
Pas possible avec dateutil
. Le problème est que dateutil
n'a jamais le format comme résultat intermédiaire à tout moment pendant l'analyse car il détecte séparément des composants de la date/heure - jetez un coup d'œil à cela, pas si facile à lire code source .
Je ne sais pas comment vous pouvez renvoyer le format analysé depuis dateutil
(ou tout autre analyseur d'horodatage python que je connaisse).
Implémenter votre propre méthode d'analyse d'horodatage qui renvoie le format avec l'objet datetime est assez trivial avec datetime.strptime()
, mais le faire efficacement avec une liste très utile de formats d'horodatage possibles ne l'est pas.
L'exemple suivant utilise une liste d'un peu plus de 50 formats adaptés de l'un des plus grands succès à partir d'une recherche rapide de formats d'horodatage. Il ne raye même pas la surface de la grande variété de formats analysés par dateutil
. Il teste chaque format dans l'ordre jusqu'à trouver une correspondance ou épuise tous les formats de la liste (probablement beaucoup moins efficace que la méthode dateutil
de localisation des différentes parties datetime de manière indépendante, comme indiqué dans la réponse de @alecxe ).
De plus, j'ai inclus quelques exemples de formats d'horodatage qui incluent des noms de fuseau horaire (au lieu de décalages). Si vous exécutez l'exemple de méthode ci-dessous sur ces chaînes datetime particulières, vous constaterez peut-être que la méthode renvoie "Impossible d'analyser le format", bien que j'aie inclus les formats correspondants à l'aide de la directive %Z
. Vous trouverez des explications sur les difficultés liées à l'utilisation de %Z
pour gérer les noms de fuseau horaire dans numéro 22377 à l'adresse bugs.python.org (juste pour mettre en évidence un autre aspect non trivial de l'implémentation de votre propre méthode d'analyse datetime).
Avec toutes ces mises en garde, si vous avez affaire à un ensemble gérable de formats potentiels, la mise en œuvre de quelque chose de simple comme ci-dessous peut vous donner ce dont vous avez besoin.
Exemple de méthode qui tente de faire correspondre une chaîne datetime à une liste de formats et renvoie l'objet datetime avec le format correspondant:
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')
Exemples de formats et de chaînes de date/heure adaptés à partir de Formats d’horodatage, de fuseaux horaires, de plages de temps et de date :
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']
Exemple d'utilisation:
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')
datetime.strptime
pour analyser la chaîne de date avec le format de date possible individuel.datetime.strftime
, si le résultat est égal à la chaîne de date d'origine, ce format est un format de date possible.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 __== '__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')
$ 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
Mon idée était de créer une classe quelque chose comme ça, peut-être pas juste
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())
Vous pouvez envelopper la fonction pour stocker les arguments avec le résultat chaque fois que vous appelez la version encapsulée:
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',)}