Converter um datetime UTC do python em um datetime local usando apenas a biblioteca padrão do python?

189

Eu tenho uma instância de data e hora python que foi criada usando datetime.utcnow () e persistiu no banco de dados.

Para exibição, eu gostaria de converter a instância datetime recuperada do banco de dados em datetime local usando o fuso horário local padrão (ou seja, como se o datetime fosse criado usando datetime.now ()).

Como converter o horário e hora UTC em um horário local usando apenas a biblioteca padrão python (por exemplo, nenhuma dependência pytz)?

Parece que uma solução seria usar datetime.astimezone (tz), mas como você obteria o fuso horário local padrão?

Nitro Zark
fonte
Em qual formato o tempo persistiu no banco de dados? Se for um formato padrão, pode ser que você não precise fazer nenhuma conversão.
Apalala 31/12
1
Esta resposta mostra uma maneira simples de usar pytz .
juan

Respostas:

275

No Python 3.3 ou superior:

from datetime import datetime, timezone

def utc_to_local(utc_dt):
    return utc_dt.replace(tzinfo=timezone.utc).astimezone(tz=None)

No 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)

Usando pytz(ambos 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

Exemplo

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()))

Resultado

Python 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
Python 2
2010-06-06 21:29:07.730000 
2010-12-06 20:29:07.730000 
2012-11-08 14:19:50.093911 
pytz
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

Nota: leva em consideração o horário de verão e a alteração recente do deslocamento utc para o fuso horário do MSK.

Não sei se as soluções não-pytz funcionam no Windows.

jfs
fonte
1
o que 'normalizar' faz?
avi
4
@avi: em geral: pytz: Por que a normalização é necessária na conversão entre fusos horários? . Nota: Não é necessário com a implementação atual de .normalize()porque o fuso horário de origem .astimezone()é UTC.
JFS
1
Recebo TypeError: replace() takes no keyword argumentsao tentar a solução pytz.
28718 Craig
@ Craig o código funciona como está, como mostra a saída na resposta. Crie um exemplo de código mínimo completo que leve ao TypeError, mencione suas versões Python, pytz que você usa e publique-o como uma pergunta separada do Stack Overflow.
precisa
Dica rápida para a solução Python 3.3+. Para ir na direção oposta (nativo para UTC), isso funciona: local_dt.replace(tzinfo=None).astimezone(tz=timezone.utc)
jasonrhaas
50

Você não pode fazer isso apenas com a biblioteca padrão, pois a biblioteca padrão não possui fusos horários. Você precisa de pytz ou 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())

Ou então, você pode fazer isso sem pytz ou dateutil implementando seus próprios fusos horários. Mas isso seria bobagem.

Lennart Regebro
fonte
Obrigado, vou manter isso em mãos se precisar de uma solução completa. O dateutil suporta o Python 3.1 (diz Python 2.3+, mas é suspeito)?
Nitro Zark
O pytz realmente lida melhor com as trocas de horário de verão.
Lennart Regebro
2
Atualização: pytz e dateutil suportam o Python 3 atualmente.
Lennart Regebro 28/10/12
1
@JFSebastian: dateutil não pode distinguir entre as duas 1:30 vezes que acontecem durante uma troca de horário de verão. Se você precisar distinguir essas duas vezes, a solução alternativa é usar pytz, que pode lidar com isso.
Lennart Regebro
8

Você não pode fazer isso com a biblioteca padrão. Usando o módulo pytz , você pode converter qualquer objeto de data / hora ingênuo / consciente para qualquer outro fuso horário. Vamos ver alguns exemplos usando o Python 3.

Objetos ingênuos criados através do método de classe utcnow()

Para converter um objeto ingênuo em qualquer outro fuso horário, primeiro você deve convertê-lo em um objeto de data e hora consciente . Você pode usar o replacemétodo para converter um ingênuo objeto de data e hora em um objeto de data e hora consciente . Em seguida, para converter um objeto datetime com reconhecimento para qualquer outro fuso horário, você pode usar o astimezonemétodo

A variável pytz.all_timezonesfornece a lista de todos os fusos horários disponíveis no módulo 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)

Objetos ingênuos criados através do método de classe now()

