Python中的strptime格式及其可选位

89

现在我有:

timestamp = datetime.strptime(date_string, '%Y-%m-%d %H:%M:%S.%f')

除非我要转换一个没有微秒的字符串,否则这个方法很好用。如何指定微秒是可选的(如果字符串中没有微秒,则应将其视为0)?

9个回答

80

您可以使用try/except块:

try:
    timestamp = datetime.strptime(date_string, '%Y-%m-%d %H:%M:%S.%f')
except ValueError:
    timestamp = datetime.strptime(date_string, '%Y-%m-%d %H:%M:%S')

2
有点遗憾的是,这取决于异常作为控制流 - Joe Sadoski
2
@JoeSadoski 我不认为上面的解决方案符合您链接文章中所描述的内容。 - Alexander
1
虽然它能工作,但这不是 try/catch 的正确使用方式——这里没有发生任何异常情况。 - developer

47

如果它不存在,那么只需追加它怎么样?

if '.' not in date_string:
    date_string = date_string + '.0'

timestamp = datetime.strptime(date_string, '%Y-%m-%d %H:%M:%S.%f')

20
这是一个不错的回答,但我感到失望的是,一个旨在将日期和时间字符串转换为日期和时间对象的库没有处理这些非常简单的用例。这样的库的整个目的是为了让用户无需担心这些细节。 - Auspice
2
与使用 try/catch 相比,我非常喜欢这个答案。 - sniperd

12

虽然我来晚了,但我发现如果你不关心可选部分,这将为你移除.%f

datestring.split('.')[0]

1
谢谢。这个方案比其他长度解决方案更适合我的使用情况。 - vu le

10

我更喜欢使用正则表达式匹配而不是尝试和捕获异常。这样可以允许多种可接受格式的回退。

# full timestamp with milliseconds
match = re.match(r"\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}\.\d+Z", date_string)
if match:
    return datetime.strptime(date_string, "%Y-%m-%dT%H:%M:%S.%fZ")

# timestamp missing milliseconds
match = re.match(r"\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}Z", date_string)
if match:
    return datetime.strptime(date_string, "%Y-%m-%dT%H:%M:%SZ")

# timestamp missing milliseconds & seconds
match = re.match(r"\d{4}-\d{2}-\d{2}T\d{2}:\d{2}Z", date_string)
if match:
    return datetime.strptime(date_string, "%Y-%m-%dT%H:%MZ")

# unknown timestamp format
return false

不要忘记为这个方法导入"re"和"datetime"模块。

我认为这个答案应该得到更多的投票 - 这是很好的编程逻辑 - 如果你想创建一个时间戳解析器。 - developer

2
datetime(*map(int, re.findall('\d+', date_string)))

可以解析 '%Y-%m-%d %H:%M:%S.%f''%Y-%m-%d %H:%M:%S' 格式的日期时间。如果您未对输入进行过滤,则太宽松了。

这个方法很快但有时候 strptime() 的执行速度过慢。如果您知道输入的日期格式是符合预期的,那么可以使用该方法。


如果在“date_string”中省略了微秒部分的尾随零,则会导致结果不正确。 - jez
1
@jez:是的,这就是我说它“过于宽容”的原因。它只在输入具有预期格式(微秒为零或6位数字)时才起作用。2-关于您的编辑:看一下问题:“datetime”在这里是类,而不是模块。 - jfs

1
如果您正在使用Pandas,还可以过滤Series并将其连接起来。索引会自动加入。
import pandas as pd

# Every other row has a different format
df = pd.DataFrame({"datetime_string": ["21-06-08 14:36:09", "21-06-08 14:36:09.50", "21-06-08 14:36:10", "21-06-08 14:36:10.50"]})
df["datetime"] = pd.concat([
    pd.to_datetime(df["datetime_string"].iloc[1::2], format="%y-%m-%d %H:%M:%S.%f"),
    pd.to_datetime(df["datetime_string"].iloc[::2], format="%y-%m-%d %H:%M:%S"),
])


日期时间字符串 日期时间
0 21-06-08 14:36:09 2021-06-08 14:36:09
1 21-06-08 14:36:09.50 2021-06-08 14:36:09.500000
2 21-06-08 14:36:10 2021-06-08 14:36:10
3 21-06-08 14:36:10.50 2021-06-08 14:36:10.500000

