将UTC日期时间字符串转换为本地日期时间

314

我以前从来没有把时间转换成UTC格式,但最近有一个需求要求我的应用程序能够自动适应时区,这让我一直困扰着。有很多关于将本地时间转换为UTC时间的信息,我觉得这些信息已经比较基础了(也许我还做错了),但是我无法找到任何关于如何轻松将UTC时间转换为最终用户所在的时区的信息。

简而言之,一个Android应用程序向我(App Engine应用程序)发送数据,并包含一个时间戳。为了将该时间戳存储为UTC时间,我正在使用以下方法:

datetime.utcfromtimestamp(timestamp)

好像这个方法行得通。当我的应用程序存储数据时,它以比我所在的 EST -5 早五个小时(即UTC+0)的时间保存。

数据存储在AppEngine的BigTable上,在检索时会以字符串的形式返回,如下所示:

"2011-01-21 02:37:21"

如何将此字符串转换为用户正确的时区的DateTime?

另外,什么是存储用户时区信息的推荐方式?(通常如何存储tz信息,例如:“-5:00”或“EST”等等?)我相信对第一个问题的回答可能会包含一个参数来回答第二个问题。


2
如何使用Python标准库将Python UTC日期时间转换为本地日期时间?(参考链接:https://dev59.com/r2455IYBdhLWcg3wD_yZ#13287083) - jfs
这个答案展示了如何以简单的方式解决这个问题。 - juan
17个回答

579

如果您不想提供自己的tzinfo对象,请查看python-dateutil库。它基于zoneinfo(Olson)数据库提供了tzinfo实现,因此您可以按照某种规范名称引用时区规则。

from datetime import datetime
from dateutil import tz

# METHOD 1: Hardcode zones:
from_zone = tz.gettz('UTC')
to_zone = tz.gettz('America/New_York')

# METHOD 2: Auto-detect zones:
from_zone = tz.tzutc()
to_zone = tz.tzlocal()

# utc = datetime.utcnow()
utc = datetime.strptime('2011-01-21 02:37:21', '%Y-%m-%d %H:%M:%S')

# Tell the datetime object that it's in UTC time zone since 
# datetime objects are 'naive' by default
utc = utc.replace(tzinfo=from_zone)

# Convert time zone
central = utc.astimezone(to_zone)

编辑:扩展了示例以展示strptime的用法。

编辑2:更正了API使用,以展示更好的入口点方法。

编辑3:包括时区自动检测方法(Yarin)。


1
在我的上一条评论中,我能够导入 dateutil.tz 并使用 tz.tzutc()tz.tzlocal() 作为我正在寻找的时区对象。看起来我的系统上的时区数据库很好(我在 /usr/share/zoneinfo 中进行了检查)。不确定出了什么问题。 - Ben Kreeger
2
@MattoTodd: dateutil中的utc -> local转换存在问题,请使用pytz代替。 - jfs
2
@J.F.Sebastian 这个问题已经在#225中得到修复。 - tim-phillips
2
@briankip 这取决于你的应用程序需要多么复杂,但一般来说,这是一个问题。首先,根据一年中的时间不同,添加/减去的小时数可能会发生变化,因为有些时区遵守夏令时,而其他时区则不遵守。其次,时区规则随着时间的推移会任意更改,所以如果你正在处理过去的日期/时间,你需要应用当时有效的规则,而不是现在的规则。这就是为什么最好使用一个在幕后利用Olson TZ数据库的API。 - Joe Holloway
1
通常情况下,如果需要在不同时区之间进行转换(或仅在单一时区中进行操作),则会将UTC时间戳存储。在某些情况下,您可能还需要知道数据捕获时所处的时区,因此可以将时区或本地化时间与其一起存储,但使用UTC时间戳可确保您拥有一个“时间点”,该点相对于位置不会产生歧义。 - Joe Holloway
显示剩余20条评论

77

这里有一种弹性的方法,不依赖于任何外部库:

from datetime import datetime
import time

def datetime_from_utc_to_local(utc_datetime):
    now_timestamp = time.time()
    offset = datetime.fromtimestamp(now_timestamp) - datetime.utcfromtimestamp(now_timestamp)
    return utc_datetime + offset

这可以避免DelboyJay示例中的时间问题,以及Erik van Oosten修改中较小的时间问题。

有趣的是,上面计算的时区偏移量可能会与下面看似等价的表达式不同,这可能是由于夏令时规则的更改造成的:

offset = datetime.fromtimestamp(0) - datetime.utcfromtimestamp(0) # NO!

更新:这段代码的缺陷在于使用了当前时间的UTC偏移量,可能与输入日期时间的UTC偏移量不同。请参见此答案的评论以获取另一种解决方案。

为了解决不同时间的问题,请从传递的时间中获取时代时间。这是我所做的:

def utc2local(utc):
    epoch = time.mktime(utc.timetuple())
    offset = datetime.fromtimestamp(epoch) - datetime.utcfromtimestamp(epoch)
    return utc + offset

2
这个很好用,只需要时间和日期。 - Mike_K
13
@Mike_K: 但是这是不正确的。它不支持夏令时或者由于其他原因过去有不同UTC偏移量的时区。完全不使用类似pytz的数据库提供全面支持是不可能的,参见PEP-431。虽然你可以编写仅基于标准库的解决方案,在那些已经拥有历史时区数据库的系统上可以运行,例如Linux、OS X,请参见我的回答 - jfs
@J.F.Sebastian:你说这段代码一般情况下不起作用,但又说它在OS X和Linux上能运行。你是不是意味着这段代码在Windows上不能工作? - David Foster
3
@DavidFoster:你的代码可能也会在Linux和OS X上出现问题。你的代码和我的仅使用标准库的解决方案之间的区别在于,你的代码使用当前偏移量,这可能与utc_datetime时间的UTC偏移量不同。 - jfs
2
好的,很敏感的区别。UTC偏移量也可能随时间变化。叹气 - David Foster
显示剩余2条评论

38
请参见datetime文档中关于tzinfo对象的介绍。您需要自己实现所需支持的时区。文档底部有示例。
以下是一个简单的示例:
from datetime import datetime,tzinfo,timedelta

class Zone(tzinfo):
    def __init__(self,offset,isdst,name):
        self.offset = offset
        self.isdst = isdst
        self.name = name
    def utcoffset(self, dt):
        return timedelta(hours=self.offset) + self.dst(dt)
    def dst(self, dt):
            return timedelta(hours=1) if self.isdst else timedelta(0)
    def tzname(self,dt):
         return self.name

GMT = Zone(0,False,'GMT')
EST = Zone(-5,False,'EST')

print datetime.utcnow().strftime('%m/%d/%Y %H:%M:%S %Z')
print datetime.now(GMT).strftime('%m/%d/%Y %H:%M:%S %Z')
print datetime.now(EST).strftime('%m/%d/%Y %H:%M:%S %Z')

t = datetime.strptime('2011-01-21 02:37:21','%Y-%m-%d %H:%M:%S')
t = t.replace(tzinfo=GMT)
print t
print t.astimezone(EST)

输出

01/22/2011 21:52:09 
01/22/2011 21:52:09 GMT
01/22/2011 16:52:09 EST
2011-01-21 02:37:21+00:00
2011-01-20 21:37:21-05:00a

感谢接受!我稍微更新了一下,以翻译您特定的时间示例。解析时间会创建文档所称的“naive”时间。使用replace将时区设置为GMT并使其成为“timezone aware”,然后使用astimezone转换为另一个时区。 - Mark Tolonen
1
很抱歉因为Joe的答案而更换了您的答案。从技术上讲,您的答案确切地解释了如何使用原始Python来完成它(这是很好知道的),但他建议的库可以更快地让我得到一个解决方案。 - MattoTodd
6
似乎它不会自动处理夏令时。 - Gringo Suave
@GringoSuave:它本来就不是为此而设计的。这方面的规则非常复杂且经常变化。 - Mark Tolonen

28

如果您想获取即使对于对应于模糊本地时间(例如,在夏令时转换期间)和/或本地UTC偏移在本地时区的不同时间不同,也能得到正确结果,那么请使用 pytz 时区:

#!/usr/bin/env python
from datetime import datetime
import pytz    # $ pip install pytz
import tzlocal # $ pip install tzlocal

local_timezone = tzlocal.get_localzone() # get pytz tzinfo
utc_time = datetime.strptime("2011-01-21 02:37:21", "%Y-%m-%d %H:%M:%S")
local_time = utc_time.replace(tzinfo=pytz.utc).astimezone(local_timezone)

23

如果您不想使用除datetime之外的任何其他模块,那么这个答案应该会有所帮助。

datetime.utcfromtimestamp(timestamp)返回一个“naive”(非时区感知)的datetime对象。时区感知和非时区感知的对象是不同的,前者可以处理时区信息,而后者不行。如果你要在多个时区之间进行转换(例如在UTC时间和本地时间之间),则需要使用时区感知对象。

如果您不能自己实例化日期时间对象,但仍可以创建一个用于表示UTC时间的“naive” datetime对象,则可以尝试使用以下Python 3.x代码进行转换:

import datetime

d=datetime.datetime.strptime("2011-01-21 02:37:21", "%Y-%m-%d %H:%M:%S") #Get your naive datetime object
d=d.replace(tzinfo=datetime.timezone.utc) #Convert it to an aware datetime object in UTC time.
d=d.astimezone() #Convert it to your local timezone (still aware)
print(d.strftime("%d %b %Y (%I:%M:%S:%f %p) %Z")) #Print it with a directive of choice

要小心不要错误地认为,如果您的时区当前为MDT,夏令时与上述代码不兼容,因为它打印MST。请注意,如果您将月份更改为8月,它将打印MDT。

另一种获取带有时区意识的datetime对象的简单方法(在Python 3.x中)是最初指定具有时区的对象。以下是一个示例,使用UTC:

import datetime, sys

aware_utc_dt_obj=datetime.datetime.now(datetime.timezone.utc) #create an aware datetime object
dt_obj_local=aware_utc_dt_obj.astimezone() #convert it to local time

#The following section is just code for a directive I made that I liked.
if sys.platform=="win32":
    directive="%#d %b %Y (%#I:%M:%S:%f %p) %Z"
else:
    directive="%-d %b %Y (%-I:%M:%S:%f %p) %Z"

print(dt_obj_local.strftime(directive))

如果您使用的是Python 2.x版本,您可能需要创建datetime.tzinfo的子类,并使用它来帮助您创建一个"aware"的datetime对象,因为在Python 2.x中不存在datetime.timezone


12
如果使用Django,则可以使用 timezone.localtime 方法:
from django.utils import timezone
date 
# datetime.datetime(2014, 8, 1, 20, 15, 0, 513000, tzinfo=<UTC>)

timezone.localtime(date)
# datetime.datetime(2014, 8, 1, 16, 15, 0, 513000, tzinfo=<DstTzInfo 'America/New_York' EDT-1 day, 20:00:00 DST>)

7
以下内容对我在美国西部的云环境中适用:
import datetime
import pytz

#set the timezone
tzInfo = pytz.timezone('America/Los_Angeles')
dt = datetime.datetime.now(tz=tzInfo)
print(dt)

谢谢,这对我来说是最好的解决方案。然而有一个小错别字:dt = datetime.datetime.now(tz=tzInfo)应该改为dt = datetime.now(tz=tzInfo) - Jeremy Morgan

5
你可以使用 Arrow
from datetime import datetime
import arrow

now = datetime.utcnow()

print(arrow.get(now).to('local').format())
# '2018-04-04 15:59:24+02:00'

你可以使用任何东西来调用arrow.get()。时间戳,ISO字符串等


将时间戳转换为易读的格式:from datetime import datetime;
dt_obj = datetime.fromtimestamp(1652139647); print(arrow.get(dt_obj).to('local').format()) # 我们可以使用自己设定的时区,例如:"Asia/Tehran"
- ali reza

5

简短而简单:

from datetime import datetime

t = "2011-01-21 02:37:21"
datetime.fromisoformat(t) + (datetime.now() - datetime.utcnow())

(datetime.now() - datetime.utcnow()) --> datetime.timedelta(seconds=7199, microseconds=999995) 最好将datetime.now()保存在一个变量中,以避免微小的偏移 :) - undefined

5

将来自franksands的答案整合成一个方便的方法。

import calendar
import datetime

def to_local_datetime(utc_dt):
    """
    convert from utc datetime to a locally aware datetime according to the host timezone

    :param utc_dt: utc datetime
    :return: local timezone datetime
    """
    return datetime.datetime.fromtimestamp(calendar.timegm(utc_dt.timetuple()))

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