日期时间:四舍五入/截取微秒位数

95

我目前正在记录一些东西,并使用自己的格式化程序以及一个自定义的formatTime()函数:

def formatTime(self, _record, _datefmt):
    t = datetime.datetime.now()
    return t.strftime('%Y-%m-%d %H:%M:%S.%f')

我的问题是微秒,%f,有六个数字。是否有办法输出较少的数字,比如微秒的前三个数字?

13个回答

126
最简单的方法是使用切片,只需截取微秒的最后三位即可:
def format_time():
    t = datetime.datetime.now()
    s = t.strftime('%Y-%m-%d %H:%M:%S.%f')
    return s[:-3]

我强烈建议只进行截断。我曾经编写过一些记录代码,它会对时间戳进行四舍五入而不是截断,但我发现当舍入改变了最后一位数字时,实际上有点令人困惑。有计时代码在特定时间戳停止运行,但由于舍入,却有带有该时间戳的日志事件。更简单和可预测的方法是直接截断。

如果你想要将数字实际上进行舍入而不是只进行截断,那就需要更多的工作但并不糟糕:

def format_time():
    t = datetime.datetime.now()
    s = t.strftime('%Y-%m-%d %H:%M:%S.%f')
    head = s[:-7] # everything up to the '.'
    tail = s[-7:] # the '.' and the 6 digits after it
    f = float(tail)
    temp = "{:.03f}".format(f)  # for Python 2.x: temp = "%.3f" % f
    new_tail = temp[1:] # temp[0] is always '0'; get rid of it
    return head + new_tail

显然,您可以使用更少的变量来简化以上内容;我只是希望它非常易于理解。

你的四舍五入方法对我不起作用,它产生了这个结果:'2015-04-12 17:09:020.588',秒数是无效的。 - NuclearPeon
2
我在Python 2.7.6和Python 3.4.0中测试了您的代码。复制并粘贴您的代码,每次都得到相同的结果。您的秒数区域有3个数字。我正在查看代码,似乎您的浮点数(f变量)在小数点前添加了前导0(例如:0.367),这被附加为字符串。秒数区域中的前导0是出错的起点。 - NuclearPeon
3
@NuclearPeon,我向你道歉。你发现了一个真正的漏洞,这证明我在发布之前并没有真正测试过它。我已经编辑了代码来修复这个漏洞,现在应该可以为你工作了。感谢你让我注意到这一点。 - steveha
这个不起作用。当微秒值恰好为零时,秒值会被截断,例如:'2007-12-24T11:32'。 - Mohamed
当微秒至少为999500时,它会截断而不是四舍五入。在定义f之后,您可以通过以下方式进行修复:if f >= 1.0: return (t + datetime.timedelta(milliseconds=1)).strftime('%Y-%m-%d %H:%M:%S.000')(否则继续如之前所述)。 - Nick Matteo
这对于 logging.basicConfig 中的 datefmt 无效。 - Att Righ

57

从Python 3.6版本开始,该语言已经内置了此功能:

def format_time():
    t = datetime.datetime.now()
    s = t.isoformat(timespec='milliseconds')
    return s

1
最简单的方法就是这样! - Muhammmed Nihad

11
该方法应始终返回一个时间戳,其格式与此完全相同(取决于输入的dt对象是否包含时区信息): 2016-08-05T18:18:54.776+0000 它以datetime对象作为输入(您可以使用datetime.datetime.now()生成该对象)。要获得与示例输出相同的时区,您需要import pytz并传递datetime.datetime.now(pytz.utc)
import pytz, datetime


time_format(datetime.datetime.now(pytz.utc))

def time_format(dt):
    return "%s:%.3f%s" % (
        dt.strftime('%Y-%m-%dT%H:%M'),
        float("%.3f" % (dt.second + dt.microsecond / 1e6)),
        dt.strftime('%z')
    )   
我注意到之前提到的一些方法,如果存在尾随零,则会省略它(例如0.870变成了0.87),这会对我将这些时间戳馈送到解析器中造成问题。这种方法没有这个问题。

