Перетворити рядок date date UTC у місцевий час date


206

Мені ніколи не доводилося перетворювати час на UTC і з нього. Нещодавно в мене з’явився запит на те, щоб моє додаток було відоме часовому поясу, і я працював у колі. Дуже багато інформації про перетворення місцевого часу в UTC, що я вважав досить елементарним (можливо, я також роблю це неправильно), але я не можу знайти жодної інформації про те, щоб легко перетворити час UTC у часовий пояс кінцевих користувачів.

Коротше кажучи, і додаток для Android надсилає мені дані (додаток додатка), і всередині цих даних є часова мітка. Для зберігання цієї часової позначки до часу, який я використовую:

datetime.utcfromtimestamp(timestamp)

Це, здається, працює. Коли мій додаток зберігає дані, він зберігається на 5 годин попереду (я EST -5)

Дані зберігаються на BigTable додатка, і коли їх отримують, вони виходять у вигляді рядка таким чином:

"2011-01-21 02:37:21"

Як перетворити цей рядок у DateTime у правильному часовому поясі користувачів?

Крім того, що рекомендується зберігати інформацію про часовий пояс користувачів? (Як ти зазвичай зберігаєш інформацію про tz, тобто: "-5: 00" або "EST" тощо)? Я впевнений, що відповідь на моє перше запитання може містити параметр, відповіді на другий.



Ця відповідь показує, як вирішити це простим способом.
хуан

Відповіді:


402

Якщо ви не хочете надавати власні tzinfoоб’єкти, перегляньте бібліотеку python-dateutil . Він надає tzinfoреалізацію поверх бази даних zoneinfo (Olson) таким чином, що ви можете посилатися на правила часового поясу за якоюсь канонічною назвою.

from datetime import datetime
from dateutil import tz

# METHOD 1: Hardcode zones:
from_zone = tz.gettz('UTC')
to_zone = tz.gettz('America/New_York')

# METHOD 2: Auto-detect zones:
from_zone = tz.tzutc()
to_zone = tz.tzlocal()

# utc = datetime.utcnow()
utc = datetime.strptime('2011-01-21 02:37:21', '%Y-%m-%d %H:%M:%S')

# Tell the datetime object that it's in UTC time zone since 
# datetime objects are 'naive' by default
utc = utc.replace(tzinfo=from_zone)

# Convert time zone
central = utc.astimezone(to_zone)

Редагуйте розгорнутий приклад, щоб показати strptimeвикористання

Відредагуйте 2 Фіксоване використання API, щоб показати кращий метод точки введення

Редагувати 3 Включені методи автоматичного виявлення часових поясів (Ярін)


1
На своєму попередньому коментарі мені вдалося імпортувати dateutil.tzта використовувати tz.tzutc()і tz.tzlocal()як об’єкти часового поясу я шукав. Схоже, база даних часового поясу в моїй системі гарна (я зареєструвався /usr/share/zoneinfo). Не впевнений, що було.
Бен Крігер

1
@Benjamin Ви на правильному шляху. tzМодуль є правильною точкою входу використовувати для цієї бібліотеки. Я оновив свою відповідь, щоб це відобразити. dateutil.zoneinfoМодуль я показував раніше внутрішньо використовується в tzмодулі , як падіння назад , якщо він не може знайти системи ZoneInfo БД. Якщо ви заглянете всередину бібліотеки, то побачите, що в пакеті, який він використовує, є зонд БД zoneinfo, якщо він не може знайти БД вашої системи. Мій старий приклад намагався вдарити безпосередньо в цю БД, і я здогадуюсь, що у вас виникли проблеми з завантаженням приватної БД (чи не так на шляху пітона?)
Джо Холлоуей,


1
@JFSebastian це зафіксовано у # 225
Ромер

2
@briankip Це залежить від того, наскільки складною повинна бути ваша програма, але, як правило, це проблема. Перш за все залежно від того, який час року це, кількість годин для додавання / віднімання може змінюватися, оскільки деякі часові пояси вшановують DST та інші. По-друге, правила часового поясу довільно змінюються з часом, тому якщо ви працюєте з минулою датою / часом, вам потрібно застосувати правила, які були дійсні в той момент часу, а не теперішній час. Ось чому найкраще використовувати API, який використовує базу даних Olson TZ за кадром.
Джо Холлоуей

