Skip to content

Modified rrulewraper to handle timezone-aware datetimes. #9028

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Dec 1, 2017

Conversation

pganssle
Copy link
Member

@pganssle pganssle commented Aug 14, 2017

PR Summary

dateutil.rrule.rrule will more or less work just fine with dateutil zones, but does not work with pytz zones. In general, I'd say that for the current (and past) version(s) of dateutil, it's best to use naive zones for your rrule and then attach your original tzinfo to the naive zones.

This modifies rrulewrapper with shims that strips off time zones going in to rrule and then adds them back on the way out of rrule.

Fixes #9018

PR Checklist

  • Has Pytest style unit tests
  • Code is PEP 8 compliant (pretty sure I haven't added any new PEP8 problems)

@pganssle pganssle force-pushed the fix_rrulewrapper_tzinfo branch from 2ce1c44 to 68d43e5 Compare August 14, 2017 01:09
@dstansby dstansby added this to the 2.1.1 (next bug fix release) milestone Aug 16, 2017
@pganssle
Copy link
Member Author

Ping @dstansby - Are you the one reviewing this? Just a gentle prod.

Copy link
Member

@WeatherGod WeatherGod left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Still wrapping my head around this, so I have some initial questions.

Another question is if any of this dependent upon a minimum version of dateutil?

@@ -732,20 +733,105 @@ def __call__(self, x, pos=None):


class rrulewrapper(object):
def __init__(self, freq, tzinfo=None, **kwargs):
kwargs['freq'] = freq
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I actually have no clue if it is always safe to modify a **kwargs. Does it never modify a passed in dictionary?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It would be pretty weird if it could modify a passed in dictionary, since it's an expansion of the keywords. I assume that the only way there would even be a dictionary to pass in would be if you passed it like f(**kwargs). Testing it out on Python 3 seems to indicate that does not modify the dictionary. I can try and read up on it later, though.

def test_func(*args, **kwargs):
    kwargs['demo'] = 9

if __name__ == "__main__":
    kwargs = {}
    test_func(**kwargs)
    print(kwargs)    # {}

    kwargs2 = {'demo': 6}
    test_func(**kwargs)
    print(kwargs)    # {}

Copy link
Member Author

@pganssle pganssle Aug 25, 2017

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I have looked into this a bit and I don't see anyone actually saying this is safe, but it also doesn't seem like the question has come up before - I would guess that it would be surprising if it were unsafe and someone would have commented on it by now. My understanding of how these things are supposed to work makes me fairly certain that this will not be a problem, but I've opened a question on SO just in case.

Either way, if we find that there's some weird situation where it could be a problem, we can just have a second PR that adds kwargs = kwargs.copy() at the top of the function, so I don't see any reason why uncertainty in this regard should be blocking.

Edit: I should have waited a few more minutes, the first answer on the SO says that it's always safe based on the spec.

# datetimes are returned
if 'dtstart' in kwargs:
dtstart = kwargs['dtstart']
if dtstart.tzinfo is not None:
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

could do dtstart = kwargs.get('dtstart', None), and therefore be able to reduce all of this by an indent.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, this is fine. I think the main reason I did it this way is that it avoids the unnecessary replace(tzinfo=None) call at the end, but I'm fine with it either way (I really doubt anyone will be bogged down by any potential performance hit in that case).

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@WeatherGod Actually, looking at this, I don't think you can reduce all this by an indent, because if dtstart is None, the dtstart.tzinfo is None and dtstart.replace() calls will fail.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

actually, it'll fail for a different reason. dtstart would be None and it wouldn't have the tzinfo attribute. Don't know why I didn't see that originally.


if 'until' in kwargs:
until = kwargs['until']
if until.tzinfo is not None:
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same here: until = kwargs.get('until', None)

def _attach_tzinfo(self, dt, tzinfo):
# pytz zones are attached by "localizing" the datetime
if hasattr(tzinfo, 'localize'):
return tzinfo.localize(dt, is_dst=True)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Does it make sense to hard-code is_dst?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@WeatherGod The default for pytz.localize is essentially the same as hard-coding is_dst=False, which is inconsistent with PEP-495, since rrule will never set the fold attribute.

In the end I think very few people will need the feature of choosing which side of a DST fold their ticks are on, but it would not be difficult to add in a later version.

@pganssle
Copy link
Member Author

@WeatherGod I am not sure if it requires a specific version of dateutil, but it's not using any unusual features of rrule - the changes are all about constructing the right rrule and then dealing with time zones in the wrapper.

I don't know how I would install python-dateutil==1.1, since that is not on PyPI and the tags in the git repo don't go back that far. It works fine with python-dateutil==2.0, though. It's not particularly convenient for me to test this locally on Python 2.7, so I can't test python-dateutil==1.4 unless you think it's critical.

@pganssle
Copy link
Member Author

Ping @WeatherGod . Any other changes required?

@tacaswell tacaswell modified the milestones: 2.1.1 (next bug fix release), 2.2 (next feature release) Oct 9, 2017
@pganssle
Copy link
Member Author

Poke @tacaswell @WeatherGod @dstansby

@dopplershift dopplershift merged commit 725d8d6 into matplotlib:master Dec 1, 2017
@QuLogic QuLogic modified the milestones: needs sorting, v2.2.0 Feb 13, 2018
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

6 participants