如何仅使用标准库将 UTC 日期时间转换为本地日期时间?
Posted
技术标签:
【中文标题】如何仅使用标准库将 UTC 日期时间转换为本地日期时间?【英文标题】:How to convert a UTC datetime to a local datetime using only standard library? 【发布时间】:2011-06-01 14:04:55 【问题描述】:我有一个使用datetime.utcnow()
创建并保存在数据库中的python datetime
实例。
为了显示,我想使用默认本地时区将从数据库检索到的datetime
实例转换为本地datetime
(即,就好像datetime
是使用datetime.now()
创建的一样)。
如何仅使用 python 标准库(例如,没有 pytz
依赖项)将 UTC datetime
转换为本地 datetime
?
似乎一种解决方案是使用datetime.astimezone(tz)
,但您将如何获得默认的本地时区?
【问题讨论】:
时间以哪种格式保存到数据库中?如果是标准格式,可能不需要进行任何转换。 这个answer 展示了使用pytz 的简单方法。 无法通过pytz
找到答案。感觉很傻。
【参考方案1】:
在 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()))
输出
蟒蛇 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
蟒蛇2
2010-06-06 21:29:07.730000
2010-12-06 20:29:07.730000
2012-11-08 14:19:50.093911
皮茨
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 和 MSK 时区最近更改的 UTC 偏移量。
我不知道非 pytz 解决方案是否适用于 Windows。
【讨论】:
“标准化”是做什么的? @avi:一般来说:pytz: Why is normalize needed when converting between timezones?。注意:.normalize()
的当前实现不需要,因为.astimezone()
的源时区是 UTC。
我在尝试 pytz 解决方案时收到 TypeError: replace() takes no keyword arguments
。
@Craig 代码按原样工作,如答案中的输出所示。创建导致 TypeError 的完整最小代码示例,提及您使用的 Python、pytz 版本并将其作为单独的 Stack Overflow 问题发布。
Python 3.3+ 解决方案的快速提示。要走相反的方向(原生于 UTC),这可行:local_dt.replace(tzinfo=None).astimezone(tz=timezone.utc)
【参考方案2】:
从 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')
然后转换。要在时区之间进行转换,日期时间必须知道它所在的时区,然后我们只需使用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
。 datutil 的工作方式类似于 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
【讨论】:
谢谢,如果我需要完整的解决方案,我会把它放在手边。 dateutil 是否支持 Python 3.1(说是 Python 2.3+,但很可疑)? dateutil might fail with the .astimezone() code pytz 确实可以更好地处理 DST 转换。 更新:pytz 和 dateutil 现在都支持 Python 3。 @J.F.Sebastian:dateutil 无法区分 DST 转换期间发生的两个 1:30 时间。如果你需要能够区分这两次,变通方法是使用pytz,它可以处理它。【参考方案3】:Python 3.9 添加了zoneinfo
模块,所以现在可以按如下方式完成(仅限stdlib):
from zoneinfo import ZoneInfo
from datetime import datetime
utc_unaware = datetime(2020, 10, 31, 12) # loaded from database
utc_aware = utc_unaware.replace(tzinfo=ZoneInfo('UTC')) # make aware
local_aware = utc_aware.astimezone(ZoneInfo('localtime')) # convert
中欧比 UTC 早 1 或 2 小时,所以local_aware
是:
datetime.datetime(2020, 10, 31, 13, 0, tzinfo=backports.zoneinfo.ZoneInfo(key='localtime'))
作为str
:
2020-10-31 13:00:00+01:00
Windowshas no系统时区数据库,所以这里需要一个额外的包:
pip install tzdata
有一个允许在 Python 3.6 到 3.8 中使用的向后移植:
sudo pip install backports.zoneinfo
然后:
from backports.zoneinfo import ZoneInfo
【讨论】:
这里甚至不需要zoneinfo
- 请参阅jfs' answer 的第一部分(Python3.3+ 部分);-)
@MrFuppes 我认为ZoneInfo('localtime')
比tz=None
清晰得多(毕竟,人们可能期望tz=None
删除时区或返回UTC 而不是本地时间)。
@MrFuppes Plus 问题可能涉及 OP 拥有一个网站并希望在本地时间向用户显示时间戳。为此,您必须转换为用户的明确时区而不是本地时间。
您在 Win10 上的注释可能是正确的,因为 Python 3.9 不使用它,但 Win10 已包含自 Windows 10 1703(2017 年发布)以来的 IANA ICU 数据,但它在 1903 年有所改进。根据 Raymond Chen 的说法,他们现在建议应用程序在 Windows 注册表 TZ 数据上使用它。 devblogs.microsoft.com/oldnewthing/20210527-00/?p=105255
好答案,希望有一天能登上榜首。【参考方案4】:
你不能用标准库做到这一点。使用 pytz 模块,您可以将任何天真/感知的日期时间对象转换为任何其他时区。让我们看一些使用 Python 3 的示例。
通过类方法
utcnow()
创建的朴素对象
要将 naive 对象转换为任何其他时区,首先您必须将其转换为 aware 日期时间对象。您可以使用 replace
方法将 naive 日期时间对象转换为 aware 日期时间对象。然后要将 aware 日期时间对象转换为任何其他时区,您可以使用 astimezone
方法。
变量 pytz.all_timezones
为您提供 pytz 模块中所有可用时区的列表。
import datetime,pytz
dtobj1=datetime.datetime.utcnow() #utcnow class method
print(dtobj1)
dtobj3=dtobj1.replace(tzinfo=pytz.UTC) #replace method
dtobj_hongkong=dtobj3.astimezone(pytz.timezone("Asia/Hong_Kong")) #astimezone method
print(dtobj_hongkong)
通过类方法
now()
创建的朴素对象
因为now
方法返回当前日期和时间,所以你必须首先让 datetime 对象知道时区。 localize
函数将 naive 日期时间对象转换为可识别时区的日期时间对象。然后可以使用astimezone
方法将其转换为另一个时区。
dtobj2=datetime.datetime.now()
mytimezone=pytz.timezone("Europe/Vienna") #my current timezone
dtobj4=mytimezone.localize(dtobj2) #localize function
dtobj_hongkong=dtobj4.astimezone(pytz.timezone("Asia/Hong_Kong")) #astimezone method
print(dtobj_hongkong)
【讨论】:
【参考方案5】:以阿列克谢的评论为基础。这也适用于 DST。
import time
import datetime
def utc_to_local(dt):
if time.localtime().tm_isdst:
return dt - datetime.timedelta(seconds = time.altzone)
else:
return dt - datetime.timedelta(seconds = time.timezone)
【讨论】:
【参考方案6】:我想我想通了:计算自纪元以来的秒数,然后使用 time.localtime 转换为本地 timzeone,然后将时间结构转换回 datetime...
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 )
它正确地应用了夏季/冬季 DST:
>>> 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)
【讨论】:
呃。但这至少应该在 Unix 上工作。对于 Windows,如果您的夏令时规则在转换日期后发生了变化,则可能是不正确的。你为什么不想使用图书馆? 我只需要 utc/local 转换,所以拖动这样的库似乎有点矫枉过正。由于我是该应用程序的唯一用户,因此我可以忍受一些限制。它还避免了管理依赖相关问题(支持 Python 3?、Windows?质量......),这比处理我的小脚本更昂贵。 好吧。如果你需要 Python3,那么你就不走运了。但是,与使用库相比,研究和制作自己的解决方案是多余的。 +1 (以抵消反对票:寻求仅 stdlib 的解决方案可能是有效的要求),并提到了 @Lennart 的警告。鉴于 Unix 和 Windows 上的 dateutil might fail。顺便说一句,为了清楚起见,您可以从代码中提取utctotimestamp(utc_dt) -> (seconds, microseconds)
,请参阅 Python 2.6-3.x implementation
一个 pytz(和 dateutil)在 Python 3 上工作已经有一段时间了,所以 Python3/Windows/质量问题不再是问题了。【参考方案7】:
标准 Python 库根本没有任何 tzinfo
实现。我一直认为这是 datetime 模块的一个令人惊讶的缺点。
documentation for the tzinfo class 确实提供了一些有用的示例。查找本节末尾的大代码块。
【讨论】:
我对核心实现之类的猜测是因为时区数据库需要定期更新,因为某些国家/地区没有确定 DST 周期的固定规则。如果我没记错的话,例如需要更新 JVM 以获取最后一个时区数据库... @Nitro Zark,至少他们应该有一个 UTC。os
模块可以根据操作系统功能提供一个本地时间。
我正在检查 3.2 中的新功能列表,他们在 3.2 中添加了 UTC 时区 (docs.python.org/dev/whatsnew/3.2.html#datetime)。不过似乎没有本地时区...【参考方案8】:
使用time.timezone
,它给出一个整数,单位为“UTC 以西的秒数”。
例如:
from datetime import datetime, timedelta, timezone
import time
# make datetime from timestamp, thus no timezone info is attached
now = datetime.fromtimestamp(time.time())
# make local timezone with time.timezone
local_tz = timezone(timedelta(seconds=-time.timezone))
# attach different timezones as you wish
utc_time = now.astimezone(timezone.utc)
local_time = now.astimezone(local_tz)
print(utc_time.isoformat(timespec='seconds'))
print(local_time.isoformat(timespec='seconds'))
在我的 PC (Python 3.7.3) 上,它提供:
2021-05-07T12:50:46+00:00
2021-05-07T20:50:46+08:00
相当简单,只使用标准库~
【讨论】:
【参考方案9】:一种适用于 Python 2 和 3 的简单(但可能存在缺陷)方式:
import time
import datetime
def utc_to_local(dt):
return dt - datetime.timedelta(seconds = time.timezone)
它的优点是写反函数很简单
【讨论】:
这给出了错误的结果,因为time.timezone
没有考虑夏令时。【参考方案10】:
我发现最简单的方法是获取你所在位置的时间偏移,然后从小时中减去。
def format_time(ts,offset):
if not ts.hour >= offset:
ts = ts.replace(day=ts.day-1)
ts = ts.replace(hour=ts.hour-offset)
else:
ts = ts.replace(hour=ts.hour-offset)
return ts
这对我有用,在 Python 3.5.2 中。
【讨论】:
【参考方案11】:这是另一种以日期时间格式更改时区的方法(我知道我在这上面浪费了我的精力,但我没有看到这个页面,所以我不知道如何)没有 min.和秒。因为我的项目不需要它:
def change_time_zone(year, month, day, hour):
hour = hour + 7 #<-- difference
if hour >= 24:
difference = hour - 24
hour = difference
day += 1
long_months = [1, 3, 5, 7, 8, 10, 12]
short_months = [4, 6, 9, 11]
if month in short_months:
if day >= 30:
day = 1
month += 1
if month > 12:
year += 1
elif month in long_months:
if day >= 31:
day = 1
month += 1
if month > 12:
year += 1
elif month == 2:
if not year%4==0:
if day >= 29:
day = 1
month += 1
if month > 12:
year += 1
else:
if day >= 28:
day = 1
month += 1
if month > 12:
year += 1
return datetime(int(year), int(month), int(day), int(hour), 00)
【讨论】:
使用 timedelta 在时区之间切换。您所需要的只是时区之间的小时偏移量。不必为日期时间对象的所有 6 个元素摆弄边界。 timedelta 也可以轻松处理闰年、闰世纪等。您必须“从日期时间导入 timedelta”。那么如果偏移量是以小时为单位的变量:timeout = timein + timedelta(hours = offset),其中 timein 和 timeout 是 datetime 对象。例如timein + timedelta(hours = -8) 从 GMT 转换为 PST。【参考方案12】:这是一种糟糕的方法,但它避免了创建定义。它满足了坚持使用基本 Python3 库的要求。
# Adjust from UST to Eastern Standard Time (dynamic)
# df.my_localtime should already be in datetime format, so just in case
df['my_localtime'] = pd.to_datetime.df['my_localtime']
df['my_localtime'] = df['my_localtime'].dt.tz_localize('UTC').dt.tz_convert('America/New_York').astype(str)
df['my_localtime'] = pd.to_datetime(df.my_localtime.str[:-6])
【讨论】:
虽然这很有趣,我将使用这种方法,但它不仅仅使用标准库。它正在使用 Pandas 进行转换pandas.pydata.org/pandas-docs/version/0.25/reference/api/…【参考方案13】:使用 timedelta 在时区之间切换。您所需要的只是时区之间的小时偏移量。不必为日期时间对象的所有 6 个元素摆弄边界。 timedelta 也可以轻松处理闰年、闰世纪等。你必须先
from datetime import datetime, timedelta
那么如果offset
是以小时为单位的时区增量:
timeout = timein + timedelta(hours = offset)
其中 timein 和 timeout 是日期时间对象。例如
timein + timedelta(hours = -8)
从 GMT 转换为 PST。
那么,如何确定offset
?这是一个简单的函数,前提是您只有几种转换可能性,而不使用时区“感知”的日期时间对象,而其他一些答案很好地做到了。有点手动,但有时清晰是最好的。
def change_timezone(timein, timezone, timezone_out):
'''
changes timezone between predefined timezone offsets to GMT
timein - datetime object
timezone - 'PST', 'PDT', 'GMT' (can add more as needed)
timezone_out - 'PST', 'PDT', 'GMT' (can add more as needed)
'''
# simple table lookup
tz_offset = 'PST': 'GMT': 8, 'PDT': 1, 'PST': 0, \
'GMT': 'PST': -8, 'PDT': -7, 'GMT': 0, \
'PDT': 'GMT': 7, 'PST': -1, 'PDT': 0
try:
offset = tz_offset[timezone][timezone_out]
except:
msg = 'Input timezone=' + timezone + ' OR output time zone=' + \
timezone_out + ' not recognized'
raise DateTimeError(msg)
return timein + timedelta(hours = offset)
在查看了众多答案并使用了我能想到的最紧凑的代码之后(目前),所有应用程序,其中时间很重要并且必须考虑到混合时区,都应该真正努力做到所有日期时间对象“知道”。那么看起来最简单的答案是:
timeout = timein.astimezone(pytz.timezone("GMT"))
例如转换为 GMT。当然,要转换到/从您希望的任何其他时区,本地或其他时区,只需使用 pytz 理解的适当时区字符串(来自 pytz.all_timezones)。然后还会考虑夏令时。
【讨论】:
这是/不是/一个好主意。 DST 不会在整个世界的同一日期发生变化。使用日期时间函数来做到这一点。 @gabe - 了解该表不是一个好主意,尽管它可能适合仅涵盖美国时区的人。正如我最后所说,最好的方法是始终让 datetime 对象“感知”,以便您可以轻松使用 datetime 函数。以上是关于如何仅使用标准库将 UTC 日期时间转换为本地日期时间?的主要内容,如果未能解决你的问题,请参考以下文章
如何将 UTC 日期字符串转换为本地时间 (systemTimeZone)