带有ISO格式的Python UTC时间戳

时间:2017-02-10 08:30:26

标签: python datetime datetime-format python-datetime

我知道有人问过,但那里没有提供解决方案。 Python UTC datetime object's ISO format doesn't include Z (Zulu or Zero offset)

我正在寻找一种在Python中使用此格式生成UTC时间戳的 clean 方式。我需要的格式是 2013-10-29T09:38:41.341Z

具体来说,我需要包括" Z"在末尾。 Python的datetime.utcnow().isoformat()没有附加" Z"最后。

请注意,手动附加" Z"不是我能接受的解决方案。我正在寻找一种干净的方法来做到这一点。

使用后缀Z以ISO格式生成UTC时间戳的简洁方法是什么?

5 个答案:

答案 0 :(得分:2)

这样的东西
datetime.utcnow().isoformat()[:-3] + 'Z'

答案 1 :(得分:1)

您可以使用arrow库。

Arrow尚未涵盖,请参阅github issue。而且我认为任何python库都没有做到这一点。但现在破解它非常简单。

虽然需要与pip一起安装:

$ pip install arrow

然后获取你的iso格式,但没有Zulu格式

import arrow

arrow.utcnow().isoformat() 
#'2017-02-10T08:44:38.954159+00:00'

或者你自己制作。

arrow.utcnow().format('YYYY-MM-DDTHH:mm:ss.SSS') + 'Z'
# 2017-02-11T12:34:30.483Z

答案 2 :(得分:0)

zulu = "{}Z".format(arrow.utcnow().format('YYYY-MM-DDTHH:mm:ss.SSS'))
#'2018-11-28T21:54:49.639Z'

答案 3 :(得分:0)

由于缺乏声誉,我将其添加为新答案。

Tomasz Swider 解决方案是一个不错的开始,但是如果提供的时间没有微秒,[:-3]将会缩短秒数:

我将使用utcfromtimestamp进行演示:

In [10]: datetime.utcfromtimestamp(0.1).isoformat()[:-3] + 'Z'
Out[10]: '1970-01-01T00:00:00.100Z'

In [10]: datetime.utcfromtimestamp(0).isoformat()[:-3] + 'Z'
Out[11]: '1970-01-01T00:00Z'

我认为这是一种更清洁的解决方案,可以获取毫秒级的ISO日期和Zulu时间的“ Z”:

datetime.utcnow().isoformat(timespec='milliseconds')+ 'Z'

再次使用utcfromtimestamp进行演示:

In [25]: datetime.utcfromtimestamp(0.1).isoformat(timespec='milliseconds')+ 'Z'
Out[25]: '1970-01-01T00:00:00.100Z'

In [25]: datetime.utcfromtimestamp(0).isoformat(timespec='milliseconds')+ 'Z'
Out[25]: '1970-01-01T00:00:00.000Z'

答案 4 :(得分:0)

DJango 就是这样做的[1][2]

<块引用>

DjangoJSONEncoder

类 django.core.serializers.json.DjangoJSONEncoder¶

JSON 序列化程序使用 DjangoJSONEncoder 进行编码。的一个子类 JSONEncoder,它处理这些附加类型:

日期时间 ECMA-262 中定义的 YYYY-MM-DDTHH:mm:ss.sssZ 或 YYYY-MM-DDTHH:mm:ss.sss+HH:MM 形式的字符串。

    def default(self, o):
        # See "Date Time String Format" in the ECMA-262 specification.
        if isinstance(o, datetime.datetime):
            r = o.isoformat()
            if o.microsecond:
                r = r[:23] + r[26:]
            if r.endswith('+00:00'):
                r = r[:-6] + 'Z'
            return r
            print(f"aaa{naive_utcnow.isoformat()[:23] = }")

请注意日期 datetime 对象可能包含也可能不包含时区信息(称为 naiveaware 日期时间对象的区别)。

在您的示例中,datetime.utcnow() 将生成一个幼稚的对象,该对象无法与 django 代码一起正常工作。

如果您希望最后总是有一个 Z(例如为了与其他系统的互操作性,例如客户端浏览器和节点),请查看下面的脚本,其中我解释了如何到达那里以及如何处理使用 python 处理日期时间的一些常见陷阱:


