簡體   English   中英

如何僅使用標准庫將 UTC 日期時間轉換為本地日期時間?

[英]How to convert a UTC datetime to a local datetime using only standard library?

我有一個 python datetime時間實例,它是使用datetime.utcnow()創建並保存在數據庫中的。

為了顯示,我想使用默認的本地時區將從數據庫檢索到的datetime時間實例轉換為本地datetime時間(即,就好像datetime時間是使用datetime.now()創建的一樣)。

如何僅使用 python 標准庫(例如,沒有pytz依賴項)將 UTC datetime時間轉換為本地datetime時間?

似乎一種解決方案是使用datetime.astimezone(tz) ,但是您將如何獲得默認的本地時區?

在 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.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.146917 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

注意:它考慮了夏令時和最近 MSK 時區的 utc 偏移量變化。

我不知道非 pytz 解決方案是否適用於 Windows。

你不能只用標准庫來做,因為標准庫沒有任何時區。 您需要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 的情況下完成它。 但那將是愚蠢的。

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

你不能用標准庫來做到這一點。 使用pytz模塊,您可以將任何原始/感知日期時間對象轉換為任何其他時區。 讓我們看一些使用 Python 3 的例子。

通過類方法utcnow()創建的朴素對象

要將原始對象轉換為任何其他時區,首先必須將其轉換為可感知的日期時間對象。 您可以使用replace方法將原始日期時間對象轉換為感知日期時間對象。 然后將有意識的日期時間對象轉換為任何其他時區,您可以使用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函數將原始日期時間對象轉換為時區感知日期時間對象。 然后您可以使用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)

以阿列克謝的評論為基礎。 這也適用於 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)

我想我想通了:計算自紀元以來的秒數,然后使用 time.localtime 轉換為本地 timzeone,然后將時間結構轉換回日期時間...

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)

標准 Python 庫根本沒有附帶任何tzinfo實現。 我一直認為這是 datetime 模塊的一個令人驚訝的缺點。

tzinfo 類文檔確實附帶了一些有用的示例。 查找本節末尾的大代碼塊。

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

在我的電腦(Python 3.7.3)上,它給出:

2021-05-07T12:50:46+00:00
2021-05-07T20:50:46+08:00

非常簡單,只使用標准庫~

一種適用於 Python 2 和 3 的簡單(但可能有缺陷)方式:

import time
import datetime

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

它的優點是寫反函數很簡單

我發現的最簡單的方法是獲取所在位置的時間偏移,然后從小時中減去它。

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

這是另一種以日期時間格式更改時區的方法(我知道我浪費了我的精力,但我沒有看到這個頁面,所以我不知道如何)沒有分鍾。 和秒。 因為我的項目不需要它:

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)

對於特定情況:
輸入 utc 日期時間字符串。 // 通常來自日志
output 語言環境日期時間字符串。


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'

這是一種糟糕的方法,但它避免了創建定義。 它滿足了堅持使用基本 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 在時區之間切換。 您所需要的只是時區之間以小時為單位的偏移量。 不必擺弄日期時間對象的所有 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)。 夏令時也會被考慮在內。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM