Dateutilで日付を解析した後に「フォーマット」を取得する方法はありますか?たとえば次のようなもの:
>>> 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
pdate:この質問に賞金を追加して、渡された一般的な日付文字列の文字列形式を取得するのと同等の回答を追加できるかどうかを確認します。必要に応じてdateutil
を使用できますが、必須ではありません。うまくいけば、ここでいくつかの創造的な解決策を得るでしょう。
私の考えは:
候補が1つしかない場合は、それが正しい形式であると確信できます。ただし、多くの場合、多くの可能性があります(特に、日付、月、分、時間がすべて0〜10の範囲にある場合)。
クラスの例:
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)
そしていくつかのサンプルテスト:
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
少しわかりやすくするために、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']
Dateutilで日付を解析した後に「形式」を取得する方法はありますか?
dateutil
では使用できません。問題は、解析中にdateutil
が中間結果としてフォーマットを持たないことです日時の個別のコンポーネントが個別に検出されるためです。 -かなり読みにくい ソースコード を見てください。
dateutil
(または他のpython私が知っているタイムスタンプパーサー)から解析済みの形式を返す方法がわかりません。
Datetimeオブジェクトとともに形式を返す独自のタイムスタンプ解析関数を実装することは、datetime.strptime()
を使用するとかなり簡単ですが、可能なタイムスタンプ形式の広く有用なリストに対して効率的に行うことはできません。
次の例では、タイムスタンプ形式のクイック検索の上位ヒットの1つから採用された50をわずかに超える形式のリストを利用しています。 dateutil
によって解析されるさまざまな形式の表面を傷つけることすらありません。一致するものが見つかるか、リスト内のすべての形式を使い果たすまで、各形式を順番にテストします( @ alecxe からの回答に示されているように、さまざまな日時部分を個別に検索するdateutil
のアプローチよりもはるかに効率が悪い可能性があります) 。
さらに、(オフセットの代わりに)タイムゾーン名を含むタイムスタンプ形式の例をいくつか含めました。これらの特定の日時文字列に対して以下の例の関数を実行すると、%Z
ディレクティブを使用して一致する形式を含めても、「形式を解析できません」を返すことがあります。タイムゾーン名を処理するための%Z
の使用に関する課題についての説明は、 バグ22377(bugs.python.orgの場合)22377 にあります(独自の日時を実装する別の重要な側面を強調するためだけです)解析関数)。
これらすべての注意事項を踏まえて、潜在的なフォーマットの管理可能なセットを処理している場合、以下のような単純なものを実装すると、必要なものが得られる場合があります。
日時文字列をフォーマットのリストと照合し、一致したフォーマットとともに日時オブジェクトを返す関数の例:
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')
タイムスタンプ、タイムゾーン、時間範囲、日付形式 から変更された形式と日時文字列の例:
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']
使用例:
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
を使用して、日付文字列を個々の可能な日付形式で解析します。datetime.strftime
でフォーマットします。結果がOriginの日付文字列と等しい場合、このフォーマットは可能な日付フォーマットです。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')
$ 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
私のアイデアは、このようなクラスを作成することでしたが、正確ではないかもしれません
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())
関数をラップして、ラップされたバージョンを呼び出すときはいつでも、結果とともに引数を格納できます。
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',)}