Python 获取dateutil.parse中的格式

Python 获取dateutil.parse中的格式,python,python-3.x,python-dateutil,Python,Python 3.x,Python Dateutil,在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

在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

更新:我想为这个问题添加一个悬赏,看看是否有人可以添加一个答案,该答案在传递普通日期字符串的字符串格式时起到同等作用。如果需要,它可以使用
dateutil
,但不必这样做。希望我们能在这里找到一些创造性的解决方案

在dateutil中解析日期后,是否有方法获取“格式”


不能使用
dateutil
。问题是,
dateutil
在解析过程中的任何时候都不会将该格式作为中间结果,因为它会分别检测datetime的各个组件-看看这个不太容易阅读的部分。

我不知道有什么方法可以从
dateutil
返回解析后的格式(或者我所知道的任何其他python时间戳解析器)

使用
datetime.strtime()
实现自己的时间戳解析函数,返回格式和datetime对象,这是非常简单的,但是针对广泛使用的可能的时间戳格式列表高效地执行这一功能却不是一件容易的事

下面的示例使用了一个列表,其中有50多种格式是从快速搜索时间戳格式的热门内容中改编而来的。它甚至没有触及由
dateutil
解析的各种格式的表面。它按顺序测试每种格式,直到找到匹配项或用尽列表中的所有格式(与独立定位不同日期时间部分的
dateutil
方法相比,效率可能要低得多,如中的答案所述)

此外,我还提供了一些包含时区名称(而不是偏移量)的时间戳格式示例。如果针对这些特定的日期时间字符串运行下面的示例函数,您可能会发现它返回“无法解析格式”尽管我已经包含了使用
%Z
指令的匹配格式。关于使用
%Z
处理时区名称所面临的挑战,可以在中找到一些解释(只是为了强调实现自己的日期时间解析功能的另一个非平凡方面)

有了所有这些注意事项,如果您正在处理一组可管理的潜在格式,实现下面这样简单的东西可能会满足您的需要

尝试根据格式列表匹配datetime字符串并返回datetime对象和匹配格式的示例函数:

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')
示例格式和日期时间字符串改编自:

用法示例:

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')

我的想法是创建一个类似这样的类,可能不准确

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())
我的想法是:

  • 创建一个对象,其中包含您认为可能在日期模式中的候选说明符列表(添加的越多,从另一端获得的可能性就越大)
  • 解析日期字符串
  • 根据您提供的日期和候选列表,为字符串中的每个元素创建一个可能的说明符列表
  • 重新组合它们以生成“可能”列表
  • 如果你只有一个候选人,你可以很肯定这是正确的格式。但你通常会得到很多可能性(特别是日期、月份、分钟和小时都在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 shell中使用此代码的一些示例:

    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']
    
    想法:
  • 检查用户输入的日期字符串,并生成可能的日期格式集
  • 在格式集上循环,使用
    datetime.strtime
    使用单个可能的日期格式解析日期字符串
  • 使用
    datetime.strftime
    格式化步骤2中的日期,如果结果等于原始日期字符串,则此格式是一种可能的日期格式
  • 算法实现 用法:
    无论何时调用包装版本,都可以包装函数以存储参数和结果:

    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',)}
    

    @Alexe哇,好的。你建议如何在dateutil的帮助下获取字符串格式?@David542这需要一些时间来研究,这取决于你试图解决的实际问题。根据问题的范围和可能的输入,你可以只迭代常见的datetime格式和我们必须一个接一个地尝试。例如,你可以使用和尝试流行格式,直到它起作用为止-它的工作格式将是字符串中的日期时间字符串的格式。我知道了,我喜欢这种方法,我会尝试。有没有一个地方可能包含大约十个“最流行”的格式日期格式?@David542这是一个很好的问题,
    dateparser
    提供了一些流行格式的示例:,但不确定最佳位置。谢谢,让好的工作和好的问题继续进行,David。谢谢,为它添加了一个奖励以鼓励一些答案。因此,您需要反向使用python日期格式,如字符串
    %Y-%m-%d%H:%m:%S
    或类似字符串的ISO格式
    YYYY-MM-DD HH:MM:SS
    ?@Enix让我们说python。@SebastiaanvandenBroek这是针对csv/excel解析器的,我们无法控制用户的输入,希望能够检测到可能使用的一些常见模式。我喜欢这个问题。您看过解析器的UnitTest吗?如果没有,您可以查看d it,
    dateutil
    支持一整套格式,但是当您看到本文中的解析器代码时,您会发现没有一种方法可以直接执行您要求的同一个包。您可以提取该代码并实现所需的输出,我认为这是最佳方式。否则@benvc answer应该足够了。也许我这就是你要找的?如果是“2017年1月4日”之类的东西,或者真的是什么东西,那该怎么办?(我用最常用的日期格式YYYY-MM-DD进行了尝试,但它不起作用)你需要通过%Y-%m-%d,如果你是pa,它将返回YYYY-MM DDI
    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 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',)}