0
对于那些想要更优雅的解决方案的人,我创建了一个小模块,这样你就不必自己动手了。它接受普通格式字符串,但使用方括号作为可选项。使用简单的计数算法来捕获带方括号的“可选项”,并使用组合库构建可能的输入日期格式字符串的组合列表。
它不能高效地找到所有可能的组合,所以在运行任何密集逻辑之前,我建议先输入和存储格式列表,但对于任何人来说,这应该是一个不错的解决方案。
需要注意的是,如果你没有正确格式化方括号,会出现丑陋的索引错误。如果有需要,可以随意创建自己的异常处理缺少闭合方括号的情况。我也不确定是否充分处理了所有嵌套方括号的情况。不过我进行了广泛的测试,所以我相当确信这将涵盖所有情况。当然,如果你想使用除了[]方括号之外的其他符号,我还提供了一些易于更改的属性变量。
from datetime import datetime
from itertools import combinations

opening_char = '['
closing_char = ']'

def parse_datetime(date_string, date_formats):
    for format_string in date_formats:
        try:
            parsed_date = datetime.strptime(date_string, format_string)
            return parsed_date
        except ValueError:
            continue
    
    print(f"Unable to parse date with any given format for string: {date_string}")
    return None

def _extract_optional_components(format_string):
    if opening_char in format_string:
        sub_strings = _get_bracketed_strings(format_string)
        
        for s in sub_strings:
            s.replace(opening_char, '')
            s.replace(closing_char, '')
        
        return sub_strings
    else:
        return []
                
def _get_bracketed_strings(input_string):
    sub_strings = []
    for i, char in enumerate(input_string):
        if char == opening_char:
            openpos = i
            closepos = openpos
            counter = 1
            while counter > 0:
                closepos += 1
                c = format_string[closepos]
                if c == opening_char:
                    counter += 1
                elif c == closing_char:
                    counter -= 1
            sub_strings.append(input_string[openpos + 1:closepos])
    return sub_strings

def _generate_date_formats(format_string):
    optional_components = _extract_optional_components(format_string)
    num_optionals = len(optional_components)
    
    all_combinations = []
    for r in range(num_optionals + 1):
        for combination in combinations(range(num_optionals), r):
            all_combinations.append(combination)

    output_formats = []
    for combination in all_combinations:
        new_format = format_string
        for i in range(num_optionals):
            if i in combination:
                new_format = new_format.replace(f'[{optional_components[i]}]', optional_components[i])
            else:
                new_format = new_format.replace(f'[{optional_components[i]}]', '')

        output_formats.append(new_format)

    return output_formats


if __name__ == "__main__":
    # Example usage
    format_string = "%Y-%m-%d[T%H:%M:%S[.%f]][Z]"
    optional_format_list = _generate_date_formats(format_string)

    date_string1 = "2023-06-16T03:09:23.155Z"
    date_string2 = "2023-06-16T02:53:18Z"
    date_string3 = "2023-06-16"

    datetime_obj1 = parse_datetime(date_string1, optional_format_list)
    datetime_obj2 = parse_datetime(date_string2, optional_format_list)
    datetime_obj3 = parse_datetime(date_string3, optional_format_list)

    print(datetime_obj1)  # 2023-06-16 03:09:23.155000+00:00
    print(datetime_obj2)  # 2023-06-16 02:53:18+00:00
    print(datetime_obj3)  # 2023-06-16 00:00:00+00:00

-2

使用一个正则表达式和一些列表表达式

time_str = "12:34.567"
# time format is [HH:]MM:SS[.FFF]
sum([a*b for a,b in zip(map(lambda x: int(x) if x else 0, re.match(r"(?:(\d{2}):)?(\d{2}):(\d{2})(?:\.(\d{3}))?", time_str).groups()), [3600, 60, 1, 1/1000])])
# result = 754.567

1
我猜这既不易读,也没有用处...你完全在重新发明轮子,而不是使用strptime()并解决可选微秒的问题。 - hnwoh

-3

对于我类似的问题,使用jq,我使用了以下代码:

|split("Z")[0]|split(".")[0]|strptime("%Y-%m-%dT%H:%M:%S")|mktime

作为按时间正确排序列表的解决方案。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接