Không thể trừ các datetimes ngây thơ và bù đắp


305

Tôi có một timestamptztrường nhận biết múi giờ trong PostgreSQL. Khi tôi lấy dữ liệu từ bảng, sau đó tôi muốn trừ thời gian ngay bây giờ để tôi có thể nhận được tuổi.

Vấn đề tôi gặp phải là cả hai datetime.datetime.now()datetime.datetime.utcnow()dường như trả lại dấu thời gian mà không biết dấu thời gian, dẫn đến việc tôi gặp phải lỗi này:

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

Có cách nào để tránh điều này (tốt nhất là không sử dụng mô-đun của bên thứ ba).

EDIT: Cảm ơn các đề xuất, tuy nhiên, việc cố gắng điều chỉnh múi giờ dường như gây ra lỗi cho tôi .. vì vậy tôi sẽ sử dụng dấu thời gian không biết về múi giờ trong PG và luôn chèn bằng cách sử dụng:

NOW() AT TIME ZONE 'UTC'

Theo cách đó, tất cả các dấu thời gian của tôi là UTC theo mặc định (mặc dù việc này khó chịu hơn).

Câu trả lời:


316

Bạn đã cố gắng để loại bỏ nhận thức múi giờ?

từ http://pytz.sourceforge.net/

naive = dt.replace(tzinfo=None)

có thể phải thêm chuyển đổi múi giờ là tốt.

chỉnh sửa: Xin lưu ý tuổi của câu trả lời này. Một câu trả lời Python 3 dưới đây.


32
Đây dường như là cách duy nhất để làm điều đó. Có vẻ khá khập khiễng rằng trăn có sự hỗ trợ khủng khiếp cho các múi giờ đến nỗi nó cần một mô-đun của bên thứ ba để hoạt động với dấu thời gian đúng cách ..
Ian

33
(Chỉ để ghi lại) Trên thực tế, việc thêm thông tin về múi giờ có thể là một ý tưởng tốt hơn: stackoverflow.com/a/4530166/548696
Tadeck

7
các đối tượng datetime ngây thơ vốn đã mơ hồ và do đó chúng nên được tránh. Nó là dễ dàng để thêm tzinfo thay
JFS

1
@Kylotan: UTC là múi giờ trong bối cảnh này (như được đại diện bởi lớp tzinfo). Nhìn vào datetime.timezone.utchoặc pytz.utc. Ví dụ: 1970-01-01 00:00:00không rõ ràng và bạn phải thêm múi giờ để định hướng : 1970-01-01 00:00:00 UTC. Bạn thấy, bạn phải thêm thông tin mới; dấu thời gian của chính nó là mơ hồ.
JFS

1
@JFSebastian: Vấn đề là utcnowkhông nên trả lại một đối tượng ngây thơ hoặc dấu thời gian mà không có múi giờ. Từ các tài liệu, "Một đối tượng nhận thức được sử dụng để thể hiện một thời điểm cụ thể không mở để giải thích". Bất cứ lúc nào trong UTC đáp ứng tiêu chí này, theo định nghĩa.
Kylotan

214

Giải pháp chính xác là thêm thông tin múi giờ, ví dụ, để lấy thời gian hiện tại làm đối tượng datetime nhận thức trong Python 3:

from datetime import datetime, timezone

now = datetime.now(timezone.utc)

