Tidak dapat mengurangkan data offset-naif dan tidak-sadar

305

Saya memiliki timestamptzbidang sadar zona waktu di PostgreSQL. Ketika saya menarik data dari tabel, saya kemudian ingin mengurangi waktu sekarang sehingga saya bisa mendapatkan usianya.

Masalah yang saya alami adalah keduanya datetime.datetime.now()dan datetime.datetime.utcnow()sepertinya mengembalikan cap waktu yang tidak disadari zona waktu, yang mengakibatkan saya mendapatkan kesalahan ini:

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

Apakah ada cara untuk menghindari ini (sebaiknya tanpa menggunakan modul pihak ketiga).

EDIT: Terima kasih atas sarannya, namun mencoba menyesuaikan zona waktu sepertinya memberi saya kesalahan .. jadi saya hanya akan menggunakan cap waktu yang tidak disadari zona waktu di PG dan selalu menyisipkan menggunakan:

NOW() AT TIME ZONE 'UTC'

Dengan begitu semua stempel waktu saya adalah UTC secara default (meskipun lebih menjengkelkan untuk melakukan ini).

Ian
sumber

Jawaban:

316

Sudahkah Anda mencoba menghilangkan kesadaran zona waktu?

dari http://pytz.sourceforge.net/

naive = dt.replace(tzinfo=None)

mungkin harus menambahkan konversi zona waktu juga.

sunting: Perlu diketahui usia jawaban ini. Jawaban Python 3 ada di bawah ini.

Phillc
sumber
32
Ini sepertinya satu-satunya cara untuk melakukannya. Tampaknya sangat lumpuh bahwa python mendapat dukungan buruk untuk zona waktu sehingga perlu modul pihak ketiga untuk bekerja dengan cap waktu dengan benar ..
Ian
33
(Hanya sebagai catatan) Sebenarnya menambahkan informasi tentang zona waktu mungkin merupakan ide yang lebih baik: stackoverflow.com/a/4530166/548696
Tadeck
7
objek datetime naif secara inheren ambigu dan karenanya mereka harus dihindari. Lebih mudah untuk menambahkan tzinfo sebagai gantinya
jfs
1
@Kylotan: UTC adalah zona waktu dalam konteks ini (sebagaimana diwakili oleh kelas tzinfo). Lihatlah datetime.timezone.utcatau pytz.utc. Sebagai contoh, 1970-01-01 00:00:00adalah ambigu dan Anda harus menambahkan zona waktu untuk disambiguate: 1970-01-01 00:00:00 UTC. Anda lihat, Anda harus menambahkan informasi baru; stempel waktu dengan sendirinya ambigu.
jfs
1
@ JFSebastian: Masalahnya adalah bahwa utcnowseharusnya tidak mengembalikan objek naif atau stempel waktu tanpa zona waktu. Dari dokumen, "Objek sadar digunakan untuk mewakili momen spesifik dalam waktu yang tidak terbuka untuk interpretasi". Kapan saja di UTC memenuhi kriteria ini, menurut definisi.
Kylotan
214

Solusi yang benar adalah dengan menambahkan info zona waktu misalnya, untuk mendapatkan waktu saat ini sebagai objek datetime sadar di Python 3:

from datetime import datetime, timezone

now = datetime.now(timezone.utc)

Pada versi Python yang lebih lama, Anda bisa mendefinisikan utcsendiri objek tzinfo (contoh dari datetime docs):

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

kemudian:

now = datetime.now(utc)
jfs
sumber
10
Lebih baik daripada menghapus tz karena jawaban yang diterima mendukung IMHO.
Shautieh
3
Berikut adalah daftar zona waktu python: stackoverflow.com/questions/13866926/…
comfytoday
61

Saya tahu beberapa orang menggunakan Django khusus sebagai antarmuka untuk abstrak interaksi database jenis ini. Django menyediakan utilitas yang dapat digunakan untuk ini:

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

Anda perlu mengatur infrastruktur pengaturan Django dasar, bahkan jika Anda hanya menggunakan jenis antarmuka ini (dalam pengaturan, Anda harus memasukkan USE_TZ=Trueuntuk mendapatkan datetime sadar).

Dengan sendirinya, ini mungkin tidak cukup dekat untuk memotivasi Anda untuk menggunakan Django sebagai antarmuka, tetapi ada banyak fasilitas lainnya. Di sisi lain, jika Anda tersandung di sini karena Anda membuat aplikasi Django Anda (seperti yang saya lakukan), maka mungkin ini membantu ...

