2012-09-19 25 views
11

Hiện hỗ trợ dateutil rrule DST và TZ? Cần một cái gì đó tương tự như iCalendar RRULE.Làm cách nào để xử lý DST và TZ trong các sự kiện định kỳ?

Nếu không - làm thế nào để giải quyết vấn đề này (lập kế hoạch định kỳ sự kiện & DST bù đắp thay đổi)

Imports

>>> from django.utils import timezone 
>>> import pytz 
>>> from datetime import timedelta 
>>> from dateutil import rrule 
>>> now = timezone.now() 
>>> pl = pytz.timezone("Europe/Warsaw") 

Issue với timedelta (cần phải có những giờ địa phương giống nhau, nhưng offsets DST khác nhau) :

>>> pl.normalize(now) 
datetime.datetime(2012, 9, 20, 1, 16, 58, 226000, tzinfo=<DstTzInfo 'Europe/Warsaw' CEST+2:00:00 DST>)  
>>> pl.normalize(now+timedelta(days=180)) 
datetime.datetime(2013, 3, 19, 0, 16, 58, 226000, tzinfo=<DstTzInfo 'Europe/Warsaw' CET+1:00:00 STD>) 

Vấn đề với rrule (cần phải có cùng giờ địa phương của mỗi lần xuất hiện):

>>> r = rrule.rrule(3,dtstart=now,interval=180,count=2) 
>>> pl.normalize(r[0]) 
datetime.datetime(2012, 9, 20, 1, 16, 58, tzinfo=<DstTzInfo 'Europe/Warsaw' CEST+2:00:00 DST>) 
>>> pl.normalize(r[1]) 
datetime.datetime(2013, 3, 19, 0, 16, 58, tzinfo=<DstTzInfo 'Europe/Warsaw' CET+1:00:00 STD>) 
+0

để có các phương pháp hay nhất về tiết kiệm ánh sáng ban ngày và múi giờ, http://stackoverflow.com/q/2532729/1167333 cung cấp tóm tắt tốt về các phương pháp hay nhất – oberron

Trả lời

10

@asdf: Tôi không thể thêm mã để bình luận vì vậy tôi cần phải đăng bài này như một câu trả lời:

tôi sợ rằng với giải pháp của bạn tôi sẽ luôn luôn lỏng thông tin DST, do đó một nửa số tái phát năm sẽ thời gian nghỉ là 1 giờ.

Căn cứ vào câu trả lời của bạn tôi phát hiện ra rằng đây có thể là giải pháp đúng:

>>> from datetime import datetime 
>>> import pytz 
>>> from dateutil import rrule 
>>> # this is raw data I get from the DB, according to django docs I store it in UTC 
>>> raw = datetime.utcnow().replace(tzinfo=pytz.UTC) 
>>> # in addition I need to store the timezone so I can do dst the calculations 
>>> tz = pytz.timezone("Europe/Warsaw") 
>>> # this means that the actual local time would be 
>>> local = raw.astimezone(tz) 
>>> # but rrule doesn't take into account DST and local time, so I must convert aware datetime to naive 
>>> naive = local.replace(tzinfo=None) 
>>> # standard rrule 
>>> r = rrule.rrule(rrule.DAILY,interval=180,count=10,dtstart=naive) 
>>> for dt in r: 
>>>  # now we must get back to aware datetime - since we are using naive (local) datetime, 
     # we must convert it back to local timezone 
...  print tz.localize(dt) 

Đây là lý do tại sao tôi nghĩ rằng giải pháp của bạn có thể thất bại:

>>> from datetime import datetime 
>>> from dateutil import rrule 
>>> import pytz 
>>> now = datetime.utcnow() 
>>> pl = pytz.timezone("Europe/Warsaw") 
>>> r = rrule.rrule(rrule.DAILY, dtstart=now, interval=180, count=2) 
>>> now 
datetime.datetime(2012, 9, 21, 9, 21, 57, 900000) 
>>> for dt in r: 
...  local_dt = dt.replace(tzinfo=pytz.UTC).astimezone(pl) 
...  print local_dt - local_dt.dst() 
...  
2012-09-21 10:21:57+02:00 
2013-03-20 10:21:57+01:00 
>>> # so what is the actual local time we store in the DB ? 
>>> now.replace(tzinfo=pytz.UTC).astimezone(pl) 
datetime.datetime(2012, 9, 21, 11, 21, 57, 900000, tzinfo=<DstTzInfo 'Europe/Warsaw' CEST+2:00:00 DST>) 