44

Ось стійкий метод, який не залежить від будь-яких зовнішніх бібліотек:

from datetime import datetime
import time

def datetime_from_utc_to_local(utc_datetime):
    now_timestamp = time.time()
    offset = datetime.fromtimestamp(now_timestamp) - datetime.utcfromtimestamp(now_timestamp)
    return utc_datetime + offset

Це дозволяє уникнути проблем із термінами на прикладі DelboyJay. І менші терміни, пов'язані з поправкою Еріка ван Оостена.

Як цікава примітка, зміщення часового поясу, обчислене вище, може відрізнятися від наступного, здавалося б, еквівалентного вираження, ймовірно, через зміни правил літнього часу:

offset = datetime.fromtimestamp(0) - datetime.utcfromtimestamp(0) # NO!

Оновлення: у цьому фрагменті є слабкість використання зрушення UTC за теперішнім часом, яке може відрізнятися від зміщення UTC вхідної дати введення. Дивіться коментарі до цієї відповіді для іншого рішення.

Щоб обійти різні часи, схопіть епоху від минулого часу. Ось що я роблю:

def utc2local (utc):
    epoch = time.mktime(utc.timetuple())
    offset = datetime.fromtimestamp (epoch) - datetime.utcfromtimestamp (epoch)
    return utc + offset

Це працює добре і не вимагає нічого, крім часу та дати.
Mike_K

6
@Mike_K: але це неправильно. Він не підтримує DST або часові пояси, які мали інші зміщення ультразвукових перешкод з інших причин. Повна підтримка без pytzподібних db неможлива, див. PEP-431. Хоча ви можете написати рішення, призначене лише для stdlib, яке працює в таких випадках у системах, які вже мають db історичного часового поясу, наприклад, Linux, OS X, дивіться мою відповідь .
jfs

@JFSebastian: Ви кажете, що цей код не працює в цілому, але ви також говорите, що він працює в OS X та Linux. Ви маєте на увазі, що цей код не працює в Windows?
Девід Фостер

2
@DavidFoster: ваш код може вийти з ладу і в Linux та OS X. Різниця між вашими та моїми рішеннями лише для stdlib полягає в тому, що ваше використовує поточне зміщення, яке може бути відмінним від зміщення utc за utc_datetimeчасом.
jfs

1
Гарний дзвінок. Це дуже тонка різниця. Зсув UTC може змінитися і з часом. зітхання
Девід Фостер

23

Див DATETIME документації по tzinfo об'єктів. Ви повинні реалізувати часові пояси, які ви хочете підтримувати. Наведені приклади внизу документації.

Ось простий приклад:

from datetime import datetime,tzinfo,timedelta

class Zone(tzinfo):
    def __init__(self,offset,isdst,name):
        self.offset = offset
        self.isdst = isdst
        self.name = name
    def utcoffset(self, dt):
        return timedelta(hours=self.offset) + self.dst(dt)
    def dst(self, dt):
            return timedelta(hours=1) if self.isdst else timedelta(0)
    def tzname(self,dt):
         return self.name

GMT = Zone(0,False,'GMT')
EST = Zone(-5,False,'EST')

print datetime.utcnow().strftime('%m/%d/%Y %H:%M:%S %Z')
print datetime.now(GMT).strftime('%m/%d/%Y %H:%M:%S %Z')
print datetime.now(EST).strftime('%m/%d/%Y %H:%M:%S %Z')

t = datetime.strptime('2011-01-21 02:37:21','%Y-%m-%d %H:%M:%S')
t = t.replace(tzinfo=GMT)
print t
print t.astimezone(EST)

Вихідні дані

01/22/2011 21:52:09 
01/22/2011 21:52:09 GMT
01/22/2011 16:52:09 EST
2011-01-21 02:37:21+00:00
2011-01-20 21:37:21-05:00a

Дякуємо за прийняття! Я трохи оновив його, щоб перевести ваш конкретний приклад часу. Розбір часу створює те, що документи називають "наївним" часом. Використовуйте, replaceщоб встановити часовий пояс на GMT та зробити його часовим поясом "відомим", а потім скористайтеся astimezoneдля перетворення в інший часовий пояс.
Марк Толонен

