Python UTC时间戳的ISO格式化

8
我知道这个问题被问过,但是那里没有提供解决方案。Python UTC datetime object's ISO format doesn't include Z (Zulu or Zero offset) 我正在寻找一种在 Python 中以这种格式生成 UTC 时间戳的简洁方法。我需要的格式是2013-10-29T09:38:41.341Z
具体来说,我需要在末尾包含 "Z"。 Python 的 datetime.utcnow().isoformat() 不会在结尾附加 "Z"。
请注意,手动添加 "Z" 不是我可以接受的解决方案。 我正在寻找一种简洁的方式来解决这个问题。
如何以 ISO 格式生成带有后缀 Z 的 UTC 时间戳?

"如果时间是UTC时间,请在时间后面直接添加Z,不要加空格。 "——如果正确操作,附加它是没有问题的。这是一个不错的方法,因为它可以很好地转换回来。" - l'L'l
5个回答

11

不妨考虑类似的东西

datetime.utcnow().isoformat()[:-3] + 'Z'

5
如我所写,像这样手动添加是不可接受的。这是一种粗制滥造的方法。 - leopoodle
2
如果你认为这是一个优雅的解决方案,那么你的标准就和我不同。我不希望看到我的代码做出这样的事情。在我看来,它绝对不是干净的。 - leopoodle
@SteveHe:那么你的需求将不得不改变以适应你喜欢的代码。 - John Zwinck
如果需求不是由我设定的,该怎么办?这正是我的处境。我无法改变。 - leopoodle
你可以使用datetime.utcnow().strftime('%Y-%m-%dT%H:%M:%S.%fZ'),但是它不包含毫秒,所以你将得到类似'2017-02-10T08:49:24.862346Z'的结果。 - Tomasz Swider
我认为将审美感应用于编码并不明智 :), 例如,Python的内部是一个大开关语句,丑陋得像母亲之夜。 - Tomasz Swider

5
由于声誉不足,我将其作为新答案添加。
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'

我认为这是一种更简洁的方法来获取带有毫秒和“Z”标志的ISO日期,以表示协调世界时:

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'

3
现在尝试使用datetime.datetime.now(tz=datetime.timezone.utc).isoformat()+'Z',它返回...+00:00Z,但这是无效的。该操作者有很好的理由希望避免手动添加“Z”。 - AVee

4
你可以使用arrow库。
目前Arrow还没有涵盖此功能,请参见github问题。 我认为目前没有任何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

1
仔细阅读问题。这不是我想要的格式。我想要后缀“Z”,而不是“+00:00”。 - leopoodle
3
arrow.utcnow().format('YYYY-MM-DDTHH:mm:ss.SSS') + 'Z' 运行良好,非常感谢。 - RCP
1
目前来说,这很容易。您可以使用以下代码:arrow.utcnow().format("YYYY-MM-DDThh:mm:ss.SSS[Z]") - Vadim Birkos

2
这就是 DJango 的做法 [1], [2]

DjangoJSONEncoder

class django.core.serializers.json.DjangoJSONEncoder¶

JSON 序列化器使用 DjangoJSONEncoder 进行编码。它是 JSONEncoder 的子类,可以处理以下附加类型:

datetime 一个形如 YYYY-MM-DDTHH:mm:ss.sssZ 或者 YYYY-MM-DDTHH:mm:ss.sss+HH:MM 的字符串,格式定义在 ECMA-262 中。

    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()将生成一个naive对象,它与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及以上总是输出以ECMA-262兼容的字符串结尾的Z,可以接受任何时区的日期时间对象。如果传递了无时区意义的日期时间对象,调用者代码必须指定对象是否在UTC、本地或其他任何时区,并自动转换为UTC。
PS:我使用了Python>=3.8的新fstring调试语法,通过使用=更友好且简洁地打印输出,除此之外,代码应该可以在Python>=3.2上正常运行。

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

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