Como o nowmétodo retorna a data e a hora atuais, é necessário conscientizar primeiro o fuso horário do objeto datetime. A localize função converte um objeto datetime ingênuo em um objeto datetime com reconhecimento de fuso horário. Em seguida, você pode usar o astimezonemétodo para convertê-lo em outro fuso horário.

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)
N Randhawa
fonte
6

Acho que descobri: calcula o número de segundos desde a época, depois converte para uma timzeone local usando time.localtime e depois converte a estrutura de tempo novamente em um datetime ...

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 )

Aplica o horário de verão / inverno corretamente:

>>> 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)
Nitro Zark
fonte
1
Ugh. Mas isso deve funcionar no Unix, pelo menos. No Windows, pode estar incorreto se as regras de horário de verão tiverem sido alteradas desde a data da conversão. Por que você não quer usar uma biblioteca?
Lennart Regebro
Eu só preciso de conversão utc / local, portanto, arrastar essa biblioteca parece ser um exagero. Como sou o único usuário do aplicativo, posso viver com algumas limitações. Também evita gerenciar os problemas relacionados à dependência (suporte ao Python 3 ?, Windows? Qualidade ...) que seriam mais caros do que trabalhar no meu pequeno script.
Nitro Zark
1
Bem. se você precisar de Python3, estará sem sorte. Mas pesquisar e criar sua própria solução é um exagero comparado ao uso de uma biblioteca.
Lennart Regebro
1
+1 (para compensar o voto negativo: pode ser um requisito válido procurar soluções apenas para stdlib) com a ressalva @Lennart mencionada. Dado que o dateutil pode falhar no Unix e no Windows. btw, você pode extrair utctotimestamp(utc_dt) -> (seconds, microseconds)do seu código para maior clareza, consulte Implementação em Python 2.6-3.x
jfs
1
Um pytz (e dateutil) funciona no Python 3 há algum tempo, então os problemas do Python3 / Windows / Qualidade não são mais um problema.
Lennart Regebro 28/10/12
6

Com base no comentário de Alexei. Isso deve funcionar para o horário de verão também.

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)
John Kerns
fonte
4

A biblioteca padrão do Python não vem com nenhuma tzinfoimplementação. Sempre considerei isso uma falha surpreendente do módulo datetime.

A documentação para a classe tzinfo vem com alguns exemplos úteis. Procure o bloco de código grande no final da seção.

Mark Ransom
fonte
1
Meu palpite sobre a implementação principal é que o banco de dados de fuso horário precisa ser atualizado regularmente, pois alguns países não têm regras fixas para determinar os períodos de horário de verão. Se não me engano, a JVM, por exemplo, precisa ser atualizada para obter o último banco de dados de fuso horário ...
Nitro Zark
@ Nitro Zark, pelo menos eles deveriam ter um para o UTC. O osmódulo poderia ter fornecido um horário local com base nas funções do sistema operacional.
Mark Ransom
1
Eu estava checando a lista de novos recursos do 3.2 e eles adicionaram o fuso horário UTC no 3.2 ( docs.python.org/dev/whatsnew/3.2.html#datetime ). Porém, parece não haver um fuso horário local ...
Nitro Zark
2

O Python 3.9 adiciona o zoneinfomódulo , agora ele pode ser feito da seguinte maneira (apenas 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

A Europa Central está 1 ou 2 horas à frente do UTC, assim local_awareé:

datetime.datetime(2020, 10, 31, 13, 0, tzinfo=backports.zoneinfo.ZoneInfo(key='localtime'))

como str:

2020-10-31 13:00:00+01:00

O Windows não possui um banco de dados de fuso horário do sistema; portanto, aqui é necessário um pacote extra:

pip install tzdata  

Há um backport para permitir o uso no Python 3.6 a 3.8 :

sudo pip install backports.zoneinfo

Então:

from backports.zoneinfo import ZoneInfo
xjcl
fonte
você nem precisa zoneinfoaqui - veja a primeira parte da resposta do jfs (parte do Python3.3 +) ;-)
MrFuppes
@ MrFuppes Eu acho que ZoneInfo('localtime')é muito mais claro do que tz=None(afinal, pode-se esperar tz=Noneremover o fuso horário ou retornar o UTC em vez da hora local).
xjcl 12/07
@MrFuppes Plus, a pergunta pode envolver o OP ter um site e querer mostrar aos usuários os registros de data e hora no horário local. Para isso, você precisaria converter para o fuso horário explícito do usuário, em vez da hora local.
xjcl 12/07
0

Uma maneira simples (mas talvez falha) que funciona no Python 2 e 3:

import time
import datetime

def utc_to_local(dt):
    return dt - datetime.timedelta(seconds = time.timezone)

Sua vantagem é que é trivial escrever uma função inversa

Alexei Averchenko
fonte
1
Isso dá o resultado errado, pois time.timezonenão leva em consideração o horário de verão.
guyarad
0

A maneira mais fácil que encontrei é calcular o horário de onde você está e subtraí-lo da hora.

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

Isso funciona para mim, no Python 3.5.2.

Zld Productions
fonte
0

Aqui está outra maneira de alterar o fuso horário no formato datetime (eu sei que desperdicei minha energia nisso, mas não vi esta página, então não sei como) sem min. e seg. porque eu não preciso dele para o meu projeto:

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)
Convidado
fonte
Use timedelta para alternar entre fusos horários. Tudo o que você precisa é o deslocamento em horas entre os fusos horários. Não é necessário mexer nos limites dos 6 elementos de um objeto de data e hora. A timedelta lida com anos bissextos, séculos bissextos, etc., também, com facilidade. Você deve 'da data e hora da importação timedelta'. Então, se o deslocamento for uma variável em horas: timeout = timein + timedelta (hours = offset), em que timein e timeout são objetos de data e hora. por exemplo, timein + timedelta (horas = -8) converte de GMT para PST.
Karl Rudnick
0