Вибачте за обмін за відповідь Джо. Технічно ваша відповідь пояснює, як саме це зробити з сировинним пітоном (що добре знати), але бібліотека, яку він запропонував, набагато швидше підходить до мене.
MattoTodd

4
Не схоже, що це автоматично обробляє літній час.
Gringo Suave

@GringoSuave: цього не передбачалося. Правила цього складні і часто змінюються.
Марк Толонен

19

Якщо ви хочете отримати правильний результат навіть за час, що відповідає неоднозначному місцевому часу (наприклад, під час переходу DST) та / або локальне зміщення ультразвукового сигналу у вашому локальному часовому поясі відрізняється в різний час, тоді використовуйте pytzчасові пояси:

#!/usr/bin/env python
from datetime import datetime
import pytz    # $ pip install pytz
import tzlocal # $ pip install tzlocal

local_timezone = tzlocal.get_localzone() # get pytz tzinfo
utc_time = datetime.strptime("2011-01-21 02:37:21", "%Y-%m-%d %H:%M:%S")
local_time = utc_time.replace(tzinfo=pytz.utc).astimezone(local_timezone)

10

Ця відповідь повинна бути корисною, якщо ви не хочете використовувати жодні інші модулі datetime.

datetime.utcfromtimestamp(timestamp)повертає наївний datetimeоб’єкт (не усвідомлений). Усвідомлюють це часові пояси, а наївні - ні. Ви хочете усвідомлювати, якщо ви хочете конвертувати між часовими поясами (наприклад, між UTC та місцевим часом).

Якщо ви не той, хто започаткує дату для початку, але ви все одно можете створити наївний datetimeоб’єкт у UTC час, ви можете спробувати цей код Python 3.x для його перетворення:

import datetime

d=datetime.datetime.strptime("2011-01-21 02:37:21", "%Y-%m-%d %H:%M:%S") #Get your naive datetime object
d=d.replace(tzinfo=datetime.timezone.utc) #Convert it to an aware datetime object in UTC time.
d=d.astimezone() #Convert it to your local timezone (still aware)
print(d.strftime("%d %b %Y (%I:%M:%S:%f %p) %Z")) #Print it with a directive of choice

Будьте обережні, щоб помилково не припустити, що якщо ваш часовий пояс зараз MDT, економія денного світла не працює з наведеним вище кодом, оскільки він друкує MST. Ви помітите, що якщо змінити місяць на серпень, він надрукує MDT.

Ще один простий спосіб отримати обізнаний datetimeоб’єкт (також в Python 3.x) - це створити його з часовим поясом, визначеним для початку. Ось приклад, використовуючи UTC:

import datetime, sys

aware_utc_dt_obj=datetime.datetime.now(datetime.timezone.utc) #create an aware datetime object
dt_obj_local=aware_utc_dt_obj.astimezone() #convert it to local time

#The following section is just code for a directive I made that I liked.
if sys.platform=="win32":
    directive="%#d %b %Y (%#I:%M:%S:%f %p) %Z"
else:
    directive="%-d %b %Y (%-I:%M:%S:%f %p) %Z"

print(dt_obj_local.strftime(directive))

Якщо ви використовуєте Python 2.x, вам, ймовірно, доведеться підклас datetime.tzinfoі використовувати це, щоб допомогти вам створити обізнаний datetimeоб’єкт, оскільки datetime.timezoneйого немає в Python 2.x.


8

Якщо ви використовуєте Django, ви можете використовувати timezone.localtimeметод:

from django.utils import timezone
date 
# datetime.datetime(2014, 8, 1, 20, 15, 0, 513000, tzinfo=<UTC>)

timezone.localtime(date)
# datetime.datetime(2014, 8, 1, 16, 15, 0, 513000, tzinfo=<DstTzInfo 'America/New_York' EDT-1 day, 20:00:00 DST>)

2

Можна використовувати стрілку

from datetime import datetime
import arrow

now = datetime.utcnow()

print(arrow.get(now).to('local').format())
# '2018-04-04 15:59:24+02:00'

можна годувати arrow.get()чим завгодно. позначка часу, ізо-рядок тощо


1

Я традиційно відкладаю це на фронтменді - надсилаю часи з бекенда у вигляді часових позначок або якогось іншого формату дати в UTC, а потім дозволяю клієнтові визначити зміщення часового поясу та передати ці дані у належний часовий пояс.

