Converter string de data e hora UTC em data e hora local

206

Eu nunca tive que converter tempo de e para o UTC. Recentemente, solicitei que meu aplicativo soubesse do fuso horário, e eu tenho me executado em círculos. Muitas informações sobre a conversão da hora local em UTC, que eu achei bastante elementar (talvez eu esteja fazendo isso errado também), mas não consigo encontrar nenhuma informação sobre como converter facilmente a hora UTC no fuso horário dos usuários finais.

Em poucas palavras, e o aplicativo Android me envia dados (aplicativo de mecanismo) e dentro desses dados há um carimbo de data / hora. Para armazenar esse timestamp no utc time, estou usando:

datetime.utcfromtimestamp(timestamp)

Isso parece estar funcionando. Quando meu aplicativo armazena os dados, ele está sendo armazenado com 5 horas de antecedência (estou EST -5)

Os dados estão sendo armazenados na BigTable do aplicativo e, quando recuperados, saem como uma string assim:

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

Como faço para converter essa string em um DateTime no fuso horário do usuário?

Além disso, qual é o armazenamento recomendado para as informações de fuso horário dos usuários? (Como você normalmente armazena informações tz, ou seja: "-5: 00" ou "EST" etc etc?) Tenho certeza de que a resposta à minha primeira pergunta pode conter um parâmetro que as respostas da segunda.

MattoTodd
fonte

Respostas:

402

Se você não deseja fornecer seus próprios tzinfoobjetos, consulte a biblioteca python-dateutil . Ele fornece tzinfoimplementações sobre um banco de dados zoneinfo (Olson) , para que você possa consultar as regras de fuso horário por um nome um tanto canônico.

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)

Editar exemplo expandido para mostrar o strptimeuso

Editar 2 Uso fixo da API para mostrar um melhor método de ponto de entrada

Editar 3 Métodos de detecção automática incluídos para fusos horários (Yarin)

Joe Holloway
fonte
1
No meu comentário anterior, consegui importar dateutil.tze usar tz.tzutc()e tz.tzlocal()como objetos de fuso horário que estava procurando. Parece que o banco de dados de fuso horário no meu sistema está bom (fiz o check-in /usr/share/zoneinfo). Não tenho certeza do que estava acontecendo.
precisa
1
@ Benjamin Você está no caminho certo. O tzmódulo é o ponto de entrada correto a ser usado para esta biblioteca. Atualizei minha resposta para refletir isso. O dateutil.zoneinfomódulo que eu estava mostrando anteriormente é usado internamente pelo tzmódulo como um retorno, se não conseguir localizar o zoneinfo DB do sistema. Se você olhar dentro da biblioteca, verá que há um tarball zoneinfo DB no pacote que ele usa se não conseguir encontrar o DB do sistema. Meu velho exemplo estava tentando bater esse DB diretamente e eu estou supondo que você estava tendo problemas de carregamento que DB privada (não está no caminho python de alguma forma?)
Joe Holloway
1
@JFSebastian isso foi corrigido no # 225
Rohmer
2
@briankip Depende da sofisticação da sua aplicação, mas em aplicações gerais, sim, é um problema. Antes de tudo, dependendo da época do ano, o número de horas a adicionar / subtrair pode mudar, pois alguns fusos horários respeitam o horário de verão e outros não. Em segundo lugar, as regras de fuso horário são arbitrariamente alteradas ao longo do tempo. Portanto, se você estiver trabalhando com uma data / hora do passado, precisará aplicar as regras que eram válidas naquele momento, e não no presente. É por isso que é melhor usar uma API que aproveite o banco de dados Olson TZ nos bastidores.
21816 Joe Holloway
45

Aqui está um método resiliente que não depende de nenhuma biblioteca externa:

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

Isso evita os problemas de tempo no exemplo de DelboyJay. E as questões de menor tempo na emenda de Erik van Oosten.

Como uma nota de rodapé interessante, o deslocamento do fuso horário calculado acima pode diferir da seguinte expressão aparentemente equivalente, provavelmente devido a alterações nas regras de horário de verão:

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

Atualização: este trecho tem a fraqueza de usar o deslocamento UTC da hora atual, que pode diferir do deslocamento UTC da data e hora de entrada. Veja os comentários nesta resposta para outra solução.

Para contornar os diferentes momentos, agarre o tempo da época do tempo passado. Aqui está o que eu faço:

def utc2local (utc):
    epoch = time.mktime(utc.timetuple())
    offset = datetime.fromtimestamp (epoch) - datetime.utcfromtimestamp (epoch)
    return utc + offset
David Foster
fonte
Isso funciona bem e requer nada além de hora e data e hora.
11553 Mike_K em 04/11
6
@ Mike_K: mas está incorreto. Ele não suporta DST ou fusos horários que tiveram deslocamento de utc diferente no passado por outros motivos. Suporte total sem pytzdb-like é impossível, consulte PEP-431. Embora você possa escrever uma solução stdlib-only que funcione nesses casos em sistemas que já possuem fuso horário histórico db, por exemplo, Linux, OS X, veja minha resposta .
JFS
@JFSebastian: Você diz que esse código não funciona em geral, mas também diz que funciona no OS X e Linux. Você quer dizer que esse código não funciona no Windows?
David Foster
2
@ DavidFoster: seu código também pode falhar no Linux e no OS X. A diferença entre as suas soluções somente stdlib e a minha é que a sua usa o deslocamento atual que pode ser diferente do deslocamento utc no utc_datetimemomento.
JFS
1
Boa decisão. Essa é uma diferença muito sutil. O deslocamento UTC também pode mudar com o tempo. sigh
David Foster
23

