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


188

我有一个使用datetime.utcnow()创建并保存在数据库中的python datetime实例。

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

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

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


时间以哪种格式保存在数据库中?如果是标准格式,则可能不需要进行任何转换。
阿帕拉拉

1
这个答案显示了使用pytz的简单方法。
2013年

Answers:


272

在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上运行。


1
“规范化”是做什么的?
2014年

4
@avi:通常:pytz:为什么在时区之间进行转换时需要规范化?。注意:由于当前.normalize()的时区.astimezone()是UTC ,因此在当前实现中没有必要。
jfs 2014年

1
TypeError: replace() takes no keyword arguments在尝试pytz解决方案时得到了。
Craig

@Craig代码按原样工作,如答案中的输出所示。创建导致TypeError的完整的最小代码示例,提及您使用的Python和pytz版本,并将其发布为单独的Stack Overflow问题。
jfs

Python 3.3+解决方案的快速提示。要朝相反的方向(与UTC相对),可以使用: local_dt.replace(tzinfo=None).astimezone(tz=timezone.utc)
jasonrhaas,

50

您不能仅使用标准库来执行此操作,因为标准库没有任何时区。您需要pytzdateutil

>>> from datetime import datetime
>>> now = datetime.utcnow()
>>> from dateutil import tz
>>> HERE = tz.tzlocal()
>>> UTC = tz.gettz('UTC')

The Conversion:
>>> gmt = now.replace(tzinfo=UTC)
>>> gmt.astimezone(HERE)
datetime.datetime(2010, 12, 30, 15, 51, 22, 114668, tzinfo=tzlocal())

或者,可以通过实现自己的时区来实现,而无需pytz或dateutil。但这将是愚蠢的。


谢谢,如果需要完整的解决方案,我会随时处理。dateutil是否支持Python 3.1(例如Python 2.3+,但可疑)?
Nitro Zark 2010年


pytz确实可以更好地处理DST转换。
Lennart Regebro 2012年

2
更新:pytz和dateutil现在都支持Python 3。
Lennart Regebro 2012年

1
@JFSebastian:dateutil无法区分DST转换期间发生的两个1:30时间。如果您需要能够区分这两次,则可以使用pytz解决该问题,该方法可以处理该问题。
Lennart Regebro 2012年

8

您无法使用标准库执行此操作。使用 pytz模块,您可以将任何原始/知道的datetime对象转换为任何其他时区。让我们来看一些使用Python 3的示例。

通过类方法创建的幼稚对象 utcnow()

要将原始对象转换为任何其他时区,首先必须将其转换为可感知的日期时间对象。您可以使用replace一个转换方法天真的 DateTime对象的感知 DateTime对象。然后到转换意识到 DateTime对象,你可以使用任何其它时区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()

因为nowmethod返回当前日期和时间,所以您必须首先使datetime对象时区知道。该localize 函数将天真日期时间对象转换为时区感知日期时间对象。然后,您可以使用该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)

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)

1
啊。但这至少应该在Unix上有效。对于Windows,如果自转换日期以来夏令时规则已更改,则可能是不正确的。您为什么不想使用图书馆?
Lennart Regebro

我只需要utc /本地转换,因此拖拽这样的库似乎太过分了。由于我是该应用程序的唯一用户,因此我可以忍受一些限制。它还避免管理与依赖关系有关的问题(支持Python 3?,Windows?质量...),这比处理我的小脚本要昂贵得多。
Nitro Zark 2010年

1
好。如果您需要Python3,那么您就不走运了。但是与使用库相比,研究和制定自己的解决方案是过大的。
Lennart Regebro 2010年

1
+1(以抵消反对意见:寻求仅stdlib的解决方案可能是有效要求),其中提到了警告@Lennart。鉴于dateutil在Unix和Windows上都可能失败。顺便说一句,utctotimestamp(utc_dt) -> (seconds, microseconds)为了清楚起见,您可以从代码中提取内容,请参阅Python 2.6-3.x实现
jfs

1
从现在开始,pytz(和dateutil)就可以在Python 3上使用了,因此Python3 / Windows / Quality问题不再是问题。
Lennart Regebro 2012年

6

基于Alexei的评论。这也适用于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)

4

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

tzinfo类文档的确提供了一些有用的示例。在本节末尾查找较大的代码块。


1
我对核心实现之类的猜测是因为时区数据库需要定期更新,因为某些国家/地区没有确定DST周期的固定规则。如果我没记错的话,例如需要更新JVM以获取最后的时区数据库...
Nitro Zark 2010年

@Nitro Zark,至少他们应该为UTC准备一个。该os模块可以根据操作系统功能为本地时间提供一个。
Mark Ransom

1
我正在检查3.2中的新功能列表,并在3.2中添加了UTC时区(docs.python.org/dev/whatsnew/3.2.html#datetime)。不过似乎没有本地时区...
Nitro Zark 2010年

1

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

Windows 没有系统时区数据库,因此这里需要一个额外的程序包:

pip install tzdata  

有一个backport允许在Python 3.6到3.8中使用

sudo pip install backports.zoneinfo

然后:

from backports.zoneinfo import ZoneInfo

您甚至不需要zoneinfo在这里-请参阅jfs答案的第一部分(Python3.3 +部分;-)
MrFuppes

0

一种适用于Python 2和3的简单(但可能有缺陷)方法:

import time
import datetime

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

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


1
由于time.timezone没有考虑夏令时,因此得出的结果是错误的。
Guyarad'8

0

我发现的最简单的方法是获取所在位置的时间偏移,然后从小时中减去该时间。

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中。


0

这是更改日期时间格式的时区的另一种方法(我知道我在此上浪费了精力,但是我没有看到此页面,所以我不知道如何)而没有分钟。和秒。因为我的项目不需要它:

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也可以轻松处理leap年,leap历世纪等。您必须“从datetime导入timedelta”。然后,如果偏移量是小时变量:timeout = timein + timedelta(hours = offset),其中timein和timeout是datetime对象。例如timein + timedelta(hours = -8)从GMT转换为PST。
卡尔·鲁德尼克

0

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

尽管这很有趣,并且我将使用此方法,但它不仅使用stdlib。它是利用熊猫做转换 pandas.pydata.org/pandas-docs/version/0.25/reference/api/...
蒂姆·休斯

-3

使用timedelta在时区之间切换。您所需要的只是时区之间的小时数偏移。不必摆弄日期时间对象的所有6个元素的边界。timedelta也可以轻松处理leap年,leap历世纪等。你必须先

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)

在查看了无数的答案并尝试了我能想到的最严格的代码之后(目前),似乎所有时间都非常重要且必须考虑混合时区的应用程序似乎应该尽一切努力来制作所有datetime对象“知道的”。那么,最简​​单的答案似乎是:

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

例如转换为格林尼治标准时间。当然,要与您希望的任何其他时区(本地或其他)进行相互转换,只需使用pytz可以理解的适当时区字符串(来自pytz.all_timezones)。然后还要考虑夏令时。


1
这不是一个好主意。DST不会在整个世界的同一日期转移。使用datetime函数可以做到这一点。
加布

@gabe-了解该表格不是一个好主意,尽管它可能适合仅涵盖美国时区的人。就像我在最后所说的那样,最好的方法是始终“使”日期时间对象成为“对象”,以便您可以轻松地使用日期时间函数。
Karl Rudnick
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.