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

104

是否有一种方法可以通过指定日期来获取UTC时间戳?我期望得到的结果是:

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

应该得到的结果是

 1199145600

创建一个天真的datetime对象意味着没有时区信息。如果我查看datetime.utcfromtimestamp的文档,创建UTC时间戳意味着省略时区信息。因此我猜测,创建一个天真的datetime对象(像我做的那样)会得到一个UTC时间戳。然而:

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

导致结果

2007-12-31 23:00:00

datetime对象中是否仍然存在隐藏的时区信息?我做错了什么吗?


问题在于then.strftime('%s')期望本地时间,但时间戳表明datetime(2008, 1, 1)处于UTC时区。 - jfs
就我个人而言,我认为这个相关回答非常有帮助。 - bluenote10
10个回答

107

本地时间与带时区的时间

默认情况下,datetime对象被称为"本地时间":它们只保留时间信息,而不包含时区信息。可以将本地时间类比为相对数字(例如:+4),但没有明确的起点(实际上,您的起点将在系统边界内通用)。

相反,考虑到带时区的时间是绝对数字(例如:8),具有整个世界的共同起点。

如果缺少时区信息,则无法将“本地时间”转换为任何非本地化的时间表示形式(如果我们不知道从哪里开始,那么+4代表什么?)。这就是为什么不能有一个datetime.datetime.toutctimestamp()方法的原因(参见:http://bugs.python.org/issue1457227)。

要检查您的datetime dt是否为本地时间,请检查dt.tzinfo,如果为None,则为本地时间:

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是天真的,我该怎么办?

根据您的特定上下文,您必须进行假设:

您需要问自己的问题是:您的datetime是否在UTC上?还是在本地时间上?

  • 如果您正在使用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不再是naïve状态,方法是将它们的预期时区赋回给它们。

    在生成目标naïve datetime时,您需要知道时区的名称DST是否生效的信息(关于DST的最后信息是必需的,以处理边缘情况):

    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 的后果:

    如果目标日期时间在向后的 DST (例如通过减少一个小时更改 DST 时间)期间生成, 则不使用 is_dst 会生成错误的时间(以及 UTC 时间戳)。

    提供不正确的 is_dst 将在 DST 重叠或漏洞时生成不正确的 时间(以及 UTC 时间戳)。而在“漏洞”中提供不正确的时间(由于向前移动 DST 而从未存在的时间)时,is_dst 将给出一个如何考虑这个虚假时间的解释,这是唯一的情况 .normalize(..) 实际上会发挥作用,因为它将把它转换为实际的有效时间(如果需要,则同时更改日期时间和 DST 对象)。请注意,要获得正确的 UTC 时间戳,并不需要使用 .normalize(),但如果您不喜欢变量中有虚假时间的想法,尤其是如果您在其他地方重复使用此变量,则可能建议这样做。

    避免使用以下内容:(参见:Datetime Timezone conversion using pytz

    dt = dt.replace(tzinfo=timezone('Europe/Amsterdam'))  ## BAD !!
    
    为什么?因为.replace() 盲目替换 tzinfo,没有考虑目标时间,并将选择错误的 DST 对象。然而 .localize() 使用目标时间和你的 is_dst 提示来选择正确的 DST 对象。
    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)生效期间的某些时间点),情况并非如此。

5
天真的datetime对象应始终表示UTC时间。其他时区只应用于I/O(显示)。Python 3.3中有datetime.timestamp()方法。 - jfs
2
time.mktime() 应该仅用于本地时间。calendar.timegm() 可以用于将 UTC 时间元组转换为 POSIX 时间戳。或者更好的方法是仅使用 datetime 方法。请参见我的答案 - jfs
4
你的代码假设当前时间和历史纪元时间在本地时区具有相同的UTC偏移量,然而在全球116个时区(从430个常见时区中)中,并非如此。请注意修正。 - jfs
1
请勿在具有非固定UTC偏移量的时区(例如'Europe/Amsterdam')中使用.replace()。请参见使用pytz进行日期时间时区转换 - jfs
1
您IP地址为143.198.54.68,由于运营成本限制,当前对于免费用户的使用频率限制为每个IP每72小时10次对话,如需解除限制,请点击左下角设置图标按钮(手机用户先点击左上角菜单按钮)。 - jfs
显示剩余7条评论

38
另一种可能性是:
d = datetime.datetime.utcnow()
epoch = datetime.datetime(1970,1,1)
t = (d - epoch).total_seconds()

这段代码可以同时处理"d"和"epoch"两个变量,因为它们都是naive datetimes类型,所以"-"运算符是合法的,并返回一个时间间隔。通过调用total_seconds()方法将时间间隔转换成秒数。需要注意的是,total_seconds()方法返回一个浮点数,即使d.microsecond == 0也是如此。


13
好的,实际上,这个想法是相同的,但这个更容易理解 :) - Natim
3
你会认为在时间库中应该有一个单一的方法之类的东西……唉。 - wordsforthewise
1
要获取时间戳,请执行datetime.datetime.utcnow().timestamp()。 - Eugene
@Eugene datetime.datetime.utcnow().timestamp() 给出的结果与这个解决方案不同。 - alper
@alper,是的,它只会给你当前时间戳。 - Eugene

23

一种不使用外部模块的简单解决方案:

from datetime import datetime, timezone

