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

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

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

datetime.utcfromtimestamp(timestamp)

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

Данные хранятся в BigTable appengine, и при извлечении они отображаются в виде строки, например:

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

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

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


person MattoTodd    schedule 22.01.2011    source источник
comment
В этом ответе показано, как решить эту проблему простым способом.   -  person juan    schedule 06.09.2013


Ответы (14)


Если вы не хотите предоставлять свои собственные 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, чтобы лучше показать метод точки входа.

Edit 3 Включены методы автоматического определения часовых поясов (Yarin)

person Joe Holloway    schedule 23.01.2011
comment
Спасибо что подметил это. Библиотека указывает на этот проект (twinsun.com/tz/tz-link.htm) в качестве источника часовых поясов. Вы знаете, где находится список возможных строк, которые можно передать gettz ()? - person MattoTodd; 23.01.2011
comment
Кроме того, есть еще одна библиотека под названием pytz (pytz.sourceforge.net), которая также предоставляет базу данных tz. Я лично предпочитаю python-dateutil из-за других служебных модулей, которые он предоставляет, но если вы не видите, что используете эти модули, pytz тоже работает. - person Joe Holloway; 23.01.2011
comment
Похоже, python-dateutil будет работать нормально. Отличное решение. - person MattoTodd; 23.01.2011
comment
попробовал ваш код, но на самом деле он не работает, from_zone и to_zone являются NoneTypes, поэтому utc.aztimezone (to_zone), который, как я полагаю, должен быть 'astimezone', возвращает 'TypeError: аргумент astimezone () 1 должен быть datetime.tzinfo, а не None' - person aschmid00; 31.05.2011
comment
Если from_zone и to_zone равны None, то в вашей системе может быть проблема с базой данных zoneinfo. Какую ОС / дистрибутив вы используете? Спасибо, что указали на опечатку, я исправил. - person Joe Holloway; 31.05.2011
comment
@ Джо-Холлоуэй: У меня тоже None ошибка, что и у @ aschmid00. Я использую последнюю сборку Snow Leopard 10.6.7. Есть ли способ проверить, какие tz есть в моей базе данных zoneinfo? - person Ben Kreeger; 03.06.2011
comment
В моем предыдущем комментарии я смог импортировать dateutil.tz и использовать tz.tzutc() и tz.tzlocal() в качестве объектов часового пояса, которые я искал. Похоже, что база данных часовых поясов в моей системе в порядке (я проверил /usr/share/zoneinfo). Не уверен, что случилось. - person Ben Kreeger; 03.06.2011
comment
@Benjamin Вы на правильном пути. Модуль tz - это правильная точка входа для использования в этой библиотеке. Я обновил свой ответ, чтобы отразить это. Модуль dateutil.zoneinfo, который я показывал ранее, используется внутри модуля tz как запасной вариант, если он не может найти системную базу данных zoneinfo. Если вы заглянете в библиотеку, вы увидите, что в пакете есть tarball-архив базы данных zoneinfo, который он использует, если не может найти БД вашей системы. Мой старый пример пытался напрямую попасть в эту БД, и я предполагаю, что у вас возникли проблемы с загрузкой этой частной БД (почему-то не на пути Python?) - person Joe Holloway; 03.06.2011
comment
comment
@ J.F.Sebastian, это исправлено в # 225. - person Rohmer; 07.04.2016
comment
@Rohmer, проблема с github закрыта, но я не знаю, прошел ли исходный тест из # 112. Вы пробовали это с обновленной dateutil версией? - person jfs; 07.04.2016
comment
@ J.F.Sebastian Хм, я только что посмотрел на последний комментарий в # 112, в котором говорится, что он исправлен, извиняюсь, если это было преждевременно. Позвольте мне проверить. - person Rohmer; 07.04.2016
comment
@ J.F.Sebastian Я получаю правильные данные tz для PDT прямо сейчас - person Rohmer; 07.04.2016
comment
@Rohmer: ты прав. Тест проходит на главной ветке dateutil. Хотя изменение еще не выпущено (на текущей версии тест не пройден: python-dateutil-2.5.2). - person jfs; 07.04.2016
comment
Есть ли проблема с простым добавлением фиксированного количества времени для преобразования времени в другой часовой пояс? - person briankip; 21.04.2016
comment
@briankip Это зависит от того, насколько сложным должно быть ваше приложение, но в общих приложениях да, это проблема. Прежде всего, в зависимости от того, какое сейчас время года, количество часов для добавления / вычитания может измениться, поскольку одни часовые пояса учитывают летнее время, а другие - нет. Во-вторых, правила часовых поясов произвольно меняются с течением времени, поэтому, если вы работаете с прошлой датой / временем, вам нужно применить правила, которые были действительны в этот момент времени, а не в настоящее время. Вот почему лучше всего использовать API, который за кулисами использует базу данных Olson TZ. - person Joe Holloway; 22.04.2016
comment
Итак, учитывает ли он DST или нет? Я немного запуталась. Я EST (обычно GMT-5), но с DST сейчас GMT-4. Если у меня есть дата в прошлом, которая соответствует GMT-5, но другая дата - сегодня (то есть GMT-4), я полагаю, этот ответ не будет учитывать ее и использовать GMT-4 для обеих дат, верно? Будет ли для этого простое решение, поскольку летнее время не начинается и не заканчивается в одну и ту же дату каждый год? - person dan; 19.07.2017
comment
@dnLL Да, будет, если вы правильно используете UTC вместо локализованного datetime. UTC не зависит от DST, поэтому дата и время UTC не меняется при изменении правил DST в определенном часовом поясе. Это чистый момент времени. Когда вы выполняете локализацию из UTC в конкретный часовой пояс, вам нужно знать правила DST, и эти правила закодированы в базе данных tz, которую используют различные библиотеки. База данных tz обновляется по мере того, как геополитические факторы влияют на летнее время. Отсюда следует, что важно поддерживать базу данных tz в актуальном состоянии (регулярно обновлять библиотеки, системные пакеты и т. Д.). - person Joe Holloway; 19.07.2017
comment
Итак, если у меня есть время UTC в феврале и другое в июле, и я применяю tz.tzlocal() к обоим, все будет умным достаточно, чтобы заставить его работать? Вы говорите, что UTC не меняется с DST, поэтому, если между двумя датами, скажем, ровно 6 месяцев (около 4320 часов), когда я применяю tz.tzlocal(), все равно будет 4320 часов между обеими датами, даже если бы я считал час за часом это могло быть 4321, потому что мы пропустили час на летнее время? Если так работает, то июльская дата, вероятно, неправильная (GMT-5 вместо GMT-4 с летним временем). Извините, если это сбивает с толку, просто пытаюсь понять здесь. - person dan; 19.07.2017
comment
Я имею в виду, мы буквально пропускаем час в марте и возвращаем его в ноябре IIRC с DST. Это должно быть где-то. - person dan; 19.07.2017
comment
Да, я понимаю, как работает DST. Ваша проблема в том, что вы думаете по местному времени, а не по всемирному координированному времени. Вам нужно думать о времени в формате UTC и рассматривать свой часовой пояс как локализованное время, полученное из него (на основе правил tz, которые действуют / действовали в данное время в формате UTC). Вы можете перейти от UTC к локализованному времени без двусмысленности, но вы не всегда можете перейти от локализованного времени к UTC без двусмысленности. Когда летнее время возвращается на час, у вас есть целое часовое окно времени, которое повторяется по местному времени, но не по всемирному координированному времени. В UTC сохраняется прямое время. Вот почему рекомендация ... - person Joe Holloway; 19.07.2017
comment
Обычно используется для хранения временных меток в формате UTC в любое время, когда вам нужно преобразовать часовые пояса (или просто делать это исключительно). В определенных ситуациях вам также может потребоваться знать, в каком часовом поясе был захвачен фрагмент данных, поэтому либо сохраните часовой пояс, либо локализованное время рядом с ним, но наличие метки времени UTC гарантирует, что у вас есть момент времени, который не является двусмысленным по отношению к местонахождение. - person Joe Holloway; 19.07.2017
comment
Армин хорошо объясняет вещи в этом сообщении в блоге: lucumr.pocoo .org / 2011/7/15 / eppur-si-muove - person Joe Holloway; 19.07.2017
comment
для местного часового пояса datetime.now().astimezone().tzinfo, tz.tzlocal() вызывает сбой для меня - person larsaars; 28.01.2021

