如何仅使用标准库将UTC时间转为本地时间?
我有一个用 datetime.utcnow()
创建的 Python datetime
实例,并且这个实例已经存储在数据库里。
现在我想把从数据库里取出的 datetime
实例转换成本地时间,也就是像用 datetime.now()
创建的那种时间。
我该怎么用 Python 自带的库(也就是不使用 pytz
这个库)把 UTC 时间转换成本地时间呢?
看起来一个解决办法是使用 datetime.astimezone(tz)
,但是我该怎么获取默认的本地时区呢?
14 个回答
95
从Python 3.9开始,你可以使用zoneinfo
模块。
首先,我们用utcnow()
来获取当前的时间:
>>> from datetime import datetime
>>> database_time = datetime.utcnow()
>>> database_time
datetime.datetime(2021, 9, 24, 4, 18, 27, 706532)
接下来,创建时区:
>>> from zoneinfo import ZoneInfo
>>> utc = ZoneInfo('UTC')
>>> localtz = ZoneInfo('localtime')
然后进行时间转换。要在不同的时区之间转换,datetime对象必须知道自己当前处于哪个时区,然后我们只需使用astimezone()
来完成转换:
>>> utctime = database_time.replace(tzinfo=utc)
>>> localtime = utctime.astimezone(localtz)
>>> localtime
datetime.datetime(2021, 9, 24, 6, 18, 27, 706532, tzinfo=zoneinfo.ZoneInfo(key='localtime'))
如果你使用的是Python 3.6到3.8版本,你需要使用backports.zoneinfo模块:
>>> try:
>>> from zoneinfo import ZoneInfo
>>> except ImportError:
>>> from backports.zoneinfo import ZoneInfo
其他的操作是一样的。
对于更早的版本,你需要使用pytz
或者dateutil
。dateutil的工作方式和zoneinfo类似:
>>> from dateutil import tz
>>> utc = tz.gettz('UTC')
>>> localtz = tz.tzlocal()
The Conversion:
>>> utctime = now.replace(tzinfo=UTC)
>>> localtime = utctime.astimezone(localtz)
>>> localtime
datetime.datetime(2010, 12, 30, 15, 51, 22, 114668, tzinfo=tzlocal())
pytz
的接口有所不同,这是因为Python的时区处理不支持模糊时间:
>>> import pytz
>>> utc = pytz.timezone('UTC')
# There is no local timezone support, you need to know your timezone
>>> localtz = pytz.timezone('Europe/Paris')
>>> utctime = utc.localize(database_time)
>>> localtime = localtz.normalize(utctime.astimezone(localtz))
>>> localtime
447
在 Python 3.3 及以上版本中:
from datetime import datetime, timezone
def utc_to_local(utc_dt):
return utc_dt.replace(tzinfo=timezone.utc).astimezone(tz=None)
在 Python 2 和 3 中:
import calendar
from datetime import datetime, timedelta
def utc_to_local(utc_dt):
# get integer timestamp to avoid precision lost
timestamp = calendar.timegm(utc_dt.timetuple())
local_dt = datetime.fromtimestamp(timestamp)
assert utc_dt.resolution >= timedelta(microseconds=1)
return local_dt.replace(microsecond=utc_dt.microsecond)
使用 pytz
(适用于 Python 2 和 3):
import pytz
local_tz = pytz.timezone('Europe/Moscow') # use your local timezone name here
# NOTE: pytz.reference.LocalTimezone() would produce wrong result here
## You could use `tzlocal` module to get local timezone on Unix and Win32
# from tzlocal import get_localzone # $ pip install tzlocal
# # get local timezone
# local_tz = get_localzone()
def utc_to_local(utc_dt):
local_dt = utc_dt.replace(tzinfo=pytz.utc).astimezone(local_tz)
return local_tz.normalize(local_dt) # .normalize might be unnecessary
示例
def aslocaltimestr(utc_dt):
return utc_to_local(utc_dt).strftime('%Y-%m-%d %H:%M:%S.%f %Z%z')
print(aslocaltimestr(datetime(2010, 6, 6, 17, 29, 7, 730000)))
print(aslocaltimestr(datetime(2010, 12, 6, 17, 29, 7, 730000)))
print(aslocaltimestr(datetime.utcnow()))
输出
Python 3.32010-06-06 21:29:07.730000 MSD+0400
2010-12-06 20:29:07.730000 MSK+0300
2012-11-08 14:19:50.093745 MSK+0400
Python 2
2010-06-06 21:29:07.730000
2010-12-06 20:29:07.730000
2012-11-08 14:19:50.093911
pytz
2010-06-06 21:29:07.730000 MSD+0400
2010-12-06 20:29:07.730000 MSK+0300
2012-11-08 14:19:50.146917 MSK+0400
注意:这个方法考虑了夏令时(DST)和莫斯科时区最近的 UTC 偏移变化。
我不确定非 pytz 的解决方案在 Windows 上是否有效。
6
我想我明白了:它计算自1970年1月1日以来的秒数,然后使用time.localtime将其转换为本地时区的时间,最后再把这个时间结构转换回日期时间...
EPOCH_DATETIME = datetime.datetime(1970,1,1)
SECONDS_PER_DAY = 24*60*60
def utc_to_local_datetime( utc_datetime ):
delta = utc_datetime - EPOCH_DATETIME
utc_epoch = SECONDS_PER_DAY * delta.days + delta.seconds
time_struct = time.localtime( utc_epoch )
dt_args = time_struct[:6] + (delta.microseconds,)
return datetime.datetime( *dt_args )
它正确处理了夏令时和冬令时:
>>> utc_to_local_datetime( datetime.datetime(2010, 6, 6, 17, 29, 7, 730000) )
datetime.datetime(2010, 6, 6, 19, 29, 7, 730000)
>>> utc_to_local_datetime( datetime.datetime(2010, 12, 6, 17, 29, 7, 730000) )
datetime.datetime(2010, 12, 6, 18, 29, 7, 730000)