Sage
sumber
1
Anda perlu USE_TZ=True, untuk mendapatkan datetime sadar di sini.
jfs
2
Ya - Saya lupa menyebutkan bahwa Anda perlu mengatur settings.py Anda seperti yang dijelaskan JFSebastian (saya kira ini adalah contoh 'set dan lupakan').
bijak
Dan ini juga dapat dengan mudah dikonversi ke zona waktu lain, seperti + timedelta(hours=5, minutes=30)untuk IST
ABcDexter
25

Ini adalah solusi yang sangat sederhana dan jelas.
Dua baris kode

# 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

Kesimpulan: Anda harus mengubah variabel datetime Anda dengan info waktu yang sama

ePi272314
sumber
Salah? Kode yang saya tulis diuji dan saya menggunakannya dalam proyek Django. Ini jauh lebih jelas dan sederhana
ePi272314
"salah" merujuk pada kalimat terakhir dalam jawaban Anda: "... harus menambahkan ... bukan UTC" - Zona waktu UTC berfungsi di sini dan oleh karena itu pernyataannya tidak benar.
jfs
untuk menjadi jelas maksud saya: diff = datetime.now(timezone.utc) - your_timezone_aware_variableberfungsi (dan (a - b)rumus di atas adalah penjelasan mengapa (a - b)bisa bekerja meskipun a.tzinfotidak b.tzinfo).
jfs
6

Modul psycopg2 memiliki definisi zona waktu sendiri, jadi saya akhirnya menulis pembungkus saya sendiri di sekitar utcnow:

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

dan gunakan saja pg_utcnowkapan pun Anda membutuhkan waktu saat ini untuk membandingkannya dengan PostgreSQLtimestamptz

erjiang
sumber
Objek tzinfo apa pun yang mengembalikan nol utc offset akan dilakukan, misalnya .
jfs
6

Saya juga menghadapi masalah yang sama. Lalu saya menemukan solusi setelah banyak mencari.

Masalahnya adalah bahwa ketika kita mendapatkan objek datetime dari model atau bentuk itu diimbangi sadar dan jika kita mendapatkan waktu dengan sistem itu diimbangi naif .

Jadi yang saya lakukan adalah saya mendapatkan waktu saat ini menggunakan timezone.now () dan mengimpor zona waktu dengan dari django.utils mengimpor zona waktu dan meletakkan USE_TZ = True dalam file pengaturan proyek Anda.

Ashok Joshi
sumber
2

Saya datang dengan solusi ultra-sederhana:

import datetime

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

Ia bekerja dengan nilai-nilai datetime yang sadar zona waktu dan naif zona waktu. Dan tidak ada perpustakaan tambahan atau solusi pangkalan data yang diperlukan.

John L. Stanley
sumber
1

Saya temukan timezone.make_aware(datetime.datetime.now())sangat membantu dalam Django (Saya ada di 1.9.1). Sayangnya Anda tidak bisa begitu saja membuat datetimeobjek sadar-offset, lalu timetz()benda itu. Anda harus membuat datetimedan membuat perbandingan berdasarkan itu.

Joseph Coco
sumber
1

Apakah ada alasan yang mendesak mengapa Anda tidak bisa menangani perhitungan umur di PostgreSQL itu sendiri? Sesuatu seperti

select *, age(timeStampField) as timeStampAge from myTable
Nic Gibson
sumber
2
Ya ada .. tapi saya kebanyakan bertanya karena saya ingin menghindari melakukan semua perhitungan di postgre.
Ian
0

Saya tahu ini sudah tua, tetapi saya pikir saya akan menambahkan solusi saya kalau-kalau ada orang yang berguna.

Saya ingin membandingkan datetime naif lokal dengan datetime sadar dari timeserver. Saya pada dasarnya membuat objek datetime naif baru menggunakan objek datetime sadar. Ini sedikit hack dan tidak terlihat sangat cantik tetapi menyelesaikan pekerjaan.

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

... inilah fudge ...

    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
sumber
FYI, utc_to_local()dari jawaban saya mengembalikan waktu lokal sebagai objek datetime sadar (Ini adalah kode Python 3.3+)
jfs
Tidak jelas apa kode Anda coba lakukan. Anda bisa menggantinya dengan delta = response.tx_time - time.time().
jfs