1
谢谢,这个对我最有效。我更新的唯一一件事是在主字符串模板中硬编码了 "Z",而不是使用 "%z"。 - wowkin2
@Eric Herot:非常感谢您的回答。关于时间格式 '2020-05-31T14:07:3:172Z',是否可以在分钟前面添加前导零来处理? - StackGuru
@Eric Herot:我明白了,返回 "%s:%06.3f%s" % ( dt.strftime('%Y-%m-%dT%H:%M'), float("%06.3f" % (dt.second + dt.microsecond / 1e6)), dt.strftime('Z') ) - StackGuru
@StackGuru 很奇怪,使用该格式化字符串时你会得到一个非零填充的分钟。根据文档(https://docs.python.org/3/library/datetime.html#strftime-and-strptime-format-codes),默认情况下应该是这样工作的。 - Eric Herot
和其他一些解决方案一样,这个方法不能正确地四舍五入: time_format(datetime.datetime(2020, 1, 1, 23, 59, 59, 999999)) 返回 '2020-01-01T23:59:60.000' - jtniehof

5
一个简单的解决方案,适用于所有情况:
def format_time():
    t = datetime.datetime.now()
    if t.microsecond % 1000 >= 500:  # check if there will be rounding up
        t = t + datetime.timedelta(milliseconds=1)  # manually round up
    return t.strftime('%Y-%m-%d %H:%M:%S.%f')[:-3]

基本上你首先要在日期对象本身上进行手动取整,然后你可以安全地截断微秒。

5

您可以从微秒中减去当前日期和时间。

d = datetime.datetime.now()
current_time = d - datetime.timedelta(microseconds=d.microsecond)

这将把 2021-05-14 16:11:21.916229 转换为 2021-05-14 16:11:21


它正在删除微秒,问题是另外一件事。 - Piyush Bag
问题要求“四舍五入或截取微秒”。我的答案是从当前日期时间中截取微秒。 - user

4

编辑:正如下面评论中一些人指出的那样,该解决方案(以及上面的一个解决方案)在微秒值达到999500时会引入问题,因为999.5会舍入为1000(溢出)。

除了重新实现strftime以支持我们想要的格式(由于舍入引起的潜在溢出需要向上传递到秒,然后是分钟等等),最简单的方法是像接受的答案中所述,将其截断到前3位,或使用以下内容:

'{:03}'.format(int(999999/1000))

-- 保留原始答案如下 --

在我的情况下,我正在尝试将日期戳格式化为毫秒格式化为“ddd”。 我最终使用的解决方法是使用datetime对象的microsecond属性,将其除以1000.0,如果必要则用零填充,并通过格式进行四舍五入。它看起来像这样:

'{:03.0f}'.format(datetime.now().microsecond / 1000.0)
# Produces: '033', '499', etc.

微秒是0 <= 微秒 < 1000000。所以对于一百万,它将产生“1000”。这正是我遇到的情况 https://www.devrant.io/rants/466105 - Dawid Gosławski
1
正如你所提到的,它可能不是完全的一百万,但是当微秒至少为999500时,“format”将其四舍五入为1000。您可以添加500微秒到日期时间并截断以获得相同的效果,同时自动处理四舍五入滚动到分钟、小时、天、月或年的情况,而不是进行四舍五入。或者,如果半毫秒的偏差不太重要,只需截断即可:'{:03}'.format(datetime.now().microsecond // 1000) - Nick Matteo

2
这是我使用正则表达式的解决方案:

import re

# Capture 6 digits after dot in a group.
regexp = re.compile(r'\.(\d{6})')
def to_splunk_iso(dt):
    """Converts the datetime object to Splunk isoformat string."""
    # 6-digits string.
    microseconds = regexp.search(dt.isoformat()).group(1)
    return regexp.sub('.%d' % round(float(microseconds) / 1000), dt.isoformat())

2

steveha 已经说过截断。那会将你向下舍入。但如果你想要四舍五入到最接近的数字,一般来说,舍入数字的方法是在截断之前,在小数位的位置上加上5。所以例如,要将12.3456四舍五入到小数点后3位,加上0.0005;这给出了12.3461,然后你将其截断为小数点后3个字符,得到了12.346。

问题是,如何正确地在下一位小数后加5。所以你不能只考虑微秒,如果你在除夕夜午夜前刚好超过0.9995秒时进行四舍五入,进位可能会一直到年份!

但你可以使用时间差(timedelta)。然后时间计算是正确的,你只需截断即可获得正确舍入的值。

要将秒数四舍五入到小数点后3位,你希望添加0.0005秒=500微秒。这里有3个例子:首先,将0.1234秒四舍五入为0.123,然后将0.1235秒四舍五入为0.124,然后当你应该亲吻某人祝他们新年快乐时,进位会一直传递到全年而你却在做Python:

>>> r = datetime.timedelta(microseconds=500)
>>> t = datetime.datetime(2023,1,2,3,45,6,123456)
>>> (t+r).strftime("%Y-%m-%d %H:%M:%S.%f")[:-3]
'2023-01-02 03:45:06.123'
>>> t = datetime.datetime(2023,1,2,3,45,6,123556)
>>> (t+r).strftime("%Y-%m-%d %H:%M:%S.%f")[:-3]
'2023-01-02 03:45:06.124'
>>> t = datetime.datetime(2023,12,31,23,59,59,999500)
>>> (t+r).strftime("%Y-%m-%d %H:%M:%S.%f")[:-3]
'2024-01-01 00:00:00.000'

1

这种方法允许灵活的精度,并且如果您指定了过大的精度,它将消耗整个微秒值。

def formatTime(self, _record, _datefmt, precision=3):
    dt = datetime.datetime.now()
    us = str(dt.microsecond)
    f = us[:precision] if len(us) > precision else us
    return "%d-%d-%d %d:%d:%d.%d" % (dt.year, dt.month, dt.day, dt.hour, dt.minute, dt.second, int(f))

这个方法实现了保留三位小数的四舍五入:
import datetime
from decimal import *

def formatTime(self, _record, _datefmt, precision='0.001'):
    dt = datetime.datetime.now()
    seconds = float("%d.%d" % (dt.second, dt.microsecond))
    return "%d-%d-%d %d:%d:%s" % (dt.year, dt.month, dt.day, dt.hour, dt.minute,
                             float(Decimal(seconds).quantize(Decimal(precision), rounding=ROUND_HALF_UP)))

我故意避免使用strftime方法,因为我宁愿不修改一个完全序列化的日期时间对象而不重新验证它。这种方式还显示了日期内部,以防您想进一步修改它。
在舍入示例中,请注意Decimal模块的精度是基于字符串的。

0
如果想要在结果中获取星期几(例如,“星期日”),那么仅使用切片“[:-3]”将无法实现。这时,您可以尝试使用:
dt = datetime.datetime.now()
print("{}.{:03d} {}".format(dt.strftime('%Y-%m-%d %I:%M:%S'), dt.microsecond//1000, dt.strftime("%A")))

#Output: '2019-05-05 03:11:22.211 Sunday'

%H - 代表24小时制

%I - 代表12小时制

谢谢。


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