Như bạn thấy, có Chênh lệch 1 giờ giữa kết quả rrule và dữ liệu thực mà chúng tôi lưu trữ trong DB.

+0

Điều này có vẻ đúng, nhưng tôi không thể tin rằng không có cách nào tốt hơn để thực hiện điều này. – Jakobovski

+0

đã đập đầu tôi vào ngày này - cảm ơn @ g00fy! – mstringer

3

Lưu ý rằng trả về django.utils.timezone.now() có thể là thời gian chờ ngây thơ hoặc nhận biết, tùy thuộc vào cài đặt USE_TZ của bạn. Những gì bạn nên sử dụng trong nội bộ để tính toán (ví dụ: now mà bạn cung cấp cho rrule.rrule) là ngày giờ dựa trên UTC. Nó có thể là một cái nhận biết bù đắp (ví dụ: datetime.now(pytz.UTC)), hoặc một cái ngây thơ (ví dụ: datetime.utcnow()). Sau này dường như được ưa thích để lưu trữ (xem this blogpost).

Bây giờ, rrule.rrule xử lý các múi giờ, đó là lý do tại sao bạn quan sát thay đổi CEST-to-CET trong những gì rrule của bạn mang lại. Tuy nhiên, nếu những gì bạn muốn là luôn luôn nhận được cùng một giờ (ví dụ: 0 AM mỗi ngày, không có vấn đề DST hay không), sau đó bạn thực sự muốn "bỏ qua" sự thay đổi. Một cách làm như vậy là làm dt = dt - dt.dst(), nếu dt là một ngày giờ nhận thức.

Đây là cách bạn có thể làm điều đó:

from datetime import datetime 
from dateutil import rrule 
import pytz 
now = datetime.utcnow() 
pl = pytz.timezone("Europe/Warsaw") 
r = rrule.rrule(rrule.DAILY, dtstart=now, interval=180, count=2) 

# will yield naive datetimes, assumed UTC 
for dt in r: 
    # convert from naive-UTC to aware-local 
    local_dt = dt.replace(tzinfo=pytz.UTC).astimezone(pl) 
    # account for the dst difference 
    print local_dt - local_dt.dst() 

này in hai datetimes, mỗi nằm trong một múi giờ khác nhau (tốt, khác nhau thiết lập DST), cả hai đại diện cho giờ wallclock cùng. Nếu bạn đã xử lý các-UTC-datetimes thay vì ngây thơ-giả-UTC như trong ví dụ, bạn chỉ cần bỏ qua phần .replace. Một cheat sheet nhanh về các chuyển đổi này có thể được tìm thấy here.

2

Có, vấn đề là bạn KHÔNG nên lưu trữ giờ địa phương, bao giờ hết. Lưu trữ UTC và chuyển đổi thành giờ địa phương theo yêu cầu (ví dụ: trên cơ sở theo yêu cầu, sử dụng dữ liệu yêu cầu, như tiêu đề Chấp nhận ngôn ngữ, để biết bạn nên sử dụng những gì tz).

Những gì bạn đang làm là bạn đang sử dụng ngày giờ địa phương để tính toán (ví dụ: rrule.rrule()).Điều này là tối ưu, vì bạn cần phải biết múi giờ đích để làm điều đó, do đó, điều này có thể được thực hiện theo yêu cầu duy nhất, trái ngược với việc tính toán trước các thực hiện rrule. Đó là lý do tại sao bạn nên sử dụng UTC trong nội bộ (ví dụ: để tính toán trước các thời gian biểu) và sau đó chuyển đổi chúng trước khi gửi cho người dùng. Trong trường hợp này, chỉ chuyển đổi sẽ phải được thực hiện sau khi nhận được yêu cầu (nghĩa là khi múi giờ đích được biết).

Các vấn đề liên quan