Перетворити 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.