Não é possível subtrair os tempos de deslocamento e ingestão de deslocamento


305

Eu tenho um timestamptzcampo com reconhecimento de fuso horário no PostgreSQL. Quando puxo os dados da tabela, quero subtrair a hora agora para que eu possa obter sua idade.

O problema que estou tendo é que ambos datetime.datetime.now()e datetime.datetime.utcnow()parecem retornar fusos horários desconhecidos, o que resulta na obtenção desse erro:

TypeError: can't subtract offset-naive and offset-aware datetimes 

Existe uma maneira de evitar isso (de preferência sem a utilização de um módulo de terceiros).

EDIT: Obrigado pelas sugestões, no entanto, tentar ajustar o fuso horário parece me dar erros .. por isso, vou usar fusos horários desconhecidos no PG e inserir sempre usando:

NOW() AT TIME ZONE 'UTC'

Dessa forma, todos os meus carimbos de hora são UTC por padrão (mesmo que seja mais irritante fazer isso).

Respostas:


316

você tentou remover a conscientização do fuso horário?

de http://pytz.sourceforge.net/

naive = dt.replace(tzinfo=None)

pode ser necessário adicionar também a conversão de fuso horário.

edit: Esteja ciente da idade desta resposta. Uma resposta do Python 3 está abaixo.


32
Esta parece ser a única maneira de fazê-lo. Parece bastante esfarrapada que do Python tem esse apoio de baixa qualidade para fusos horários que ele precisa de um módulo de terceiros para trabalhar com data e hora corretamente ..
Ian

33
(Apenas para constar) Na verdade, adicionar informações sobre o fuso horário pode ser uma ideia melhor: stackoverflow.com/a/4530166/548696
Tadeck

7
objetos de data e hora ingênuos são inerentemente ambíguos e, portanto, devem ser evitados. É fácil adicionar o tzinfo
jfs

1
@Kylotan: UTC é um fuso horário neste contexto (como representado pela classe tzinfo). Olhe para datetime.timezone.utcou pytz.utc. Por exemplo, 1970-01-01 00:00:00é ambígua e você tem que adicionar um fuso horário para disambiguate: 1970-01-01 00:00:00 UTC. Você vê, você precisa adicionar novas informações; o registro de data e hora por si só é ambíguo.
JFS

1
@JFSebastian: O problema é que utcnownão deve retornar um objeto ingênuo ou um carimbo de data / hora sem um fuso horário. Nos documentos, "Um objeto consciente é usado para representar um momento específico que não está aberto à interpretação". Qualquer hora no UTC atende a esse critério, por definição.
Kylotan

214

A solução correta é adicionar as informações do fuso horário, por exemplo, para obter o horário atual como um objeto de data e hora ciente no Python 3:

from datetime import datetime, timezone

now = datetime.now(timezone.utc)

Nas versões mais antigas do Python, você mesmo pode definir o utcobjeto tzinfo (exemplo dos documentos datetime):

from datetime import tzinfo, timedelta, datetime

ZERO = timedelta(0)

class UTC(tzinfo):
  def utcoffset(self, dt):
    return ZERO
  def tzname(self, dt):
    return "UTC"
  def dst(self, dt):
    return ZERO

utc = UTC()

então:

now = datetime.now(utc)

10
Melhor do que remover o tz, como a resposta aceita defende o IMHO.
Shautieh

3
Aqui está uma lista de fusos horários do python: stackoverflow.com/questions/13866926/…
comfytoday

61

Eu sei que algumas pessoas usam o Django especificamente como uma interface para abstrair esse tipo de interação com o banco de dados. O Django fornece utilitários que podem ser usados ​​para isso:

from django.utils import timezone
now_aware = timezone.now()

Você precisa configurar uma infra-estrutura básica de configurações do Django, mesmo se você estiver apenas usando esse tipo de interface (nas configurações, você precisa incluir USE_TZ=Truepara obter uma data e hora conscientes).

Por si só, isso provavelmente não está nem perto o suficiente para motivar você a usar o Django como uma interface, mas existem muitas outras vantagens. Por outro lado, se você tropeçou aqui porque estava manipulando seu aplicativo Django (como eu fiz), talvez isso ajude ...


1
você precisa USE_TZ=Trueobter uma data e hora atenta aqui.
JFS

2
Sim - esqueci de mencionar que você precisa configurar o settings.py como o JFSebastian descreve (acho que essa foi uma instância do 'set and forget').
sage

