dateutil rrule是否支持DST和TZ?需要类似于iCalendar RRULE的内容。
如果没有 - 如何解决此问题(安排重复发生的事件和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偏移):
>>> 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>)
rrule问题(每次出现时需要每个当地时间相同):
>>> 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 :(得分:11)
@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小时的差异。
答案 1 :(得分:3)
请注意,django.utils.timezone.now()返回的内容可以是天真的或可识别的日期时间,具体取决于您的USE_TZ设置。您应该在内部用于计算的内容(例如,您提供给rrule.rrule的now
)是基于UTC的日期时间。它可以是偏移感知的(即datetime.now(pytz.UTC)
),也可以是天真的(即datetime.utcnow()
)。后者似乎更适合存储(见this blogpost)。
现在,rrule.rrule处理时区,这就是为什么你观察你的rrule产生的CEST-to-CET变化的原因。但是,如果您想要的是始终获得相同的时间(例如,每天0 AM,无论DST与否),那么您实际上想要“忽略”更改。如果dt = dt - dt.dst()
是一个有意识的日期时间,那么这样做的一种方法是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)
# 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设置),两者都代表相同的wallclock小时。 如果您要在示例中处理aware-UTC-datetimes而不是naive-assume-UTC,那么您只需跳过.replace部分。 有关这些转化的快速备忘单可以找到here。
答案 2 :(得分:2)
是的,重点是你不应该存储本地时间。存储UTC并按需转换为本地时间(即基于每个请求,使用请求数据,如Accept-Language标头,以了解您应该使用的内容)。
您正在做的是使用本地化的日期时间进行计算(即rrule.rrule()
)。这是次优的,因为您需要知道目标时区,因此这可以仅按请求完成,而不是预先计算rrule实现。这就是为什么你应该在内部使用UTC(即预先计算日期时间),然后在发送给用户之前转换它们。在这种情况下,只有在收到请求后才能进行转换(即,当目标时区已知时)。