使用 datetime 在 python 中获取 UTC 时间戳

2022-01-13 00:00:00 python datetime timestamp utc

问题描述

有没有办法通过指定日期来获取 UTC 时间戳?我的期望:

Is there a way to get the UTC timestamp by specifying the date? What I would expect:

datetime(2008, 1, 1, 0, 0, 0, 0)

应该会导致

 1199145600

创建一个简单的日期时间对象意味着没有时区信息.如果我查看 datetime.utcfromtimestamp 的文档,创建 UTC 时间戳意味着忽略时区信息.所以我猜想,创建一个天真的日期时间对象(就像我所做的那样)会导致一个 UTC 时间戳.然而:

Creating a naive datetime object means that there is no time zone information. If I look at the documentation for datetime.utcfromtimestamp, creating a UTC timestamp means leaving out the time zone information. So I would guess, that creating a naive datetime object (like I did) would result in a UTC timestamp. However:

then = datetime(2008, 1, 1, 0, 0, 0, 0)
datetime.utcfromtimestamp(float(then.strftime('%s')))

结果

2007-12-31 23:00:00

datetime 对象中是否还有隐藏的时区信息?我做错了什么?

Is there still any hidden time zone information in the datetime object? What am I doing wrong?


解决方案

朴素 datetime 与有意识 datetime

Naïve datetime versus aware datetime

默认的 datetime 对象被称为naïve":它们保留时间信息而没有时区信息.将朴素的 datetime 视为一个没有明确来源的相对数字(即:+4)(实际上,您的来源在整个系统边界中都很常见).

Default datetime objects are said to be "naïve": they keep time information without the time zone information. Think about naïve datetime as a relative number (ie: +4) without a clear origin (in fact your origin will be common throughout your system boundary).

相比之下,将感知 datetime 视为具有全球共同起源的绝对数字(即:8).

In contrast, think about aware datetime as absolute numbers (ie: 8) with a common origin for the whole world.

没有时区信息,您无法将朴素"日期时间转换为任何非朴素时间表示(如果我们不知道从哪里到哪里,+4 目标在哪里开始 ?).这就是为什么你不能有 datetime.datetime.toutctimestamp() 方法的原因.(参见:http://bugs.python.org/issue1457227)

