2012-09-19 32 views
11

是否dateutil rrule支持DST和TZ?需要類似於iCalendar RRULE的東西。如何處理經常性事件中的DST和TZ?

如果沒有 - 如何解決這個問題(安排定期事件& DST偏移量變化)

進口

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

問題與timedelta(需要有相同的本地時間,但不同的DST偏移) :與RRULE

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

問題(需要具有相同的每次出現的每一個地方時):

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

關於夏令時和時區的最佳實踐,這個http://stackoverflow.com/q/2532729/1167333給出了最佳實踐的一個很好的總結 – oberron

回答

10

@asdf:我不能添加代碼註釋,所以我需要張貼此作爲一個答案:

恐怕與您的解決方案我會一直寬鬆DST的信息,因此在今年復發的一半會休息1小時。

此基礎上你的答案,我發現,這可能是正確的解決方案:爲什麼我認爲您的解決方案可能會失敗

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

這就是:

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

正如你所看到的,有rrule結果與我們存儲在數據庫中的實際數據之間的1小時差異。

+0

這似乎是正確的,但我不敢相信沒有更好的方法執行此操作。 – Jakobovski

+0

整天都在b my我的頭 - 謝謝@ g00fy! – mstringer

3

請注意,根據您的USE_TZ設置,django.utils.timezone.now()返回的內容可以是天真的或意識到的日期時間。內部用於計算的內容(例如,您提供給rrule.rrule的now)是基於UTC的日期時間。它可以是偏移感知的(即,datetime.now(pytz.UTC)),或者是天真的(即,datetime.utcnow())。後者似乎是儲存的首選(見this blogpost)。

現在,rrule.rrule處理時區,這就是爲什麼你觀察你的rrule產量的CEST到CET的變化。然而,如果你想要的是總是獲得相同的時間(例如每天0點,不管DST與否),那麼你實際上是想「忽略」這個變化。如果dt是有意識的日期時間,則這樣做的一種方式是執行dt = dt - dt.dst()

這裏是你如何能做到這一點:

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

這個打印兩個日期時間,各處於不同的時區(當然,不同的DST設置),都代表相同的掛鐘小時。 如果您要處理aware-UTC-datetimes而不是像本例中那樣使用naive-assumed-UTC,那麼您只需跳過.replace部分即可。 關於這些轉換的快速備忘單可以在here找到。

2

是的,關鍵是你不應該存儲本地時間,永遠。存儲UTC並根據需要轉換爲本地時間(例如,基於每個請求,使用請求數據,如Accept-Language標頭,知道您應該使用什麼)。

你在做什麼是你正在使用本地化的日期時間進行計算(即rrule.rrule())。這是不理想的,因爲您需要知道目標時區才能這樣做,所以只能按請求完成,而不是預先計算rrule實現。這就是爲什麼你應該在內部使用UTC(即預先計算日期時間),然後在發送給用戶之前將其轉換。在這種情況下,只有在收到請求後(即目標時區已知時)才需要進行轉換。