在Python中将datetime.date转换为UTC时间戳

381

我正在使用Python处理日期,需要将它们转换为UTC时间戳以在JavaScript中使用。以下代码无法正常工作:

>>> d = datetime.date(2011,01,01)
>>> datetime.datetime.utcfromtimestamp(time.mktime(d.timetuple()))
datetime.datetime(2010, 12, 31, 23, 0)

将日期对象首先转换为datetime也没有帮助。我尝试了这个链接上的示例,但:

from pytz import utc, timezone
from datetime import datetime
from time import mktime
input_date = datetime(year=2011, month=1, day=15)

现在要么:

mktime(utc.localize(input_date).utctimetuple())
或者
mktime(timezone('US/Eastern').localize(input_date).utctimetuple())

能够工作。

因此通用问题:如何将日期转换为距离UTC时代以秒为单位的时间?


可能是如何在Python中将本地时间转换为UTC?的重复问题。 - Piotr Dobrogost
37
我不确定我是否同意将其标记为重复。虽然解决方案相似,但问题并不相同。一个(这个)试图从datetime.date创建时间戳,另一个试图将一个时区的字符串表示转换为另一个时区。作为寻找此问题解决方案的人,我可能无法得出结论,认为后者会提供我正在寻找的答案。 - DRH
6
datetime(date.year,date.month,date.day).timestamp() - Julian
4
可能是在Python中,如何将datetime对象转换为秒?的重复问题。 - Trevor Boyd Smith
注意:mktime比其他方法慢10倍。由于你很可能在热路径中执行此操作,所以这很重要。 - Remko
12个回答

545
如果d = date(2011, 1, 1)是在UTC时区:
>>> from datetime import datetime, date
>>> import calendar
>>> timestamp1 = calendar.timegm(d.timetuple())
>>> datetime.utcfromtimestamp(timestamp1)
datetime.datetime(2011, 1, 1, 0, 0)

如果d处于本地时区:

>>> import time
>>> timestamp2 = time.mktime(d.timetuple()) # DO NOT USE IT WITH UTC DATE
>>> datetime.fromtimestamp(timestamp2)
datetime.datetime(2011, 1, 1, 0, 0)
timestamp1timestamp2在当地时区午夜和UTC的午夜不同时可能会有所不同。
如果d对应于一个模糊的本地时间(例如在DST转换期间),或者如果d是过去(未来)的日期并且UTC偏移量可能已经不同,并且C mktime()不能访问给定平台上的tz数据库,则mktime()可能返回错误的结果。您可以使用pytz模块(例如通过tzlocal.get_localzone())在所有平台上获得访问tz数据库。此外,如果使用“right”时区,则utcfromtimestamp()可能失败,mktime()可能返回非POSIX时间戳
要将表示UTC日期的datetime.date对象转换为UNIX时间戳而不使用calendar.timegm()
DAY = 24*60*60 # POSIX day in seconds (exact value)
timestamp = (utc_date.toordinal() - date(1970, 1, 1).toordinal()) * DAY
timestamp = (utc_date - date(1970, 1, 1)).days * DAY

如何将日期转换为自UTC纪元以来的秒数?

要将已经表示为UTC时间的datetime.datetime(而不是datetime.date)对象转换为相应的POSIX时间戳(一个float),请使用以下方法:

Python 3.3+

datetime.timestamp()

from datetime import timezone

timestamp = dt.replace(tzinfo=timezone.utc).timestamp()

注意:必须明确提供timezone.utc,否则.timestamp()会认为您的本地日期时间对象位于本地时区。

Python 3(<3.3)

来自 datetime.utcfromtimestamp() 文档:

没有从datetime实例获取时间戳的方法, 但是可以轻松地计算对应于datetime实例dt的POSIX时间戳。对于一个naive的dt:

timestamp = (dt - datetime(1970, 1, 1)) / timedelta(seconds=1)

对于已知的数据表(dt):

timestamp = (dt - datetime(1970,1,1, tzinfo=timezone.utc)) / timedelta(seconds=1)

有趣的阅读: Epoch time vs. time of day,讲解了“现在是几点?”和“已经过了多少秒?”之间的区别。

另请参阅:datetime needs an "epoch" method

