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

189

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

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

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

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

Нитро жарк
источник
В каком формате время сохранялось в базе данных? Если это стандартный формат, возможно, вам не нужно ничего конвертировать.
Апалала
1
Этот ответ показывает простой способ использования pytz .
хуан

Ответы:

275

В 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

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

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

JFS
источник
1
что делает «нормализация»?
Ави
4
@avi: в общем: pytz: зачем нужна нормализация при преобразовании часовых поясов? , Примечание. В текущей реализации нет необходимости, .normalize()поскольку источником часового пояса .astimezone()является UTC.
Jfs
1
Я получаю TypeError: replace() takes no keyword argumentsпри попытке решения pytz.
Крейг
@Craig код работает как есть, как показывает вывод в ответе. Создайте полный пример минимального кода, который приводит к ошибке TypeError, укажите свой Python, версии pytz, которые вы используете, и опубликуйте его как отдельный вопрос переполнения стека.
Jfs
Быстрый совет по решению Python 3.3+. Чтобы пойти в противоположном направлении (уроженец UTC), это работает: local_dt.replace(tzinfo=None).astimezone(tz=timezone.utc)
Jasonrhaas
50

Вы не можете сделать это только со стандартной библиотекой, поскольку стандартная библиотека не имеет часовых поясов. Вам нужно 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, внедрив свои собственные часовые пояса. Но это было бы глупо.

Леннарт Регебро
источник
Спасибо, я буду держать это под рукой, если мне когда-нибудь понадобится полное решение. Поддерживает ли dateutil Python 3.1 (говорит Python 2.3+, но это подозрительно)?
Нитро Зарк
Pytz действительно лучше справляется с заменой DST.
Леннарт Регебро
2
Обновление: pytz и dateutil поддерживают Python 3 в настоящее время.
Леннарт Регебро
1
@JFSebastian: dateutil не может различить два раза 1:30, которые происходят во время перехода на летнее время. Если вам нужно уметь различать эти два раза, обходите его, используя pytz, который может с этим справиться.
Леннарт Регебро
8

Вы не можете сделать это со стандартной библиотекой. С помощью модуля 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 в часовой пояс-Aware объект даты и времени. Затем вы можете использовать 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)
N Randhawa
источник
6

Я думаю, я понял это: вычисляет количество секунд с начала эпохи, затем преобразует в локальный тимзеон, используя 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)
Нитро жарк
источник
1
Тьфу. Но это должно работать на Unix, по крайней мере. Для Windows это может быть неправильно, если ваши правила перехода на летнее время изменились с даты конвертации. Почему вы не хотите использовать библиотеку?
Леннарт Регебро
Мне нужно только преобразование utc / local, поэтому перетаскивание такой библиотеки кажется излишним. Поскольку я единственный пользователь приложения, я могу жить с некоторыми ограничениями. Это также позволило избежать управления проблемами, связанными с зависимостями (поддержка Python 3 ?, Windows? Качество ...), которые были бы более дорогостоящими, чем работа над моим маленьким сценарием.
Нитро Зарк
1
Хорошо. если вам нужен Python3, то вам не повезло. Но в противном случае исследование и создание собственного решения излишне по сравнению с использованием библиотеки.
Леннарт Регебро
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 больше не являются проблемой.
Леннарт Регебро
6

Опираясь на комментарии Алексея. Это должно работать и для летнего времени.

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
Я думаю о подобной реализации ядра, потому что база данных часовых поясов должна регулярно обновляться, поскольку в некоторых странах нет фиксированных правил для определения периодов летнего времени. Если я не ошибаюсь, JVM, например, необходимо обновить, чтобы получить последнюю базу данных часовых поясов ...
Nitro Zark
@Nitro Zark, по крайней мере, у них должен был быть один для UTC. osМодуль мог бы обеспечить один для местного времени на основе операционных функций системы.
Марк Рэнсом
1
Я проверял список новых функций в 3.2, и они добавили часовой пояс UTC в 3.2 ( docs.python.org/dev/whatsnew/3.2.html#datetime ). Там, кажется, нет местного часового пояса, хотя ...
Nitro Zark
2

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
xjcl
источник
вам zoneinfoздесь даже не нужно - смотрите первую часть ответа jfs (часть Python3.3 +) ;-)
MrFuppes
@MrFuppes Я думаю, что ZoneInfo('localtime')это намного понятнее tz=None(в конце концов, можно ожидать tz=Noneудаления часового пояса или возврата UTC, а не местного времени).
xjcl
@MrFuppes Плюс вопрос может касаться того, чтобы у ОП был веб-сайт и он хотел показывать временные метки пользователей по местному времени. Для этого вам придется преобразовать в явный часовой пояс пользователя, а не местное время.
xjcl
0

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

import time
import datetime

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

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

Алексей Аверченко
источник
1
Это дает неверный результат, так как time.timezoneне учитывает переход на летнее время.
Гайарад
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.

Zld Productions
источник
0

Вот еще один способ изменить часовой пояс в формате 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:
          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 элементов объекта datetime. Timedelta легко справляется с високосными годами, високосными веками и т. д. Вы должны «из даты и времени импортировать timedelta». Затем, если смещение является переменной в часах: timeout = timein + timedelta (hours = offset), где timein и timeout являются объектами datetime. например, timein + timedelta (часы = -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 для конвертации pandas.pydata.org/pandas-docs/version/0.25/reference/api/…
Тим Хьюз,
-3

Используйте 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? Вот простая функция, при условии, что у вас есть только несколько возможностей для конвертации без использования объектов даты и времени, которые «знают» часовой пояс, что приятно делать другим ответам. Немного ручной, но иногда ясность лучше.

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). Летнее время также учитывается.

Карл Рудник
источник
1
Это / не / хорошая идея. ДСТ не сдвигается в один и тот же день для всего мира. Используйте функции datetime, чтобы сделать это.
Гейб
@gabe - Поймите, что таблица не очень хорошая идея, хотя она может подойти кому-то, охватывающему только часовые пояса США. Как я сказал в конце, лучший способ ВСЕГДА сделать объекты datetime «осведомленными», чтобы вы могли легко использовать функции datetime.
Карл Рудник