from datetime import datetime, timezone

utc = timezone.utc
naive_utcnow = datetime.utcnow()
aware_utcnow = datetime.now(utc)

# there is no timezone info for naive objects here:
print(f"{naive_utcnow.isoformat() = }")

# with "+00:00":
print(f"{aware_utcnow.isoformat() = }")


# copy & paste from django implementation:
def toECMA262_django(dt: datetime):
    s = dt.isoformat()
    if dt.microsecond:
        s = s[:23] + s[26:]
    if s.endswith('+00:00'):
        s = s[:-6] + 'Z'
    return s


# note: django's version won't add Z for naive objects:
print(f"{toECMA262_django(naive_utcnow) = }")

# djanto's output is perfecly compatible with javacript
# for aware datetime objects:
print(f"{toECMA262_django(aware_utcnow) = }")


# improved version to treat naive objects as utc by default
def toECMA262_v2(dt: datetime, default_tz=utc):
    if not dt.tzinfo:
        dt = dt.replace(tzinfo=default_tz)
    s = dt.isoformat()
    if dt.microsecond:
        s = s[:23] + s[26:]
    if s.endswith('+00:00'):
        s = s[:-6] + 'Z'
    return s


# now has Z too:
print(f"{toECMA262_v2(naive_utcnow) = }")

print(f"{toECMA262_v2(aware_utcnow) = }")

# now works even with the misleading utcnow():
print(f"{toECMA262_v2(datetime.utcnow()) = }")

# CAREFUL: wrong result here, there is no distinction between
# naive objects returned from now() and utcnow(), the calling
# code is responsible for knowing if naive objects are in utc or not.
print(f"{toECMA262_v2(datetime.now()) = }")


# safer version, no default assumptions made
def toECMA262_v3(dt: datetime, naive_as_tz=None):
    if not dt.tzinfo and naive_as_tz:
        dt = dt.replace(tzinfo=naive_as_tz)
    s = dt.isoformat()
    if dt.microsecond:
        s = s[:23] + s[26:]
    if s.endswith('+00:00'):
        s = s[:-6] + 'Z'
    return s


# no tz offset for naive objects, unless explicitly specified:
print(f"{toECMA262_v3(naive_utcnow) = }")
print(f"{toECMA262_v3(naive_utcnow, utc) = }")

print(f"{toECMA262_v3(aware_utcnow) = }")

# no tz offset for naive objects, unless explicitly specified:
print(f"{toECMA262_v3(datetime.utcnow()) = }")
print(f"{toECMA262_v3(datetime.utcnow(), utc) = }")

# this is not wrong anymore, but no tz offset either
print(f"{toECMA262_v3(datetime.now()) = }")


# even safer, guarantees there will be a timezone or an exception is raised
def toECMA262_v4(dt: datetime, naive_as_tz=None):
    if not dt.tzinfo:
        if not naive_as_tz:
            raise ValueError('Aware object or naive_as_tz required')
        dt = dt.replace(tzinfo=naive_as_tz)
    s = dt.isoformat()
    if dt.microsecond:
        s = s[:23] + s[26:]
    if s.endswith('+00:00'):
        s = s[:-6] + 'Z'
    return s


def try_print(expr):
    '''little helper function to print exceptions in place'''
    try:
        print(f"{expr} = ", end='')
        print(repr(eval(expr)))
    except ValueError as exc:
        print(repr(exc))


# works with naive when tz is explicitly passed, otherwise raise:
try_print("toECMA262_v4(naive_utcnow, utc)")
try_print("toECMA262_v4(naive_utcnow)")  # raises
try_print("toECMA262_v4(aware_utcnow)")
try_print("toECMA262_v4(datetime.utcnow(), utc)")
try_print("toECMA262_v4(datetime.utcnow())")  # raises
try_print("toECMA262_v4(datetime.now())")  # raises


# Please note that if have an aware object that is not in utc,
# you will not get a string ending in Z, but the proper offset
# For example:
import dateutil.tz
tzlocal = dateutil.tz.tzlocal()
aware_now = datetime.now(tzlocal)
print(f"{toECMA262_v4(aware_now) = }")
# output '2021-05-25T04:15:44.848-03:00'


