ไม่สามารถลบชุดข้อมูลตรงข้ามที่ไร้เดียงสาและออฟเซ็ตที่รับรู้ได้


305

ฉันมีเขตtimestamptzข้อมูลเขตเวลาใน PostgreSQL เมื่อฉันดึงข้อมูลจากตารางจากนั้นฉันต้องการที่จะลบเวลาตอนนี้เพื่อที่ฉันจะได้อายุ

ปัญหาที่ฉันมีอยู่ก็คือทั้งคู่datetime.datetime.now()และdatetime.datetime.utcnow()ดูเหมือนจะย้อนกลับไปยังเขตเวลาที่ไม่ทราบเวลาซึ่งทำให้ฉันได้รับข้อผิดพลาดนี้:

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

มีวิธีการหลีกเลี่ยงปัญหานี้หรือไม่ (ควรมีโมดูลของบุคคลที่สาม)

แก้ไข: ขอบคุณสำหรับคำแนะนำ แต่พยายามปรับเขตเวลาดูเหมือนว่าจะทำให้ฉันมีข้อผิดพลาด .. ดังนั้นฉันจะใช้ timestamps ไม่ทราบเขตเวลาใน PG และใส่โดยใช้:

NOW() AT TIME ZONE 'UTC'

ด้วยวิธีนี้การประทับเวลาทั้งหมดของฉันเป็น UTC ตามค่าเริ่มต้น (แม้ว่าจะทำสิ่งนี้ได้น่ารำคาญกว่า)

คำตอบ:


316

คุณพยายามที่จะลบการรับรู้เขตเวลา?

จากhttp://pytz.sourceforge.net/

naive = dt.replace(tzinfo=None)

อาจต้องเพิ่มการแปลงเขตเวลาด้วย

แก้ไข: โปรดระวังอายุของคำตอบนี้ Python 3 คำตอบอยู่ด้านล่าง


32
นี่เป็นวิธีเดียวที่จะทำได้ ดูเหมือนว่าง่อยสวยว่างูหลามได้รับการสนับสนุนเส็งเคร็งดังกล่าวสำหรับเขตที่ต้องการโมดูลของบุคคลที่สามในการทำงานที่มีการประทับเวลาถูกต้อง ..
เอียน

33
(เพียงเพื่อบันทึก) การเพิ่มข้อมูลเกี่ยวกับเขตเวลาจริงอาจเป็นความคิดที่ดีกว่า: stackoverflow.com/a/4530166/548696
Tadeck

7
วัตถุ datetime ไร้เดียงสานั้นมีความกำกวมอย่างชัดเจนและดังนั้นจึงควรหลีกเลี่ยง มันเป็นเรื่องง่ายที่จะเพิ่ม tzinfo แทน
jfs

1
@Kylotan: UTC เป็นเขตเวลาในบริบทนี้ (ตามที่แสดงโดยคลาส tzinfo) ดูหรือdatetime.timezone.utc pytz.utcยกตัวอย่างเช่นคลุมเครือและคุณจะต้องเพิ่มเขตเวลาให้กระจ่าง:1970-01-01 00:00:00 1970-01-01 00:00:00 UTCคุณเห็นคุณต้องเพิ่มข้อมูลใหม่; การประทับเวลาด้วยตัวเองไม่ชัดเจน
jfs

1
@JFSebastian: ปัญหาคือutcnowไม่ควรส่งคืนวัตถุไร้เดียงสาหรือการประทับเวลาโดยไม่มีเขตเวลา จากเอกสาร "วัตถุที่รับรู้ใช้เพื่อแสดงช่วงเวลาที่เฉพาะเจาะจงซึ่งไม่เปิดให้ตีความ" เวลาใดก็ได้ใน UTC ตรงตามเกณฑ์นี้ตามคำนิยาม
Kylotan

214

วิธีการแก้ไขที่ถูกต้องคือการเพิ่มข้อมูลเขตเวลาเช่นเพื่อรับเวลาปัจจุบันเป็นวัตถุ datetime ที่รับรู้ใน Python 3:

from datetime import datetime, timezone

now = datetime.now(timezone.utc)

