Преобразовать дату и время Python UTC в локальную дату, используя только стандартную библиотеку Python?

У меня есть экземпляр даты и времени Python, который был создан с использованием datetime.utcnow() и сохранился в базе данных.

Для отображения я хотел бы преобразовать экземпляр datetime, извлеченный из базы данных, в локальный datetime, используя локальный часовой пояс по умолчанию (то есть, как если бы datetime был создан с использованием datetime.now()).

Как я могу преобразовать дату и время UTC в локальную дату, используя только стандартную библиотеку python (например, без зависимости от pytz)?

Кажется, одним из решений было бы использовать datetime.astimezone( tz), но как бы вы получили местный часовой пояс по умолчанию?

14 ответов

Решение

Я думаю, я понял это: вычисляет количество секунд с начала эпохи, затем преобразует в локальный тимзеон, используя time.localtime, а затем преобразует структуру времени обратно в 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 3.3+:

from datetime import 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

Примечание: он учитывает летнее время и недавнее изменение смещения utc для часового пояса MSK.

Я не знаю, работают ли не-pytz-решения на Windows.

Вы не можете сделать это только со стандартной библиотекой, поскольку стандартная библиотека не имеет часовых поясов. Вам нужно pytz или dateutil.

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

Центральная Европа на 1 или 2 часа опережает UTC, поэтому 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  

Существует резервный порт, позволяющий использовать Python с 3.6 по 3.8:

sudo pip install backports.zoneinfo

Затем:

from backports.zoneinfo import ZoneInfo

Вы не можете сделать это со стандартной библиотекой. С помощью модуля pytz вы можете конвертировать любой наивный / осведомленный объект datetime в любой другой часовой пояс. Давайте посмотрим на некоторые примеры использования Python 3.

Наивные объекты, созданные методом класса utcnow()

Чтобы преобразовать наивный объект в любой другой часовой пояс, сначала вы должны преобразовать его в осведомленный объект datetime. Вы можете использовать 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()

Так как 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)

Опираясь на комментарии Алексея. Это должно работать и для 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)

Стандартная библиотека 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

Довольно просто и использует только стандартные библиотеки ~

Самый простой способ, который я нашел, это получить смещение времени, в котором вы находитесь, а затем вычесть это из часа.

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.

для конкретной ситуации:
 введите строку utc datetime. // обычно из лога
 выводится строка даты и времени локали.


      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'

Простой (но, возможно, ошибочный) способ, который работает в Python 2 и 3:

import time
import datetime

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

Его преимущество в том, что написать обратную функцию тривиально

Вот еще один способ изменить часовой пояс в формате datetime (я знаю, что потратил впустую свою энергию на это, но я не видел эту страницу, поэтому я не знаю как) без мин. и сек. потому что мне это не нужно для моего проекта:

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:
          leap_years = [2000, 2004, 2008, 2012, 2016, 2020, 2024, 2028, 2032, 2036, 2040, 2044, 2048]
          if year in leap_years:
            if day >= 29:
              day = 1
              month += 1
              if month > 12:
                year += 1
          elif year not in leap_years:
            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 для переключения между часовыми поясами. Все, что вам нужно, - это смещение в часах между часовыми поясами. Не нужно возиться с границами для всех 6 элементов объекта datetime. timedelta с легкостью обрабатывает високосные годы, високосные века и т. д. Вы должны сначала

from datetime import datetime, timedelta

Тогда если offset дельта часового пояса в часах:

timeout = timein + timedelta(hours = offset)

где timein и timeout - это объекты datetime. например

timein + timedelta(hours = -8)

конвертирует из GMT в PST.

Итак, как определить 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"))

для преобразования, например, в GMT. Конечно, для преобразования в / из любого другого часового пояса по вашему желанию, местного или другого, просто используйте соответствующую строку часового пояса, которую понимает pytz (из pytz.all_timezones). При этом также учитывается летнее время.

Другие вопросы по тегам