Перетворити UTC-дату пітону в локальний час, використовуючи лише стандартну бібліотеку python?


189

У мене є екземпляр python datetime, створений за допомогою datetime.utcnow () і зберігається в базі даних.

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

Як я можу перетворити час дати UTC у локальний час, використовуючи лише стандартну бібліотеку python (наприклад, відсутність залежності pytz)?

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


У якому форматі час зберігався до бази даних? Якщо це стандартний формат, можливо, вам не потрібно робити перетворення.
Апалала

1
Ця відповідь показує простий спосіб використання pytz .
червня 13

Відповіді:


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

Вихідні дані

Пітон 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
Пітон 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

Примітка: вона враховує DST та нещодавню зміну utcset для часового поясу MSK.

Я не знаю, чи працюють не-pytz рішення в Windows.


1
що робить "нормалізація"?
avi

4
@avi: загалом: pytz: Для чого потрібна нормалізація при перетворенні між часовими поясами ? . Примітка. Не потрібно з поточною реалізацією, .normalize()оскільки часовим поясом для джерела .astimezone()є UTC.
jfs

1
Я отримую TypeError: replace() takes no keyword argumentsпри спробі рішення пітса.
Крейг

@Craig код працює як є, як показує вихід у відповіді. Створіть повний приклад мінімального коду, який призводить до TypeError, згадайте ваші версії Python, pytz, які ви використовуєте, та опублікуйте їх як окреме запитання про переповнення стека.
jfs

Коротка порада для рішення Python 3.3+. Щоб піти в зворотному напрямку (Native to 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+, але це підозріло)?
Nitro Zark


pytz дійсно справляється з переходом на літній час.
Леннарт Регебро

2
Оновлення: pytz та dateutil сьогодні підтримують Python 3.
Леннарт Регебро

1
@JFSebastian: dateutil не може розрізняти два 1:30 рази, які трапляються під час переходу на DST. Якщо вам потрібно вміти розрізняти ці два рази, обходьте його, використовуючи pytz, який може впоратися з цим.
Леннарт Регебро

8

Ви не можете зробити це зі стандартною бібліотекою. Використовуючи модуль 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 Функція конвертує наївні об'єкт 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)

6

Я думаю, я це зрозумів: обчислює кількість секунд з епохи, потім перетворюється на локальний timzeone за допомогою time.localtime, а потім перетворює структуру часу назад у дату ...

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? Якість ...), що було б дорожче, ніж робота над моїм невеликим сценарієм.
Nitro Zark

1
Ну. якщо вам потрібен Python3, то вам не пощастить. Але в іншому випадку дослідження та прийняття власного рішення є надмірним порівняно з використанням бібліотеки.
Леннарт Регебро

1
+1 (для компенсації нижньої позиції: може бути справедливою вимога шукати рішення, що стосуються лише stdlib) із зазначенням застереження @Lennart. Враховуючи, що датчик може вийти з ладу і для Unix, і для Windows. btw, ви можете витягнути utctotimestamp(utc_dt) -> (seconds, microseconds)з коду для наочності, див. реалізацію Python 2.6-3.x
jfs

1
Pytz (та dateutil) працює на Python 3 з певного часу, тому проблеми Python3 / Windows / Quality вже не є проблемою.
Леннарт Регебро

6

Спираючись на коментар Олексія. Це повинно працювати і для 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

@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

вам навіть zoneinfoтут не потрібно - дивіться першу частину відповіді jfs (Python3.3 + частина) ;-)
MrFuppes

@MrFuppes Я думаю ZoneInfo('localtime'), що це набагато чіткіше tz=None(зрештою, можна очікувати tz=Noneвидалення часового поясу або повернення UTC, а не локального часу).
xjcl

@MrFuppes Plus питання може стосуватись ОП, який має веб-сайт та бажає показати часові позначки користувачам у їх місцевий час. Для цього вам доведеться конвертувати в явний часовий пояс користувача, а не в локальний час.
xjcl

0

Простий (але може бути хибний) спосіб, який працює в Python 2 і 3:

import time
import datetime

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

Його перевага полягає в тому, що писати зворотну функцію тривіально


1
Це дає неправильний результат, оскільки time.timezoneне враховує економію денного світла.
guyarad

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 обробляє високосні роки, високосні століття тощо, теж легко. Ви повинні "з дати імпортувати timedelta datetime". Тоді якщо зміщення є змінною у годинах: 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 елементів об’єкта дати. 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
Це / не / гарна ідея. DST не зміщується в одну і ту ж дату для всього світу. Використовуйте для цього функції дати.
Гейб

@gabe - Зрозумійте, що таблиця не є хорошою ідеєю, хоча вона може підійти тому, хто охоплює лише часові пояси в США. Як я вже говорив наприкінці, найкращий спосіб ЗАВЖДИ зробити так, щоб об'єкти дати «усвідомлювались», щоб ви могли легко використовувати функції часу.
Карл Рудник
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.