ในเวอร์ชัน Python ที่เก่ากว่าคุณสามารถกำหนดutcวัตถุ tzinfo ด้วยตัวเอง (ตัวอย่างจากเอกสาร 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()

แล้ว:

now = datetime.now(utc)

10
ดีกว่าการลบ tz ออกเนื่องจากคำตอบที่ได้รับการยอมรับสนับสนุน IMHO
Shautieh

3
นี่คือรายการของเขตเวลาหลาม: stackoverflow.com/questions/13866926/ …
comfytoday

61

ฉันรู้ว่าบางคนใช้ Django โดยเฉพาะเป็นอินเทอร์เฟซเพื่อสรุปปฏิสัมพันธ์ฐานข้อมูลประเภทนี้ Django ให้บริการสาธารณูปโภคที่สามารถใช้สำหรับสิ่งนี้:

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

คุณจำเป็นต้องตั้งค่าโครงสร้างพื้นฐานการตั้งค่า Django ขั้นพื้นฐานแม้ว่าคุณจะใช้อินเทอร์เฟซประเภทนี้ (ในการตั้งค่าคุณจะต้องรวมUSE_TZ=Trueเพื่อรับ datetime ที่รับรู้)

โดยตัวของมันเองมันอาจจะไม่มีที่ไหนใกล้พอที่จะกระตุ้นให้คุณใช้ Django เป็นอินเทอร์เฟซ แต่ก็มีข้อดีอื่น ๆ อีกมากมาย ในทางตรงกันข้ามถ้าคุณสะดุดที่นี่เพราะคุณกำลัง mangling แอพ Django ของคุณ (เหมือนที่ฉันทำ) แล้วบางทีนี่อาจช่วย ...


1
คุณต้องการUSE_TZ=Trueเพื่อรับข้อมูลเวลาที่นี่
jfs

2
ใช่ - ฉันลืมที่จะพูดถึงว่าคุณต้องตั้งค่า settings.py ของคุณตาม JFSebastian อธิบาย (ฉันเดาว่านี่เป็นตัวอย่างของ 'set and forget')
ปราชญ์

และยังสามารถแปลงเป็นเขตเวลาอื่นได้อย่างง่ายดายเช่น+ timedelta(hours=5, minutes=30)สำหรับ IST
ABcDexter

25

นี่เป็นวิธีแก้ปัญหาที่ง่ายและชัดเจน
สองบรรทัดของโค้ด

# 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

สรุป: คุณต้องจัดการตัวแปรวันที่และเวลาของคุณด้วยข้อมูลเวลาเดียวกัน


ไม่ถูกต้อง? รหัสที่ฉันเขียนได้รับการทดสอบและฉันใช้มันในโครงการ django มันชัดเจนและเรียบง่ายมาก ๆ
ePi272314

"ไม่ถูกต้อง" หมายถึงประโยคสุดท้ายในคำตอบของคุณ: "... ต้องเพิ่ม ... ไม่ใช่ UTC" - เขตเวลา UTC ทำงานที่นี่ดังนั้นคำสั่งจึงไม่ถูกต้อง
jfs

เพื่อความชัดเจนฉันหมายถึง: diff = datetime.now(timezone.utc) - your_timezone_aware_variableทำงาน (และ(a - b)สูตรด้านบนคือคำอธิบายว่าทำไมถึง(a - b)สามารถทำงานได้แม้ว่าa.tzinfoจะไม่ใช่b.tzinfo)
jfs

6

โมดูล psycopg2 มีคำจำกัดความเขตเวลาของตัวเองดังนั้นฉันสิ้นสุดการเขียนเสื้อคลุมของตัวเองรอบ utcnow:

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

และใช้pg_utcnowเมื่อใดก็ตามที่คุณต้องการเวลาปัจจุบันเพื่อเปรียบเทียบกับ PostgreSQLtimestamptz


วัตถุ tzinfo ใด ๆ ที่ส่งคืนอ็อฟเซ็ต zero utc จะทำเช่นนั้น
jfs

6

ฉันยังประสบปัญหาเดียวกัน จากนั้นฉันก็พบทางออกหลังจากการค้นหามากมาย

ปัญหาก็คือเมื่อเราได้รับวัตถุ datetime จากรุ่นหรือรูปแบบมันจะชดเชยความตระหนักและถ้าเราได้รับเวลาโดยระบบมันจะชดเชยไร้เดียงสา

ดังนั้นสิ่งที่ฉันทำคือฉันได้เวลาปัจจุบันโดยใช้timezone.now ()และนำเข้า timezone โดยจาก django.utils นำเข้า timezoneแล้วใส่USE_TZ = Trueในไฟล์การตั้งค่าโครงการของคุณ


2

ฉันมาด้วยวิธีการที่ง่ายมาก:

import datetime

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

มันทำงานได้กับค่าทั้งสองของ timezone-aware และ timezone-naive และไม่จำเป็นต้องมีการแก้ไขไลบรารีหรือฐานข้อมูลเพิ่มเติม


1

ฉันพบว่าtimezone.make_aware(datetime.datetime.now())มีประโยชน์ใน django (ฉันใช้ 1.9.1) น่าเสียดายที่คุณไม่สามารถทำให้datetimeอ็อบเจกต์ offset ทราบtimetz()ได้ คุณต้องทำdatetimeและทำการเปรียบเทียบตามนั้น


1

มีเหตุผลเร่งด่วนบ้างไหมที่คุณไม่สามารถจัดการการคำนวณอายุใน PostgreSQL ได้ สิ่งที่ต้องการ

select *, age(timeStampField) as timeStampAge from myTable

2
ใช่มี .. แต่ส่วนใหญ่ฉันถามเพราะฉันต้องการหลีกเลี่ยงการคำนวณทั้งหมดใน postgre
เอียน

0

ฉันรู้ว่านี่เก่า แต่แค่คิดว่าฉันจะเพิ่มโซลูชันของฉันในกรณีที่มีคนพบว่ามีประโยชน์

ฉันต้องการที่จะเปรียบเทียบวันที่และเวลาที่ไร้เดียงสาท้องถิ่นกับวันที่และเวลาที่ทราบจากเซิร์ฟเวอร์เวลา ฉันสร้างวัตถุ datetime ที่ไร้เดียงสาใหม่โดยใช้วัตถุ datetime ที่รับรู้ มันเป็นบิตของการแฮ็กและดูไม่สวยนัก แต่ทำงานได้สำเร็จ

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

... ที่นี่เหลวไหล ...

    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()จากคำตอบของฉันส่งคืนเวลาท้องถิ่นเป็นวัตถุ datetime ที่รับรู้ (มันเป็นรหัส Python 3.3+)
jfs

ยังไม่ชัดเจนว่ารหัสของคุณพยายามทำอะไร delta = response.tx_time - time.time()คุณสามารถแทนที่ด้วย
jfs
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.