E isso também pode ser facilmente convertido para outros fusos horários, como o + timedelta(hours=5, minutes=30)IST
ABcDexter

25

Esta é uma solução muito simples e clara
Duas linhas de código

# First we obtain de timezone info o some datatime variable    

tz_info = your_timezone_aware_variable.tzinfo

# Now we can subtract two variables using the same time zone info
# For instance
# Lets obtain the Now() datetime but for the tz_info we got before

diff = datetime.datetime.now(tz_info)-your_timezone_aware_variable

Conclusão: você deve gerenciar suas variáveis ​​datetime com as mesmas informações de tempo


Incorreta? O código que escrevi foi testado e estou usando-o em um projeto django. É muito, muito claro e simples #
ePi272314

o "incorreto" refere-se à última frase da sua resposta: "... deve adicionar ... não o UTC" - o fuso horário UTC funciona aqui e, portanto, a afirmação está incorreta.
JFS

para ser claro, eu quis dizer: diff = datetime.now(timezone.utc) - your_timezone_aware_variablefunciona (e a (a - b)fórmula acima é a explicação por que (a - b)pode funcionar, mesmo que a.tzinfonão seja b.tzinfo).
JFS

6

O módulo psycopg2 possui suas próprias definições de fuso horário, então acabei escrevendo meu próprio wrapper em torno do utcnow:

def pg_utcnow():
    import psycopg2
    return datetime.utcnow().replace(
        tzinfo=psycopg2.tz.FixedOffsetTimezone(offset=0, name=None))

e use pg_utcnowsempre que precisar do tempo atual para comparar com um PostgreSQLtimestamptz


Qualquer objeto tzinfo que retorne zero deslocamento de utc fará, por exemplo .
jfs

6

Eu também enfrentei o mesmo problema. Então eu encontrei uma solução depois de muita pesquisa.

O problema era que, quando obtemos o objeto datetime do modelo ou formulário, ele é compensado e, se obtemos o tempo pelo sistema, ele é ingênuo .

Então, o que fiz foi obter o horário atual usando timezone.now () e importar o fuso horário de django.utils import fuso horário e colocar o USE_TZ = True no arquivo de configurações do seu projeto.


2

Eu vim com uma solução ultra-simples:

import datetime

def calcEpochSec(dt):
    epochZero = datetime.datetime(1970,1,1,tzinfo = dt.tzinfo)
    return (dt - epochZero).total_seconds()

Ele funciona com valores de data e hora com reconhecimento de fuso horário e ingênuos. E não são necessárias bibliotecas ou soluções alternativas adicionais para o banco de dados.


1

Eu descobri que timezone.make_aware(datetime.datetime.now())é útil no django (eu estou no 1.9.1). Infelizmente, você não pode simplesmente tornar um datetimeobjeto sensível ao deslocamento, então timetz()ele. Você tem que fazer datetimee fazer comparações com base nisso.


1

Existe alguma razão premente para você não poder lidar com o cálculo de idade no próprio PostgreSQL? Algo como

select *, age(timeStampField) as timeStampAge from myTable

2
Sim, existe .. mas eu estava perguntando principalmente porque quero evitar fazer todos os cálculos no postgre.
Ian

0

Sei que isso é antigo, mas pensei em adicionar minha solução caso alguém ache útil.

Eu queria comparar o datetime ingênuo local com um datetime consciente de um servidor de horas. Basicamente, criei um novo objeto datetime ingênuo usando o objeto datetime consciente. É um pouco complicado e não parece muito bonito, mas faz o trabalho.

import ntplib
import datetime
from datetime import timezone

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

try:
    ntpt = ntplib.NTPClient()
    response = ntpt.request('pool.ntp.org')
    date = utc_to_local(datetime.datetime.utcfromtimestamp(response.tx_time))
    sysdate = datetime.datetime.now()

... aí vem o doce ...

    temp_date = datetime.datetime(int(str(date)[:4]),int(str(date)[5:7]),int(str(date)[8:10]),int(str(date)[11:13]),int(str(date)[14:16]),int(str(date)[17:19]))
    dt_delta = temp_date-sysdate
except Exception:
    print('Something went wrong :-(')

FYI, utc_to_local()a partir de minha resposta retorna hora local como uma consciência objeto datetime (É Python 3.3+ código)
jfs

Não está claro o que seu código está tentando fazer. Você pode substituí-lo por delta = response.tx_time - time.time().
jfs
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.