Вот устойчивый метод, не зависящий от каких-либо внешних библиотек:

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 входного datetime. См. Комментарии к этому ответу для другого решения.

Чтобы обойти разные времена, возьмите время эпохи из прошедшего времени. Вот что я делаю:

def utc2local(utc):
    epoch = time.mktime(utc.timetuple())
    offset = datetime.fromtimestamp(epoch) - datetime.utcfromtimestamp(epoch)
    return utc + offset
person David Foster    schedule 08.10.2013
comment
Это работает хорошо и не требует ничего, кроме времени и даты и времени. - person Mike_K; 05.11.2013
comment
@Mike_K: но это неверно. Он не поддерживает DST или часовые пояса, которые имели другое смещение utc в прошлом по другим причинам. Полная поддержка без pytz-подобных db невозможна, см. PEP-431. Хотя вы можете написать решение только для stdlib, которое работает в таких случаях в системах, которые уже имеют базу данных исторического часового пояса, например, Linux, OS X, см. мой ответ. - person jfs; 15.12.2013
comment
@ J.F.Sebastian: Вы говорите, что этот код не работает в целом, но также говорите, что он работает в OS X и Linux. Вы имеете в виду, что этот код не работает в Windows? - person David Foster; 16.12.2013
comment
@DavidFoster: ваш код также может дать сбой в Linux и OS X. Разница между вашим и моими решениями только для stdlib заключается в том, что ваше использует текущее смещение, которое может быть отличается от смещения utc в utc_datetime время. - person jfs; 16.12.2013
comment
Хороший звонок. Это очень тонкая разница. Смещение UTC также может меняться со временем. вздох - person David Foster; 18.12.2013
comment
Это именно то, о чем просили. Следует предпочесть решение ИМО. - person dekingsey; 24.12.2020
comment
Он не должен возвращать utc + offset, так как это будет неправильный tzinfo, поэтому вместо этого он должен вернуть это: utc.astimezone(datetime.timezone(offset)) - person paulie4; 29.04.2021

