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?


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.


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.


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)

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)

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)

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.


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

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


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.


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)

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

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.


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
Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.