Essa é uma maneira terrível de fazer isso, mas evita a criação de uma definição. Ele cumpre o requisito de ficar com a biblioteca básica do 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])
Arthur D. Howland
fonte
Embora isso seja interessante e eu vou usar esse método, ele não está usando apenas o stdlib. Ele está usando o Pandas para fazer as conversões pandas.pydata.org/pandas-docs/version/0.25/reference/api/…
Tim Hughes
-3

Use timedelta para alternar entre fusos horários. Tudo o que você precisa é o deslocamento em horas entre os fusos horários. Não é necessário mexer nos limites dos 6 elementos de um objeto de data e hora. A timedelta lida com anos bissextos, séculos bissextos, etc., também com facilidade. Você deve primeiro

from datetime import datetime, timedelta

Então, se offseté o delta do fuso horário em horas:

timeout = timein + timedelta(hours = offset)

onde timein e timeout são objetos de data e hora. por exemplo

timein + timedelta(hours = -8)

converte de GMT para PST.

Então, como determinar offset? Aqui está uma função simples, desde que você tenha apenas algumas possibilidades de conversão sem usar objetos de data e hora que estejam "cientes" do fuso horário, o que algumas outras respostas agradam. Um pouco manual, mas às vezes a melhor clareza.

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)

Depois de examinar as numerosas respostas e brincar com o código mais rígido que consigo pensar (por enquanto), parece melhor que todos os aplicativos, nos quais o tempo é importante e os fusos horários mistos devam ser considerados, façam um esforço real para criar todos os objetos de data e hora "consciente". Parece que a resposta mais simples é:

timeout = timein.astimezone(pytz.timezone("GMT"))

para converter para GMT, por exemplo. Obviamente, para converter para / de qualquer outro fuso horário que você desejar, local ou não, basta usar a string de fuso horário apropriada que o pytz entende (de pytz.all_timezones). O horário de verão também é levado em consideração.

Karl Rudnick
fonte
1
Isso não é uma boa ideia. O horário de verão não muda na mesma data para o mundo inteiro. Use funções de data e hora para fazer isso.
Gabe
@ gabe - Entenda que a tabela não é uma boa ideia, embora possa servir para alguém que cobre apenas o fuso horário dos EUA. Como eu disse no final, a melhor maneira é SEMPRE conscientizar os objetos de data e hora para que você possa usar facilmente as funções de data e hora.
Karl Rudnick