Преобразовать дату и время 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.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
Python 22010-06-06 21:29:07.730000
2010-12-06 20:29:07.730000
2012-11-08 14:19:50.093911
pytz2010-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). При этом также учитывается летнее время.