Consulte a documentação de data e hora em objetos tzinfo . Você precisa implementar os fusos horários que deseja se sustentar. Existem exemplos na parte inferior da documentação.

Aqui está um exemplo simples:

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)

Resultado

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
Mark Tolonen
fonte
Obrigado pela aceitação! Atualizei-o um pouco para traduzir seu horário de exemplo específico. A análise do tempo cria o que os documentos chamam de tempo "ingênuo". Use replacepara definir o fuso horário como GMT e torná-lo "consciente", depois use astimezonepara converter para outro fuso horário.
precisa saber é o seguinte
Desculpe por trocar a resposta de Joe. Tecnicamente, sua resposta explica exatamente como fazê-lo com o python bruto (o que é bom saber), mas a biblioteca que ele sugeriu me leva a uma solução muito mais rápida.
precisa saber é o seguinte
4
Não parece que ele lida automaticamente com o horário de verão.
Gringo Suave
@GringoSuave: não foi feito para isso. As regras para isso são complicadas e mudam frequentemente.
precisa
19

Se você deseja obter o resultado correto mesmo para o horário que corresponde a um horário local ambíguo (por exemplo, durante uma transição de horário de verão) e / ou o deslocamento local do utc é diferente em horários diferentes no seu fuso horário local, use pytzfusos horários:

#!/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
fonte
10

Esta resposta deve ser útil se você não quiser usar outros módulos além disso datetime.

datetime.utcfromtimestamp(timestamp)retorna um datetimeobjeto ingênuo (não consciente). Os conscientes têm consciência do fuso horário e os ingênuos não. Você quer um conhecimento se quiser converter entre fusos horários (por exemplo, entre o UTC e o horário local).

Se você não instancia a data para começar, mas ainda pode criar um datetimeobjeto ingênuo no horário UTC, tente este código Python 3.x para convertê-lo:

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

Cuidado para não assumir por engano que, se o fuso horário estiver no MDT, o horário de verão não funcionará com o código acima, pois ele imprime o MST. Você notará que, se alterar o mês para agosto, o MDT será impresso.

Outra maneira fácil de obter um datetimeobjeto consciente (também no Python 3.x) é criá-lo com um fuso horário especificado para começar. Aqui está um exemplo, usando o 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))

Se você usa o Python 2.x, provavelmente precisará subclassificar datetime.tzinfoe usá-lo para ajudá-lo a criar um datetimeobjeto consciente , uma vez datetime.timezoneque não existe no Python 2.x.

Brōtsyorfuzthrāx
fonte
8

Se você estiver usando o Django, você pode usar o timezone.localtimemétodo:

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
fonte
2

Você pode usar a seta

from datetime import datetime
import arrow

now = datetime.utcnow()

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

você pode alimentar arrow.get()com qualquer coisa. timestamp, iso string etc

d21d3q
fonte
1

Tradicionalmente, adio isso para o front-end - envio os horários do back-end como carimbos de data / hora ou algum outro formato de data e hora no UTC, em seguida deixo o cliente descobrir o deslocamento do fuso horário e renderizar esses dados no fuso horário apropriado.

Para um aplicativo da web, isso é muito fácil de fazer em javascript - você pode descobrir o deslocamento do fuso horário do navegador com bastante facilidade usando métodos internos e depois renderizar os dados do back-end corretamente.

Matt Billenstein
fonte
3
Isso funciona bem em muitos casos em que você está simplesmente localizando para exibição. Ocasionalmente, no entanto, você precisará executar alguma lógica comercial com base no fuso horário do usuário e poderá fazer a conversão na camada do servidor de aplicativos.
Joe Holloway
1

Você pode usar calendar.timegmpara converter seu tempo em segundos desde a época do Unix e time.localtimeconverter novamente:

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(porque estou no UTC + 03: 00).

myaut
fonte
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

por exemplo, meu fuso horário é ' +08: 00 '. input utc = 2018-10-17T00: 00: 00.111Z , então receberei saída = 2018-10-17T08: 00: 00 + 08: 00

Eureka Bing
fonte
1
Você deve dar uma melhor descrição do seu problema em texto simples, onde você explica o resto dos usuários o que você está tentando alcançar e que é o seu problema
m33n
1

A partir da resposta aqui , você pode usar o módulo de hora para converter de utc para a hora local definida no seu computador:

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
fonte
0

Aqui está uma versão rápida e suja que usa as configurações do sistema local para calcular a diferença horária. NOTA: Isso não funcionará se você precisar converter para um fuso horário no qual seu sistema atual não está sendo executado. Testei isso com as configurações do Reino Unido no fuso horário do 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
fonte
deve ser datetime.fromtimestamp(ts): posix timestamp (float segundos) -> objeto datetime no horário local (funciona bem se o SO se lembrar de deslocamentos utc passados ​​para o fuso horário local, ou seja, no Unix, mas não no Windows para datas anteriores)). Caso contrário, o pytz poderia ser usado.
JFS
Posso sugerir o seguinte: offset = datetime.utcnow().replace(minute=0, second=0, microsecond=0) - datetime.now().replace(minute=0, second=0, microsecond=0) Eu obtive diferenças estranhas de microssegundos sem ele.
Erik van Oosten 26/09
@ErikvanOosten: você já tentou datetime.fromtimestamp(ts)a resposta?
JFS
0

Consolidar a resposta dos franksands em um método conveniente.

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
fonte