Python 2

要将上述代码适应于Python 2:

timestamp = (dt - datetime(1970, 1, 1)).total_seconds()

其中timedelta.total_seconds()等同于使用真除法计算(td.microseconds + (td.seconds + td.days * 24 * 3600) * 10**6) / 10**6

示例

from __future__ import division
from datetime import datetime, timedelta

def totimestamp(dt, epoch=datetime(1970,1,1)):
    td = dt - epoch
    # return td.total_seconds()
    return (td.microseconds + (td.seconds + td.days * 86400) * 10**6) / 10**6 

now = datetime.utcnow()
print now
print totimestamp(now)

当心浮点数问题

输出

2012-01-08 15:34:10.022403
1326036850.02

如何将一个有时区信息的datetime对象转换为POSIX时间戳

assert dt.tzinfo is not None and dt.utcoffset() is not None
timestamp = dt.timestamp() # Python 3.3+

Python 3 中:

from datetime import datetime, timedelta, timezone

epoch = datetime(1970, 1, 1, tzinfo=timezone.utc)
timestamp = (dt - epoch) / timedelta(seconds=1)
integer_timestamp = (dt - epoch) // timedelta(seconds=1)

在Python 2中:

# utc time = local time              - utc offset
utc_naive  = dt.replace(tzinfo=None) - dt.utcoffset()
timestamp = (utc_naive - datetime(1970, 1, 1)).total_seconds()

2
对于 Python 2 的情况,为什么不使用:timestamp = (dt - datetime.fromtimestamp(0)).total_seconds() - m01
7
@m01说:datetime(1970, 1, 1)更为明确。在这里使用fromtimestamp()是错误的(因为dt是以UTC时间表示的,所以应该使用utcfromtimestamp())。 - jfs
22
尽管我非常喜欢Python,但是它的日期时间库确实存在严重问题。 - Realfun
4
时区、夏令时转换、tz数据库和闰秒存在于独立于Python之外的领域。 - jfs
1
@maxkoryukov 我不明白为什么datetime(2011, 1, 1, tzinfo=timezone.utc).timestamp()arrow.get(2011, 1, 1).timestamp的答案更差(顺便说一下,你可能已经看到了我在那里发表的2016年的评论)。您无需使用第三方模块即可使用utc时区。通常情况下,如果我需要简单一致的日期时间API和可预测的结果,我更喜欢pendulum。请参见为什么不使用Arrow? - jfs
显示剩余31条评论

112

仅适用于Unix系统

>>> import datetime
>>> d = datetime.date(2011, 1, 1)
>>> d.strftime("%s")  # <-- THIS IS THE CODE YOU WANT
'1293832800'

注1:dizzyf指出这适用于本地化时区。不要在生产中使用。

注2:Jakub Narębski指出,即使针对具有偏移感知日期时间的情况(已测试Python 2.7),此代码仍将忽略时区信息


4
如果您知道您的代码将在哪个系统下运行,这是一个很好的答案,但需要注意的是,'%s'格式字符串并不适用于所有操作系统,因此该代码不具备可移植性。如果您想检查它是否被支持,可以在类Unix系统上检查'man strftime'。 - Alice Heaton
5
应该提到的是,这会丢弃小数秒部分(微秒或其他)。 - Alfe
12
警告:这适用于本地化的时区!对于相同的datetime对象,由于机器时间的不同,您将获得不同的strftime行为。 - dizzyf
3
此外,这会忽略时区信息,并假设日期时间在本地时区,即使对于具有设置了tzinfo的偏移感知日期时间也是如此。至少在Python 2.7中是这样。 - Jakub Narębski
1
d = datetime.date(2011,1,1) 避免出现“SyntaxError: invalid token”错误。 - Hari
显示剩余4条评论

44
  • 假设1: 您正在尝试将日期转换为时间戳,但由于日期涵盖了24小时,因此没有单个时间戳代表该日期。我假设您想要表示该日期的时间戳为午夜(00:00:00.000)。

  • 假设2: 您提供的日期未与特定时区相关联,但您希望确定相对于特定时区(UTC)的偏移量。如果不知道日期所在的时区,则无法计算特定时区的时间戳。我假设您希望将日期视为本地系统时区的日期。

