13

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

3 回答 3

15

@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 小时的差异。

于 2012-09-21T09:24:51.210 回答
3

请注意,根据您的 USE_TZ 设置,django.utils.timezone.now() 返回的内容可以是天真或有意识的日期时间。您应该在内部用于计算的内容(例如,now您提供给 rrule.rrule 的内容)是基于 UTC 的日期时间。它可以是一个偏移感知的(即。datetime.now(pytz.UTC)),或一个幼稚的(即。datetime.utcnow())。后者似乎更适合存储(请参阅此博客文章)。

现在, rrule.rrule 处理时区,这就是为什么您观察 rrule 产生的 CEST 到 CET 变化的原因。但是,如果您想要始终获得相同的时间(例如,每天凌晨 0 点,无论是否为 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 设置),都代表相同的挂钟时间。如果您要处理有意识的UTC-datetimes 而不是像示例中那样的naive-assumed-UTC,您只需跳过 .replace 部分。可以在此处找到有关这些转换的快速备忘单。

于 2012-09-20T18:46:04.327 回答
1

是的,关键是你永远不应该存储本地时间。存储 UTC 并按需转换为本地时间(即在每个请求的基础上,使用请求数据,如 Accept-Language 标头,以了解您应该使用什么 tz)。

What you're doing is you're using a localized datetime for calculations (ie. rrule.rrule()). This is suboptimal, as you need to know the target timezone to do that, so this can be done per request only, as opposed to precalculating the rrule realizations. That's why you should use UTC internally (ie. to precalculate the datetimes) and then convert them prior to sending to the user. In this case, only the conversion would have to be done after receiving the request (that is, when the target timezone is known).

于 2012-09-21T13:56:46.377 回答