Trên các phiên bản Python cũ hơn, bạn có thể tự xác định utcđối tượng tzinfo (ví dụ từ tài liệu 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()

sau đó:

now = datetime.now(utc)

10
Tốt hơn là loại bỏ tz như câu trả lời được chấp nhận ủng hộ IMHO.
Shautieh

3
Dưới đây là danh sách các múi giờ của python: stackoverflow.com/questions/13866926/
Khăn

61

Tôi biết một số người sử dụng Django đặc biệt như một giao diện để trừu tượng loại tương tác cơ sở dữ liệu này. Django cung cấp các tiện ích có thể được sử dụng cho việc này:

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

Bạn cần phải thiết lập cơ sở hạ tầng cài đặt Django cơ bản, ngay cả khi bạn chỉ sử dụng loại giao diện này (trong cài đặt, bạn cần bao gồm USE_TZ=Trueđể có được một datetime nhận thức).

Chính nó, điều này có lẽ không đủ gần để thúc đẩy bạn sử dụng Django như một giao diện, nhưng có nhiều đặc quyền khác. Mặt khác, nếu bạn vấp ngã ở đây vì bạn đang đọc ứng dụng Django (như tôi đã làm), thì có lẽ điều này sẽ giúp ...


1
bạn cần USE_TZ=True, để có được một datetime nhận thức ở đây.
JFS

2
Có - Tôi đã quên đề cập rằng bạn cần thiết lập cài đặt của mình như JFSebastian mô tả (Tôi đoán đây là một ví dụ của 'thiết lập và quên').
hiền nhân

Và điều này cũng có thể dễ dàng chuyển đổi sang các múi giờ khác, chẳng hạn như + timedelta(hours=5, minutes=30)đối với IST
ABcDexter

25

Đây là một giải pháp rất đơn giản và rõ ràng
Hai dòng mã

# 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

Kết luận: Bạn phải thay đổi các biến thời gian của mình với thông tin cùng thời gian


Sai? Mã tôi đã viết đã được thử nghiệm và tôi đang sử dụng nó trong một dự án django. Điều đó rất rõ ràng và đơn giản
ePi272314

"không chính xác" chỉ câu cuối cùng trong câu trả lời của bạn: "... phải thêm ... không phải UTC" - múi giờ UTC hoạt động ở đây và do đó câu lệnh không chính xác.
JFS

để rõ ràng tôi có nghĩa là: diff = datetime.now(timezone.utc) - your_timezone_aware_variablehoạt động (và (a - b)công thức trên là giải thích tại sao (a - b)có thể làm việc ngay cả khi a.tzinfokhông b.tzinfo).
jfs

6

Mô-đun psycopg2 có định nghĩa múi giờ riêng, vì vậy tôi đã kết thúc việc viết trình bao bọc của riêng mình xung quanh utcnow:

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

và chỉ sử dụng pg_utcnowbất cứ khi nào bạn cần thời gian hiện tại để so sánh với PostgreSQLtimestamptz


Bất kỳ đối tượng tzinfo nào trả về zero utc offset sẽ làm, ví dụ .
jfs

6

Tôi cũng phải đối mặt với vấn đề tương tự. Sau đó, tôi tìm thấy một giải pháp sau rất nhiều tìm kiếm.

Vấn đề là khi chúng ta lấy đối tượng datetime từ mô hình hoặc biểu mẫu thì nó được nhận biết bù trừ và nếu chúng ta có thời gian bằng hệ thống thì nó được bù đắp ngây thơ .

Vì vậy, những gì tôi đã làm là tôi có thời gian hiện tại bằng cách sử dụng timezone.now () và nhập múi giờ bằng cách nhập múi giờ django.utils và đặt USE_TZ = True vào tệp cài đặt dự án của bạn.


2

Tôi đã đưa ra một giải pháp cực kỳ đơn giản:

import datetime

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

Nó hoạt động với cả các giá trị datetime nhận biết múi giờ và múi giờ ngây thơ. Và không có thư viện bổ sung hoặc giải pháp cơ sở dữ liệu được yêu cầu.


1

Tôi đã tìm thấy timezone.make_aware(datetime.datetime.now())rất hữu ích trong django (Tôi đang ở 1.9.1). Thật không may, bạn không thể đơn giản làm cho một datetimeđối tượng nhận biết bù đắp, sau đó timetz()nó. Bạn phải làm một datetimevà so sánh dựa trên đó.


1

Có một số lý do cấp bách tại sao bạn không thể xử lý tính toán tuổi trong chính PostgreSQL? Cái gì đó như

select *, age(timeStampField) as timeStampAge from myTable

2
Vâng, có .. nhưng tôi chủ yếu hỏi vì tôi muốn tránh thực hiện tất cả các tính toán trong postgre.
Ian

0

Tôi biết điều này đã cũ, nhưng chỉ nghĩ rằng tôi sẽ thêm giải pháp của mình trong trường hợp ai đó thấy nó hữu ích.

Tôi muốn so sánh datetime ngây thơ cục bộ với datetime nhận thức từ một máy chủ thời gian. Về cơ bản tôi đã tạo ra một đối tượng datetime ngây thơ mới bằng cách sử dụng đối tượng datetime nhận thức. Đó là một chút hack và trông không đẹp lắm nhưng hoàn thành công việc.

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

... đến đây là ...

    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()từ câu trả lời của tôi trả về giờ địa phương dưới dạng đối tượng datetime nhận biết (Đó là mã Python 3.3+)
jfs

Không rõ mã của bạn đang cố gắng làm gì. Bạn có thể thay thế nó bằng delta = response.tx_time - time.time().
jfs
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.