См. Документацию 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
person Mark Tolonen    schedule 22.01.2011
comment
Спасибо за согласие! Я немного обновил его, чтобы перевести ваш конкретный пример времени. Анализ времени создает то, что в документации называют наивным временем. Используйте replace, чтобы установить часовой пояс на GMT и сделать его с учетом часового пояса, затем используйте astimezone для преобразования в другой часовой пояс. - person Mark Tolonen; 23.01.2011
comment
Извините за обмен на ответ Джо. Технически ваш ответ объясняет, как именно это сделать с необработанным питоном (что полезно знать), но предложенная им библиотека помогает мне найти решение намного быстрее. - person MattoTodd; 23.01.2011
comment
Не похоже, что он автоматически переходит на летнее время. - person Gringo Suave; 22.03.2012
comment
@GringoSuave: этого не было. Правила для этого сложны и часто меняются. - person Mark Tolonen; 23.03.2012

Если вы хотите получить правильный результат даже для времени, которое соответствует неоднозначному местному времени (например, во время перехода на летнее время) и / или локальное смещение 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)
person jfs    schedule 02.10.2015

Этот ответ должен быть полезен, если вы не хотите использовать какие-либо другие модули, кроме 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.

person Brōtsyorfuzthrāx    schedule 21.09.2017

Если вы используете 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>)
person frmdstryr    schedule 04.08.2014

Вы можете использовать стрелку

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 и т. д.

person d21d3q    schedule 04.04.2018

Объединение ответа из franksands в удобный метод.

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()))
person Martlark    schedule 21.02.2020

Вы можете использовать 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).

person myaut    schedule 08.06.2017

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

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

person Matt Billenstein    schedule 23.01.2011
comment
Это хорошо работает во многих случаях, когда вы просто локализируете для отображения. Однако иногда вам нужно выполнить некоторую бизнес-логику на основе часового пояса пользователя, и вы захотите иметь возможность выполнить преобразование уровня сервера приложений. - person Joe Holloway; 23.01.2011

Из ответа здесь вы можете использовать модуль времени для преобразования из 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)
person franksands    schedule 13.12.2018

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_tz_example_str = '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'

# call my function here
local_tz_str = utc_str_to_local_str(utc_tz_example_str, utc_fmt, local_fmt)
print(local_tz_str)   # 2018-10-17T08:00:00+08:00

Когда я ввожу utc_tz_example_str = 2018-10-17T00: 00: 00.111Z, (UTC +00: 00)
, я получаю local_tz_str = 2018-10-17T08: 00 : 00 + 08: 00 (Мой целевой часовой пояс +08: 00)

параметр utc_format - это формат, определяемый вашим конкретным utc_tz_example_str.
параметр local_fmt - это окончательный желаемый формат.

В моем случае желаемый формат - %Y-%m-%dT%H:%M:%S+08:00 (часовой пояс +08: 00). Вы должны создать желаемый формат.

person Eureka Bing    schedule 17.10.2018
comment
Вы должны лучше описать свою проблему в виде обычного текста, объяснив остальным пользователям, чего вы пытаетесь достичь и в чем заключается ваша проблема. - person m33n; 17.10.2018

В облачной среде для запада США у меня работало следующее:

import datetime
import pytz

#set the timezone
tzInfo = pytz.timezone('America/Los_Angeles')
dt = datetime.datetime.now(tz=tzInfo)
print(dt)
person Jie    schedule 11.05.2021

Вот быстрая и грязная версия, в которой для определения разницы во времени используются настройки локальной системы. ПРИМЕЧАНИЕ. Это не сработает, если вам нужно преобразовать в часовой пояс, в котором ваша текущая система не работает. Я тестировал это с настройками Великобритании в часовом поясе 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
person DelboyJay    schedule 15.04.2013
comment
это должно быть datetime.fromtimestamp(ts): метка времени posix (секунды с плавающей запятой) - ›объект datetime в локальном времени (это хорошо работает, если ОС запоминает прошлые смещения utc для местного часового пояса, то есть в Unix, но не в Windows для дат в прошлом)). В противном случае можно было бы использовать pytz. - person jfs; 03.06.2013
comment
Могу я предложить сделать следующее: offset = datetime.utcnow().replace(minute=0, second=0, microsecond=0) - datetime.now().replace(minute=0, second=0, microsecond=0) Без этого у меня были странные различия в микросекундах. - person Erik van Oosten; 26.09.2013
comment
@ErikvanOosten: вы пробовали datetime.fromtimestamp(ts) вместо ответа? - person jfs; 15.12.2013