首先,您可以使用timetuple()成员将日期实例转换为表示各个时间组件的元组:

dtt = d.timetuple() # time.struct_time(tm_year=2011, tm_mon=1, tm_mday=1, tm_hour=0, tm_min=0, tm_sec=0, tm_wday=5, tm_yday=1, tm_isdst=-1)
你可以使用 time.mktime 将其转换为时间戳:
ts = time.mktime(dtt) # 1293868800.0
您可以通过使用纪元时间(1970-01-01)对此方法进行测试,从而验证此方法,这种情况下,该函数应返回该日期本地时区的时区偏移量。
d = datetime.date(1970,1,1)
dtt = d.timetuple() # time.struct_time(tm_year=1970, tm_mon=1, tm_mday=1, tm_hour=0, tm_min=0, tm_sec=0, tm_wday=3, tm_yday=1, tm_isdst=-1)
ts = time.mktime(dtt) # 28800.0

28800.0代表8小时,这在太平洋时区(我所在的地方)是正确的。


我不确定为什么这个被踩了。它解决了 OP 的问题(OP 标记为“不工作”的工作代码)。它没有回答我的问题(将 UTC datetime.datetime 转换为时间戳),但还是... 点赞。 - Thanatos
9
注意,假设“您想将日期视为 本地系统 时区”的前提是Python中所有时区问题的根源。除非您确信脚本运行的机器的本地化,并特别是如果为来自世界各地的客户提供服务,则始终假定日期为UTC,并尽早进行转换。相信我,这样做会让你头发不乱。 - Romain G

10

我定义了自己的两个函数

  • utc_time2datetime(utc_time, tz=None)
  • datetime2utc_time(datetime)

这里:

import time
import datetime
from pytz import timezone
import calendar
import pytz


def utc_time2datetime(utc_time, tz=None):
    # convert utc time to utc datetime
    utc_datetime = datetime.datetime.fromtimestamp(utc_time)

    # add time zone to utc datetime
    if tz is None:
        tz_datetime = utc_datetime.astimezone(timezone('utc'))
    else:
        tz_datetime = utc_datetime.astimezone(tz)

    return tz_datetime


def datetime2utc_time(datetime):
    # add utc time zone if no time zone is set
    if datetime.tzinfo is None:
        datetime = datetime.replace(tzinfo=timezone('utc'))

    # convert to utc time zone from whatever time zone the datetime is set to
    utc_datetime = datetime.astimezone(timezone('utc')).replace(tzinfo=None)

    # create a time tuple from datetime
    utc_timetuple = utc_datetime.timetuple()

    # create a time element from the tuple an add microseconds
    utc_time = calendar.timegm(utc_timetuple) + datetime.microsecond / 1E6

    return utc_time

1
我直接忽略了你的例子... 对于这么简单的事情来说,太混乱和复杂了... 根本不符合Pythonic的风格。 - Angry 84
@Mayhem:我稍微整理了一下代码并添加了注释。如果你有更好、更通用的解决方案,可以将时间转换为日期时间并返回时区,请告诉我们。同时也请告诉我,我的代码应该如何更符合Pythonic风格。 - agittarius
datetime 不是 datetime2utc_time(datetime) 函数参数的好选择,因为它会与 datetime 导入模块产生命名冲突。 - MathKid

8

按照Python2.7文档,您需要使用calendar.timegm()而不是time.mktime()。

>>> d = datetime.date(2011,01,01)
>>> datetime.datetime.utcfromtimestamp(calendar.timegm(d.timetuple()))
datetime.datetime(2011, 1, 1, 0, 0)

3
如果 d 是以协调世界时(UTC)表示的,它与 我的答案 有何不同? - jfs

6

完整的时间字符串包含:

  • 日期
  • 时间
  • UTC偏移量 [+HHMM或- HHMM]

例如:

1970-01-01 06:00:00 +0500 == 1970-01-01 01:00:00 +0000 == UNIX时间戳:3600

$ python3
>>> from datetime import datetime
>>> from calendar import timegm
>>> tm = '1970-01-01 06:00:00 +0500'
>>> fmt = '%Y-%m-%d %H:%M:%S %z'
>>> timegm(datetime.strptime(tm, fmt).utctimetuple())
3600

