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

get UTC timestamp in python with datetime

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

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

应该导致

1
 1199145600

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

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

结果是

1
2007-12-31 23:00:00

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


什么是na?ve datetime

默认datetime对象被称为"天真":它们保留时间信息而没有时区信息。将na?ve datetime视为没有明确原点的相对数字(即:+4)(事实上,您的原点在整个系统边界内都是通用的)。考虑将datetime视为绝对数字(即:8),它具有整个世界的共同起源。

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

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

1
2
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?还是当地时间?

  • 如果你使用UTC(你没有遇到麻烦):

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    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非天真
    功能,通过给他们回到他们想要的时区。

    您需要时区名称和相关信息
    如果DST在产生目标天然日期时生效(
    角柜需要有关DST的最后信息):

    1
    2
    3
    4
    5
    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的后果:

    不使用is_dst将生成不正确的时间(和UTC时间戳)
    如果在落后DST到位时生成目标日期时间
    (例如,通过删除一小时来改变DST时间)。

    提供不正确的is_dst当然会产生错误
    时间(和UTC时间戳)仅限于DST重叠或孔。什么时候
    提供
    也是不正确的时间,发生在"洞"中(从未存在的时间到期)
    转发DST),is_dst将给出解释
    如何考虑这个虚假的时间,这是唯一的情况
    .normalize(..)实际上会在这里做点什么,因为它会
    将其翻译为实际有效时间(更改日期时间和
    DST对象(如果需要)。请注意,.normalize()不是必需的
    在最后有一个正确的UTC时间戳,但可能是
    如果您不喜欢在您的虚假时间中出现虚假时间的建议,建议您使用
    变量,特别是如果你在其他地方重用这个变量。

    并使用以下内容避免:(cf:使用pytz进行日期时区转换)

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

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

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

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

通过使用time.mktime我们可以创建utc_mktime

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
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对象。

最后一个解决方案是不正确的,因为它假设从现在起的UTC偏移量与EPOCH的UTC偏移量相同。对于很多时区而言并非如此(在夏令时(DST)偏移的一年中的特定时刻)。


另一种可能性是:

1
2
3
d = datetime.datetime.utcnow()
epoch = datetime.datetime(1970,1,1)
t = (d - epoch).total_seconds()

这适用于"d"和"epoch"都是天真的日期时间,使" -"运算符有效,并返回一个间隔。 total_seconds()将间隔转换为秒。 请注意,total_seconds()返回一个浮点数,甚至d.microsecond == 0


另请注意此博客条目所描述的calendar.timegm()函数:

1
2
import calendar
calendar.timegm(utc_timetuple)

输出应该与vaab的解决方案一致。


如果输入datetime对象是UTC:

1
2
3
>>> dt = datetime(2008, 1, 1, 0, 0, 0, 0)
>>> timestamp = (dt - datetime(1970, 1, 1)).total_seconds()
1199145600.0

注意:它返回浮点数,即微秒数表示为秒的分数。

如果输入日期对象是UTC:

1
2
3
4
>>> from datetime import date
>>> utc_date = date(2008, 1, 1)
>>> timestamp = (utc_date.toordinal() - date(1970, 1, 1).toordinal()) * 24*60*60
1199145600

有关详细信息,请参阅在Python中将datetime.date转换为UTC时间戳。


我觉得主要答案仍然不是那么清楚,值得花时间去理解时间和时区。

处理时间时最重要的是时间是相对的!

  • 2017-08-30 13:23:00 :(一个天真的日期时间),表示世界某处的当地时间,但请注意伦敦的2017-08-30 13:23:00与旧金山的2017-08-30 13:23:00不同。
  • 因为同一时间字符串可以被解释为不同的时间点,具体取决于您在世界中的位置,因此需要绝对的时间概念。

    UTC时间戳是Epoch的秒数(或毫秒)数(定义为1 January 1970 00:00:00 GMT时区+00:00偏移量)。

    Epoch锚定在GMT时区,因此是一个绝对的时间点。因此,UTC时间戳是与绝对时间的偏移,因此定义了绝对时间点。

    这使得可以及时订购事件。

    没有时区信息,时间是相对的,并且不能转换为绝对的时间概念,而不提供天真日期时间应锚定到的时区的一些指示。

    计算机系统中使用的时间类型是什么?

  • 天真日期时间:通常用于显示,在本地时间(即在浏览器中),OS可以向程序提供时区信息。

  • UTC时间戳:UTC时间戳是绝对时间点,如上所述,但它锚定在给定时区,因此UTC时间戳可以在任何时区转换为日期时间,但不包含时区信息。那是什么意思?这意味着1504119325对应于2017-08-30T18:55:24Z,或2017-08-30T17:55:24-01002017-08-30T10:55:24-0800。它不会告诉您记录的日期时间来自何处。它通常在服务器端用于记录事件(日志等)或用于将时区感知日期时间转换为绝对时间点并计算时间差异。

  • ISO-8601日期时间字符串:ISO-8601是一种标准格式,用于记录带有时区的日期时间。 (实际上有几种格式,请在此处阅读:https://en.wikipedia.org/wiki/ISO_8601)它用于在系统之间以可序列化的方式传达时区感知日期时间信息。

  • 什么时候用哪个?或者更确切地说,何时需要关心时区?

  • 如果您需要以任何方式关心时间,您需要时区信息。日历或闹钟需要时间来为世界上任何用户在当天的正确时间设置会议。如果此数据保存在服务器上,则服务器需要知道datetime对应的时区。

  • 要计算来自世界不同地方的事件之间的时差,UTC时间戳就足够了,但是你无法分析在什么时间发生的事件(例如,对于网络分析,你可能想知道用户什么时候来到你的当地时间的网站:你在早上或晚上看到更多用户吗?没有时间信息,你无法弄明白。

  • 日期字符串中的时区偏移量:

    另一点很重要,即日期字符串中的时区偏移量不固定。这意味着因为2017-08-30T10:55:24-0800表示偏移-0800或8小时后,并不意味着它总是会!

    在夏天,它可能是夏令时,它将是-0700

    这意味着时区偏移量(+0100)与时区名称(欧洲/法国)或时区名称(CET)不同

    America/Los_Angeles时区是世界上的一个地方,但它在冬天变为PST(太平洋标准时间)时区偏移表示法,夏季变为PDT(太平洋夏令时)。

    因此,除了从日期字符串中获取时区偏移之外,还应该使时区名称准确。

    大多数软件包都能够将数字偏移从夏令时转换为标准时间,但这并不一定只有偏移量。例如,西非的WAT时区指定是UTC + 0100,就像法国的CET时区一样,但法国观察夏令时,而西非没有(因为它们接近赤道)

    所以,简而言之,它很复杂。非常复杂,这就是为什么你不应该自己做,但相信一个为你做的包,并保持最新!

    好。


    使用utcfromtimestamp并指定时区确实存在问题。以下问题提供了一个很好的示例/解释:

    如何在转换为Unix时指定时区(UTC)? (Python)


    接受的答案似乎对我不起作用。 我的解决方案

    1
    2
    3
    4
    5
    import time
    utc_0 = int(time.mktime(datetime(1970, 01, 01).timetuple()))
    def datetime2ts(dt):
       """Converts a datetime object to UTC timestamp"""
        return int(time.mktime(dt.utctimetuple())) - utc_0


    最简单的方法:
    <击>

    1
    2
    3
    4
    >>> from datetime import datetime
    >>> dt = datetime(2008, 1, 1, 0, 0, 0, 0)
    >>> dt.strftime("%s")
    '1199163600'

    编辑:@Daniel是正确的,这会将其转换为机器的时区。 这是修改后的答案:

    1
    2
    3
    4
    5
    >>> from datetime import datetime, timezone
    >>> epoch = datetime(1970, 1, 1, 0, 0, 0, 0, timezone.utc)
    >>> dt = datetime(2008, 1, 1, 0, 0, 0, 0, timezone.utc)
    >>> int((dt-epoch).total_seconds())
    '1199145600'

    实际上,它甚至不需要指定timezone.utc,因为只要两个datetime具有相同的时区(或没有时区),时间差就是相同的。

    1
    2
    3
    4
    5
    >>> from datetime import datetime
    >>> epoch = datetime(1970, 1, 1, 0, 0, 0, 0)
    >>> dt = datetime(2008, 1, 1, 0, 0, 0, 0)
    >>> int((dt-epoch).total_seconds())
    1199145600