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

Ian
fonte

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.

phillc
fonte
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)
jfs
fonte
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 ...

sábio
fonte
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

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

erjiang
fonte
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.

Ashok Joshi
fonte
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.

John L. Stanley
fonte
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.

Joseph Coco
fonte
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
Nic Gibson
fonte
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 :-(')
I_do_python
fonte
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