# version that always output Z ended strings:
def toECMA262_v5(dt: datetime, naive_as_tz=None):
    if not dt.tzinfo:
        if not naive_as_tz:
            raise ValueError('Aware object or naive_as_tz required')
        dt = dt.replace(tzinfo=naive_as_tz)
    dt = dt.astimezone(utc)
    s = dt.isoformat()
    if dt.microsecond:
        s = s[:23] + s[26:]
    if s.endswith('+00:00'):
        s = s[:-6] + 'Z'
    return s


# all possible cases supported and correct now, all returned with Z:
try_print("toECMA262_v5(naive_utcnow, utc)")
try_print("toECMA262_v5(naive_utcnow)")  # raises
try_print("toECMA262_v5(aware_utcnow)")
try_print("toECMA262_v5(aware_now)")
try_print("toECMA262_v5(datetime.utcnow(), utc)")
try_print("toECMA262_v5(datetime.utcnow())")  # raises
try_print("toECMA262_v5(datetime.now())")  # raises
try_print("toECMA262_v5(datetime.now(), tzlocal)")  # works fine now ;)

脚本的输出:

naive_utcnow.isoformat() = '2021-05-25T07:45:22.774853'
aware_utcnow.isoformat() = '2021-05-25T07:45:22.774856+00:00'

toECMA262_django(naive_utcnow) = '2021-05-25T07:45:22.774'
toECMA262_django(aware_utcnow) = '2021-05-25T07:45:22.774Z'

toECMA262_v2(naive_utcnow) = '2021-05-25T07:45:22.774Z'
toECMA262_v2(aware_utcnow) = '2021-05-25T07:45:22.774Z'
toECMA262_v2(datetime.utcnow()) = '2021-05-25T07:45:22.774Z'
toECMA262_v2(datetime.now()) = '2021-05-25T04:45:22.774Z'

toECMA262_v3(naive_utcnow) = '2021-05-25T07:45:22.774'
toECMA262_v3(naive_utcnow, utc) = '2021-05-25T07:45:22.774Z'
toECMA262_v3(aware_utcnow) = '2021-05-25T07:45:22.774Z'
toECMA262_v3(datetime.utcnow()) = '2021-05-25T07:45:22.775'
toECMA262_v3(datetime.utcnow(), utc) = '2021-05-25T07:45:22.775Z'
toECMA262_v3(datetime.now()) = '2021-05-25T04:45:22.775'

toECMA262_v4(naive_utcnow, utc) = '2021-05-25T07:45:22.774Z'
toECMA262_v4(naive_utcnow) = ValueError('Aware object or naive_as_tz required')
toECMA262_v4(aware_utcnow) = '2021-05-25T07:45:22.774Z'
toECMA262_v4(datetime.utcnow(), utc) = '2021-05-25T07:45:22.775Z'
toECMA262_v4(datetime.utcnow()) = ValueError('Aware object or naive_as_tz required')
toECMA262_v4(datetime.now()) = ValueError('Aware object or naive_as_tz required')
toECMA262_v4(aware_now) = '2021-05-25T04:45:22.788-03:00'

toECMA262_v5(naive_utcnow, utc) = '2021-05-25T07:45:22.774Z'
toECMA262_v5(naive_utcnow) = ValueError('Aware object or naive_as_tz required')
toECMA262_v5(aware_utcnow) = '2021-05-25T07:45:22.774Z'
toECMA262_v5(aware_now) = '2021-05-25T07:45:22.788Z'
toECMA262_v5(datetime.utcnow(), utc) = '2021-05-25T07:45:22.788Z'
toECMA262_v5(datetime.utcnow()) = ValueError('Aware object or naive_as_tz required')
toECMA262_v5(datetime.now()) = ValueError('Aware object or naive_as_tz required')
toECMA262_v5(datetime.now(), tzlocal) = '2021-05-25T07:45:22.788Z'

以上版本 5 始终输出 Z 结尾的 ECMA-262 兼容字符串,接受任何时区中的日期时间对象。如果传递了原始日期时间,则调用者代码必须指定对象是 utc、本地还是任何其他时区,并且它会自动转换为 utc。


PS:我使用 python >= 3.8 的较新 fstring 调试语法和 = 以更友好/简洁的方式打印输出,此外代码应该在 python >= 3.2 下运行良好