如何仅使用标准库将UTC日期时间转换为本地日期时间?

我有一个python datetime实例,使用datetime.utcnow()创建并持久化在数据库中。

为了显示,我想使用默认的本地时区将从数据库检索到的datetime实例转换为本地datetime(即,就好像datetime是使用datetime.now()创建的一样)。

如何将UTC datetime转换为本地datetime,仅使用python标准库(例如,没有pytz依赖项)?

似乎一个解决方案是使用datetime.astimezone(tz),但你如何获得默认的本地时区?

412374 次浏览

从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,你需要后台端口。zoneinfo模块:

>>> try:
>>>     from zoneinfo import ZoneInfo
>>> except ImportError:
>>>     from backports.zoneinfo import ZoneInfo

其余的都是一样的。

更早的版本需要pytzdateutil。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

我想我弄清楚了:计算自epoch以来的秒数,然后使用时间转换为本地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 )

它正确地应用夏季/冬季夏令时:

>>> 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)

标准Python库根本没有附带任何tzinfo实现。我一直认为这是datetime模块的一个惊人缺点。

tzinfo类的文档确实提供了一些有用的例子。在小节的末尾寻找大代码块。

在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.3
2010-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和MSK时区utc偏移量的最近变化。

我不知道非pytz解决方案是否适用于Windows。

在Python 2和3中工作的简单(但可能有缺陷)方法:

import time
import datetime


def utc_to_local(dt):
return dt - datetime.timedelta(seconds = time.timezone)

它的优点是写一个逆函数很简单

你不能用标准库来做。使用pytz模块,您可以将任何naive/aware datetime对象转换为任何其他时区。让我们看一些使用Python 3的例子。

通过类方法utcnow()创建的朴素对象

要将天真的对象转换为任何其他时区,首先必须将其转换为意识到 datetime对象。可以使用replace方法将天真的 datetime对象转换为意识到 datetime对象。然后使用astimezone方法将意识到 datetime对象转换为任何其他时区。

变量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函数的作用是:将天真的 datetime对象转换为一个时区感知的datetime对象。然后可以使用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)

我找到的最简单的方法是获取所在位置的时间偏移量,然后从小时中减去它。

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中,这对我来说是可行的。

根据阿列克谢的评论。这也适用于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)

下面是另一种在datetime格式中更改时区的方法(我知道我在这上面浪费了精力,但我没有看到这个页面,所以我不知道怎么做),没有min.和sec.因为我的项目不需要它:

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)

这是一种糟糕的方法,但它避免了定义。它满足了坚持使用基本的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])

使用timedelta在时区之间切换。您所需要的只是时区之间的小时偏移量。不必为datetime对象的所有6个元素设置边界。Timedelta也可以轻松处理闰年、闰世纪等。你必须首先

from datetime import datetime, timedelta

然后,如果offset是时区delta(以小时为单位):

timeout = timein + timedelta(hours = offset)

其中timein和timeout是datetime对象。如。

timein + timedelta(hours = -8)

从格林尼治标准时间转换为太平洋标准时间。

那么,如何确定offset?这里是一个简单的函数,前提是你只有一些转换的可能性,而不使用时区“感知”的datetime对象,而其他一些答案很好地做到了。有点手动,但有时清晰是最好的。

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)

在看了大量的答案和我能想到的最严格的代码之后(目前),似乎最好的是所有应用程序,其中时间是重要的,混合时区必须考虑在内,应该真正努力使所有datetime对象“感知”。那么,最简单的答案似乎是:

timeout = timein.astimezone(pytz.timezone("GMT"))

例如,转换为格林尼治时间。当然,要转换到或从您希望的任何其他时区(本地或其他时区),只需使用pytz理解的适当的时区字符串(来自pytz.all_timezones)。日光节约时间也被考虑在内。

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

窗户 没有系统时区数据库,所以这里需要一个额外的包:

pip install tzdata

有一个允许在Python 3.6至3.8中使用的后端口:

sudo pip install backports.zoneinfo

然后:

from backports.zoneinfo import ZoneInfo

使用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

非常简单,只使用标准库~

对于特定情况:
输入utc datetime字符串。//通常from log

.输出区域日期时间字符串
def utc_to_locale(utc_str):
# from utc to locale
d1=datetime.fromisoformat(utc_str+'-00:00')
return d1.astimezone().strftime('%F %T.%f')[:-3]

测试:

>>> utc_to_locale('2022-02-14 00:49:06')
'2022-02-14 08:49:06.000'
>>> utc_to_locale('2022-02-14 00:49:06.123')
'2022-02-14 08:49:06.123'
>>> utc_to_locale('2022-02-14T00:49:06.123')
'2022-02-14 08:49:06.123'