Without timezone information you cannot convert the "naive" datetime towards any non-naive time representation (where does +4 targets if we don't know from where to start ?). This is why you can't have a datetime.datetime.toutctimestamp() method. (cf: http://bugs.python.org/issue1457227)

要检查您的 datetime dt 是否幼稚,请检查 dt.tzinfo,如果 None,那么它是天真:

To check if your datetime dt is naïve, check dt.tzinfo, if None, then it's naïve:

datetime.now()        ## DANGER: returns naïve datetime pointing on local time
datetime(1970, 1, 1)  ## returns naïve datetime pointing on user given time

我的约会时间很幼稚,我该怎么办?

您必须根据您的特定上下文做出假设:您必须问自己的问题是:您的 datetime 是 UTC 吗?还是当地时间?

You must make an assumption depending on your particular context: The question you must ask yourself is: was your datetime on UTC ? or was it local time ?

  • 如果您使用的是 UTC(您就没有麻烦了):

import calendar

def dt2ts(dt):
    """Converts a datetime object to UTC timestamp

    naive datetime will be considered UTC.

    """

    return calendar.timegm(dt.utctimetuple())

  • 如果您没有使用 UTC,欢迎来到地狱.

    在使用前者之前,您必须使您的 datetime 不幼稚功能,通过将它们归还给它们预期的时区.

    You have to make your datetime non-naïve prior to using the former function, by giving them back their intended timezone.

    您需要时区名称和关于时区的信息如果 DST 在生成目标朴素日期时间时生效(角盒需要有关 DST 的最后信息):

    You'll need the name of the timezone and the information about if DST was in effect when producing the target naïve datetime (the last info about DST is required for cornercases):

    import pytz     ## pip install pytz
    
    mytz = pytz.timezone('Europe/Amsterdam')             ## Set your timezone
    
    dt = mytz.normalize(mytz.localize(dt, is_dst=True))  ## Set is_dst accordingly
    

    不提供is_dst的后果:

    Consequences of not providing is_dst:

    不使用 is_dst 将生成不正确的时间(和 UTC 时间戳)如果在设置后向 DST 时生成了目标日期时间(例如通过删除一小时来更改 DST 时间).

    Not using is_dst will generate incorrect time (and UTC timestamp) if target datetime was produced while a backward DST was put in place (for instance changing DST time by removing one hour).

    提供不正确的is_dst当然会产生不正确的时间(和 UTC 时间戳)仅在 DST 重叠或孔上.什么时候提供时间也不正确,发生在洞"中(由于时间不存在向前移动 DST),is_dst 将给出解释如何考虑这个虚假时间,这是唯一的情况.normalize(..) 实际上会在这里做一些事情,因为它会将其翻译为实际有效时间(更改日期时间和DST 对象(如果需要).注意 .normalize() 不是必需的最后有一个正确的 UTC 时间戳,但可能是如果你不喜欢在你的时间里有虚假时间的想法,推荐变量,特别是如果您在其他地方重复使用此变量.

    Providing incorrect is_dst will of course generate incorrect time (and UTC timestamp) only on DST overlap or holes. And, when providing also incorrect time, occuring in "holes" (time that never existed due to forward shifting DST), is_dst will give an interpretation of how to consider this bogus time, and this is the only case where .normalize(..) will actually do something here, as it'll then translate it as an actual valid time (changing the datetime AND the DST object if required). Note that .normalize() is not required for having a correct UTC timestamp at the end, but is probably recommended if you dislike the idea of having bogus times in your variables, especially if you re-use this variable elsewhere.

    和避免使用以下内容:(参见:使用 pytz 进行日期时间时区转换)

    dt = dt.replace(tzinfo=timezone('Europe/Amsterdam'))  ## BAD !!
    

    为什么?因为 .replace() 盲目地替换了 tzinfo 而没有考虑到目标时间,会选择一个坏的 DST 对象.而 .localize() 使用目标时间和您的 is_dst 提示选择正确的 DST 对象.

    Why? because .replace() replaces blindly the tzinfo without taking into account the target time and will choose a bad DST object. Whereas .localize() uses the target time and your is_dst hint to select the right DST object.

    旧的错误答案(感谢@J.F.Sebastien 提出这个问题):

    OLD incorrect answer (thanks @J.F.Sebastien for bringing this up):

    希望,当您创建天真的 datetime 对象时,很容易猜测时区(您的本地来源),因为它与您希望不会在天真的日期时间之间更改的系统配置有关对象创建和您想要获取 UTC 时间戳的时刻.这个技巧可以用来给出一个不完美的问题.

    Hopefully, it is quite easy to guess the timezone (your local origin) when you create your naive datetime object as it is related to the system configuration that you would hopefully NOT change between the naive datetime object creation and the moment when you want to get the UTC timestamp. This trick can be used to give an imperfect question.

    通过使用time.mktime,我们可以创建一个utc_mktime:

    By using time.mktime we can create an utc_mktime:

    def utc_mktime(utc_tuple):
        """Returns number of seconds elapsed since epoch
    
        Note that no timezone are taken into consideration.
    
        utc tuple must be: (year, month, day, hour, minute, second)
    
        """
    
        if len(utc_tuple) == 6:
            utc_tuple += (0, 0, 0)
        return time.mktime(utc_tuple) - time.mktime((1970, 1, 1, 0, 0, 0, 0, 0, 0))
    
    def datetime_to_timestamp(dt):
        """Converts a datetime object to UTC timestamp"""
    
        return int(utc_mktime(dt.timetuple()))
    

    您必须确保您的 datetime 对象与创建您的 datetime 对象的时区在同一时区.

    You must make sure that your datetime object is created on the same timezone than the one that has created your datetime.

    最后一个解决方案是不正确的,因为它假设从现在开始的 UTC 偏移量与从 EPOCH 开始的 UTC 偏移量相同. 对于很多时区来说,情况并非如此(在特定时刻夏令时 (DST) 偏移量的年度最佳值).

    This last solution is incorrect because it makes the assumption that the UTC offset from now is the same than the UTC offset from EPOCH. Which is not the case for a lot of timezones (in specific moment of the year for the Daylight Saving Time (DST) offsets).

  • 相关文章