jazzband / django-recurrence

Utility for working with recurring dates in Django.
https://django-recurrence.readthedocs.org/
BSD 3-Clause "New" or "Revised" License
486 stars 190 forks source link

Error comparing offset-naive and offset-aware datetimes #102

Open pigmonkey opened 7 years ago

pigmonkey commented 7 years ago

I have a model similar to the Course model in the documentation.

from recurrence.fields import RecurrenceField

class Course(models.Model):
    title = models.CharField(max_length=200)
    recurrences = RecurrenceField()

I've created an object with a simple recurrence of once per month on the fifth. There are no dates (dtend, dtstart, until, etc) associated.

>>> course.recurrences.__dict__
{'_cache': {},
 'dtend': None,
 'dtstart': None,
 'exdates': [],
 'exrules': [],
 'rdates': [],
 'rrules': [<recurrence.base.Rule at 0x61d7bd8d5390>]}

>>> course.recurrences.rrules[0].__dict__
{'byday': [],
 'byhour': [],
 'byminute': [],
 'bymonth': [],
 'bymonthday': [5],
 'bysecond': [],
 'bysetpos': [],
 'byweekno': [],
 'byyearday': [],
 'count': None,
 'freq': 1,
 'interval': 1,
 'until': None,
 'wkst': None}

When asking for a list of occurrences between two naive datetime objects, everything works as expected.

>>> course.recurrences.between(datetime.datetime(2018,1,1), datetime.datetime(2018,3,1))
[datetime.datetime(2018, 1, 5, 10, 6, 55),
 datetime.datetime(2018, 2, 5, 10, 6, 55)]

However, my Django app uses timezones, so I rarely deal with naive objects. Instead I'll pass in two timezone aware objects.

>>> course.recurrences.between(datetime.datetime(2018,1,1,tzinfo=pytz.utc), datetime.datetime(2018,3,1,tzinfo=pytz.utc))
Traceback (most recent call last):
  File "<console>", line 1, in <module>
  File "/home/pigmonkey/.virtualenvs/tad/lib/python2.7/site-packages/recurrence/base.py", line 502, in between
    dtstart, dtend, cache).between(after, before, inc)
  File "/home/pigmonkey/.virtualenvs/tad/lib/python2.7/site-packages/dateutil/rrule.py", line 291, in between
    if i >= before:
TypeError: can't compare offset-naive and offset-aware datetimes

This error was unexpected since I am not providing offset-naive datetimes anywhere in this example. I believe the problem is that because I am not specifying dtstart the dateutil library calls datetime.datetime.now() when django-recurrence generates the dateutil.rrule.rrule.

Since Django can have configured timezones, it seems like django-recurrence should always pass timezone.now() as dtstart to dateutil unless the user has provided a value. That will result in an aware object if timezones are configured, and a naive object otherwise, which is likely the expected behaviour.

dominicrodger commented 7 years ago

Thanks @pigmonkey!

That change makes sense to me - would you mind sending that in a pull request?

anomitra commented 6 years ago

@pigmonkey I ran into the same issue - did you fix this? And if yes, is the fix as simple as specifying a dtstart?

pigmonkey commented 6 years ago

Yes, if you pass in an aware datetime for dtstart it will work as expected. The other option would be to not pass dtstart, but to strip the tzinfo from your other date objects (using something like timezone.make_naive.

I keep meaning to submit a PR to fix this, but have not gotten to it yet.

bramski commented 4 years ago

This pull request was merged can you make a release with it please? I am seeing this issue in version 1.10.1 which is the latest version.

bramski commented 4 years ago

I see, the pull request did not address dealing with when there is no dtstart or dtend attached to the recurrence so this support is incomplete. If you call recurrence.between(start, end) and they are not set then the datetime created is offset naive and the comparison fails and your app crashes.

bramski commented 4 years ago

Looks like not your bug: https://github.com/dateutil/dateutil/issues/979

surajsinghbisht054 commented 1 year ago

I believe RRULE is using UTC time. so, just convert aware datetime object to UTC and remove tzinfo. It should work perfectly