注意:

UNIX时间戳是一个浮点数,表示自纪元以来的秒数,在协调世界时(UTC)中。


编辑:

$ python3
>>> from datetime import datetime, timezone, timedelta
>>> from calendar import timegm
>>> dt = datetime(1970, 1, 1, 6, 0)
>>> tz = timezone(timedelta(hours=5))
>>> timegm(dt.replace(tzinfo=tz).utctimetuple())
3600

1
@user908088 按照你的要求,将 1970-01-01 06:00:00 +0500 转换为 3600 - kev
1
@user908088,在Python2中没有timezone,你需要手动计算(06:00:00 - +0500 = 01:00:00)。 - kev

3

假设您有一个名为ddatetime对象,请使用以下方法获取UTC时间戳:

d.strftime("%Y-%m-%dT%H:%M:%S.%fZ")

对于相反的方向,请使用以下内容:

d = datetime.strptime("2008-09-03T20:56:35.450686Z", "%Y-%m-%dT%H:%M:%S.%fZ")

3
使用arrow包:
>>> import arrow
>>> arrow.get(2010, 12, 31).timestamp
1293753600
>>> time.gmtime(1293753600)
time.struct_time(tm_year=2010, tm_mon=12, tm_mday=31, 
    tm_hour=0, tm_min=0, tm_sec=0, 
    tm_wday=4, tm_yday=365, tm_isdst=0)

注意:arrow使用dateutil,而dateutil已知存在多年的与时区处理相关的错误漏洞。如果您需要正确处理非固定UTC偏移量的时区,请不要使用它(对于UTC时区使用它是可以的,但标准库也可以处理UTC情况)。 - jfs
@J.F.Sebastian,该错误仅在过渡期间发生,在一个模糊的时间内。 - Paul
看起来这个 bug 在三月二十八日已经被修复了。 - Mathieu Longtin
看起来这是最好的跨Python解决方案。只需使用arrow。谢谢;) - maxkoryukov
@MathieuLongtin,dateutil可能存在其他问题 - jfs
说到底,这就是垃圾进垃圾出。这个问题存在很多模糊之处。它完全取决于记录的地点。所以,这个答案和提出的问题一样正确。一个更好的答案可能会提到,任何时区之间的转换都必须基于一个知道其所在时区的日期。上述答案没有考虑到这一点。 - Marc

2

这个问题有点混乱。时间戳不是UTC时间 - 它们是Unix的一部分。日期可能是UTC时间?假设是,如果您使用Python 3.2+,simple-date可以轻松解决此问题:

>>> SimpleDate(date(2011,1,1), tz='utc').timestamp
1293840000.0

如果你已经有了年、月和日,你就不需要创建date对象:

>>> SimpleDate(2011,1,1, tz='utc').timestamp
1293840000.0

如果日期是在其他时区(这很重要,因为我们假设是午夜而没有关联的时间):

>>> SimpleDate(date(2011,1,1), tz='America/New_York').timestamp
1293858000.0

简单日期的理念是将Python中的所有日期和时间内容收集到一个一致的类中,以便您可以进行任何转换。因此,例如,它也可以反过来做:
>>> SimpleDate(1293858000, tz='utc').date
datetime.date(2011, 1, 1)

]


SimpleDate(date(2011,1,1), tz='America/New_York') 抛出 NoTimezone 异常,而 pytz.timezone('America/New_York') 不会抛出任何异常 (simple-date==0.4.8, pytz==2014.7). - jfs
我在运行pip install simple-date时出现错误,看起来这只适用于python3/pip3。 - NoBugs

1
这对我来说可行,通过一个函数传递。
from datetime import timezone, datetime, timedelta 
import datetime


def utc_converter(dt):
    dt = datetime.datetime.now(timezone.utc)
    utc_time = dt.replace(tzinfo=timezone.utc)
    utc_timestamp = utc_time.timestamp()
    return utc_timestamp



# create start and end timestamps
_now = datetime.datetime.now()
str_start = str(utc_converter(_now))
_end = _now + timedelta(seconds=10)
str_end = str(utc_converter(_end))

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