Для Webapp це зробити досить просто в javascript - ви можете зрозуміти зміщення часового поясу браузера досить легко, використовуючи вбудовані методи, а потім візуалізувати дані з бекенда належним чином.


3
Це добре працює у багатьох випадках, коли ви просто локалізуєте для відображення. Інколи, однак, вам потрібно буде виконати деяку логіку бізнесу, засновану на часовому поясі користувача, і ви хочете зробити можливість перетворення рівня сервера додатків.
Джо Холлоуей

1

Ви можете використовувати calendar.timegmдля перетворення свого часу на секунди з епохи Unix та time.localtimeдля перетворення назад:

import calendar
import time

time_tuple = time.strptime("2011-01-21 02:37:21", "%Y-%m-%d %H:%M:%S")
t = calendar.timegm(time_tuple)

print time.ctime(t)

Дає Fri Jan 21 05:37:21 2011(бо я в часовому поясі UTC + 03: 00).


1
import datetime

def utc_str_to_local_str(utc_str: str, utc_format: str, local_format: str):
    """
    :param utc_str: UTC time string
    :param utc_format: format of UTC time string
    :param local_format: format of local time string
    :return: local time string
    """
    temp1 = datetime.datetime.strptime(utc_str, utc_format)
    temp2 = temp1.replace(tzinfo=datetime.timezone.utc)
    local_time = temp2.astimezone()
    return local_time.strftime(local_format)

utc = '2018-10-17T00:00:00.111Z'
utc_fmt = '%Y-%m-%dT%H:%M:%S.%fZ'
local_fmt = '%Y-%m-%dT%H:%M:%S+08:00'
local_string = utc_str_to_local_str(utc, utc_fmt, local_fmt)
print(local_string)   # 2018-10-17T08:00:00+08:00

наприклад, мій часовий пояс " +08: 00 ". input utc = 2018-10-17T00: 00: 00.111Z , тоді я отримаю вихід = 2018-10-17T08: 00: 00 + 08: 00


1
Ви повинні дати кращу характеристику своєї проблеми у простому тексті, де ви поясните решті користувачів, чого ви намагаєтесь досягти і яка ваша проблема
m33n

1

З відповіді тут ви можете використовувати модуль часу для перетворення з utc в локальний час, встановлений на вашому комп’ютері:

utc_time = time.strptime("2018-12-13T10:32:00.000", "%Y-%m-%dT%H:%M:%S.%f")
utc_seconds = calendar.timegm(utc_time)
local_time = time.localtime(utc_seconds)

0

Ось швидка та брудна версія, яка використовує параметри локальної системи для розробки різниці у часі. ПРИМІТКА. Це не спрацює, якщо вам потрібно перетворити на часовий пояс, у якому не працює ваша поточна система. Я перевірив це за допомогою налаштувань Великобританії за часовим поясом BST

from datetime import datetime
def ConvertP4DateTimeToLocal(timestampValue):
   assert isinstance(timestampValue, int)

   # get the UTC time from the timestamp integer value.
   d = datetime.utcfromtimestamp( timestampValue )

   # calculate time difference from utcnow and the local system time reported by OS
   offset = datetime.now() - datetime.utcnow()

   # Add offset to UTC time and return it
   return d + offset

це має бути datetime.fromtimestamp(ts): posix timetamp (float секунди) -> об'єкт datetime за місцевим часом (він працює добре, якщо ОС запам'ятовує минулі компенсації utc для локального часового поясу, тобто в Unix, але не в Windows для дат у минулому)). В іншому випадку можна використовувати піц.
jfs

Я можу запропонувати зробити наступне: у offset = datetime.utcnow().replace(minute=0, second=0, microsecond=0) - datetime.now().replace(minute=0, second=0, microsecond=0) мене були дивні мікросекундні відмінності без цього.
Ерік ван Оостен

@ErikvanOosten: ви спробували datetime.fromtimestamp(ts)замість відповіді?
jfs

0

Закріплення відповіді від франків у зручний спосіб.

import calendar
import datetime

def to_local_datetime(utc_dt):
    """
    convert from utc datetime to a locally aware datetime according to the host timezone

    :param utc_dt: utc datetime
    :return: local timezone datetime
    """
    return datetime.datetime.fromtimestamp(calendar.timegm(utc_dt.timetuple()))
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.