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

207

Мне никогда не приходилось переводить время в и из UTC. Недавно у меня был запрос, чтобы мое приложение знало часовой пояс, и я бегал сам по кругу. Много информации о преобразовании местного времени в UTC, которое я нашел довольно элементарным (возможно, я тоже делаю это неправильно), но я не могу найти никакой информации о том, как легко преобразовать время UTC в часовой пояс конечных пользователей.

В двух словах, android app отправляет мне (appengine app) данные, и в этих данных есть временная метка. Чтобы сохранить эту метку времени в UTC время, которое я использую:

datetime.utcfromtimestamp(timestamp)

Кажется, это работает. Когда мое приложение хранит данные, оно сохраняется как 5 часов вперед (я EST -5)

Данные хранятся в BigTable приложения, и при получении они выглядят как строка:

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

Как мне преобразовать эту строку в DateTime в часовом поясе пользователя?

Кроме того, каково рекомендуемое хранилище для информации о часовом поясе пользователей? (Как вы обычно храните информацию tz, например: «-5: 00» или «EST» и т. Д.?) Я уверен, что ответ на мой первый вопрос может содержать параметр, а ответы - второй.

MattoTodd
источник
Этот ответ показывает, как решить эту проблему простым способом.
хуан

Ответы:

403

Если вы не хотите предоставлять свои собственные 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
@ Бенджамин Вы на правильном пути. tzМодуль является правильной точкой входа использовать для этой библиотеки. Я обновил свой ответ, чтобы отразить это. dateutil.zoneinfoМодуль я показывал ранее внутренне используется в tzмодуле , как падение назад , если он не может найти системы ZoneInfo БД. Если вы загляните внутрь библиотеки, то увидите, что в пакете используется архив базы данных zoneinfo DB, если он не может найти базу данных вашей системы. Мой старый пример пытался поразить эту БД напрямую, и я предполагаю, что у вас возникли проблемы с загрузкой этой частной БД (не так ли по пути python?)
Джо Холлоуэй,
1
@JFSebastian это исправлено в # 225
Rohmer
2
@briankip Это зависит от того, насколько сложным должно быть ваше приложение, но в общем случае да, это проблема. Прежде всего, в зависимости от того, какое сейчас время года, количество добавляемых / вычитаемых часов может меняться, поскольку некоторые часовые пояса учитывают летнее время, а другие - нет. Во-вторых, правила часового пояса произвольно меняются со временем, поэтому, если вы работаете с прошедшей датой / временем, вам нужно применять правила, которые действовали в тот момент времени, а не в настоящее время. Вот почему лучше всего использовать API, который использует базу данных Olson TZ за кулисами.
Джо Холлоуэй
45

Вот гибкий метод, который не зависит от каких-либо внешних библиотек:

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 или часовые пояса, которые имели другое смещение utc в прошлом по другим причинам. Полная поддержка без pytzd-like db невозможна, см. PEP-431. Хотя вы можете написать решение только для stdlib, которое работает в таких случаях на системах, в которых уже есть исторический часовой пояс, например, 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для преобразования в другой часовой пояс.
Марк Толонен
Извините, что обменялись ответами Джо. Технически ваш ответ объясняет, как именно это сделать с сырым python (что полезно знать), но библиотека, которую он предложил, помогает мне быстрее найти решение.
MattoTodd
4
Не похоже, что он автоматически обрабатывает летнее время.
Gringo Suave
@GringoSuave: это не было предназначено. Правила для этого сложны и часто меняются.
Марк Толонен
19

Если вы хотите получить правильный результат даже для времени, которое соответствует неоднозначному местному времени (например, во время перехода на летнее время) и / или местное смещение utc отличается в разное время в вашем местном часовом поясе, тогда используйте 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)
JFS
источник
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объекта, поскольку в Python 2.x datetime.timezoneего не существует.

Brōtsyorfuzthrā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>)
frmdstryr
источник
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()чем угодно. отметка времени, строка iso и т. д.

d21d3q
источник
1

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

Для веб-приложения это довольно легко сделать в 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).

myaut
источник
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)
franksands
источник
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
DelboyJay
источник
это должно быть datetime.fromtimestamp(ts): posix timestamp (с плавающей запятой) -> объект datetime по местному времени (он хорошо работает, если ОС запоминает прошлые смещения utc для местного часового пояса, т. е. в Unix, но не в Windows для дат в прошлом). В противном случае можно использовать pytz.
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()))
Martlark
источник