dt = datetime(2008, 1, 1, 0, 0, 0, 0)
int(dt.replace(tzinfo=timezone.utc).timestamp())

1
谢谢!这是最好的答案! - valentinmk

21

还要注意这个函数 calendar.timegm(),正如这篇博客文章所描述的那样:

import calendar
calendar.timegm(utc_timetuple)

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


16
如果输入的 datetime 对象是 UTC 时间:
>>> dt = datetime(2008, 1, 1, 0, 0, 0, 0)
>>> timestamp = (dt - datetime(1970, 1, 1)).total_seconds()
1199145600.0

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

如果输入的日期对象是以协调世界时(UTC)表示的:

>>> 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时间戳


9

我认为主要答案仍然不够清晰,值得花时间理解时间时区

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

  • 2017-08-30 13:23:00:(原始日期时间),代表世界上某个地方的当地时间,但请注意,伦敦的2017-08-30 13:23:00与旧金山的2017-08-30 13:23:00 不是同一时间

由于同一时间字符串可能因为所处世界不同而被解释为不同时间点,因此需要一个绝对的时间概念。

UTC时间戳是从1970年1月1日00:00:00(在GMT时区+00:00偏移量下定义)开始的秒数或毫秒数。

Epoch以GMT时区为基准,因此是一个绝对时间点。 UTC时间戳作为绝对时间的偏移量因此定义了绝对时间点

这使得可以按时间顺序排列事件。

没有时区信息,时间是相对的,并且不能转换为绝对时间概念,除非提供有关原始日期时间应与之对应的时区的某些指示。

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

  • 原始日期时间:通常用于显示本地时间(即在浏览器中),操作系统可以向程序提供时区信息。

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

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

何时使用哪个?或者更确切地说,何时需要关心时区?

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

  • 要计算来自世界各地不同位置的事件之间的时间差异,UTC 时间戳就足够了,但是您失去了分析事件发生的时间(即对于 Web 分析,您可能想知道用户何时以本地时间访问您的站点:您是否看到更多的用户在早晨还是晚上?没有时间信息,您无法弄清楚。

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

另一个重要的问题是,日期字符串中的时区偏移量不是固定的。这意味着,因为 2017-08-30T10:55:24-0800 表示偏移量 -0800 或 8 小时,这并不意味着它将始终如此!

在夏季,它可能处于夏令时,那么它将是 -0700

那意味着时区偏移量(+0100)时区名称(欧洲/法国)甚至时区指定(CET)不同。 America/Los_Angeles时区是世界上的一个地方,但在冬季会变成PST(太平洋标准时间)时区偏移符号,在夏季则为PDT(太平洋夏令时)。因此,除了从日期字符串获取时区偏移量外,您还应该获取时区名称以提高准确性。大多数软件包都能自动将数字偏移量从夏令时转换为标准时间,但仅使用偏移量并不一定容易。例如,西非的WAT时区指定与法国的CET时区相同,均为UTC+0100,但法国遵循夏令时,而西非不遵循(因为他们靠近赤道)。因此,简而言之,这很复杂。非常复杂,这就是为什么您不应该自己处理,而是信任一个能够为您处理它的软件包,并保持其最新状态!

请阅读我的博客文章,了解Python日期和时间不同包的问题所在。 https://medium.com/@eleroy/10-things-you-need-to-know-about-date-and-time-in-python-with-datetime-pytz-dateutil-timedelta-309bfbafb3f7 - MrE

2

0

最简单的方法:

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

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

>>> 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具有相同的时区(或没有时区),时间差就是相同的。
>>> 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

该方法不返回UTC时间,而是将日期时间调整为当前时区。例如,如果现在是+3时区的凌晨12点,则它将返回纪元时间上的上午9点。 - Daniel Dror
啊,你说得对。我的时区是UTC-所以它才能工作。 - Mike Furlender
如果您要使用(Python 3.2及更高版本)的timezone.utc对象,则只需将其与.timestamp()一起使用:datetime(2008, 1, 1, tzinfo=timezone.utc).timestamp()。无需创建一个时代对象并进行减法运算。 - Martijn Pieters

0

我认为你提问的正确方式应该是:有没有一种方法可以通过指定UTC日期来获取时间戳?,因为时间戳只是一个绝对的数字,而不是相对的。相对的(或时区感知的)部分是日期。

我发现pandas非常方便处理时间戳,所以:

import pandas as pd
dt1 = datetime(2008, 1, 1, 0, 0, 0, 0)
ts1 = pd.Timestamp(dt1, tz='utc').timestamp()
# make sure you get back dt1
datetime.utcfromtimestamp(ts1)  

使用pandas是我个人认为的正确方法,同时也可以使用t = Timestamp.utcnow()直接获取正确的时间 :) - ntg
这很整洁,但为了读者的清晰度,我能否明确指定datetime导入,因为复制/粘贴此片段会失败。因此添加import datetime as dt,然后使用dt.datetime进行调用。 - yeliabsalohcin
还有一个非常烦人的问题是,.timestamp() 返回的 POSIX 时间戳是浮点数而不是整数。 - yeliabsalohcin
仅为时间戳推荐pandas似乎有些过度。 - Ted Brownlow

0

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

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

如果当前(dt)本地时区的UTC偏移量与1970年不同,则会失败。 mktime()期望本地时间。 - jfs

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