Skip to content
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

Timezone bug with dateutil.tz.tzlocal #187

Closed
Stuveman opened this issue May 19, 2022 · 2 comments
Closed

Timezone bug with dateutil.tz.tzlocal #187

Stuveman opened this issue May 19, 2022 · 2 comments

Comments

@Stuveman
Copy link

While testing the caldav library I found a bug regarding time zones.
When creating new events and setting the timezone with tzlocal() from the dateutil library, the retrieved data are 2 hours in the future.

So basically this is the core of my create function:

calendars[0].save_event(
                dtstart = new_event["start"], # datetime.datetime object
                dtend = new_event["end"], # datetime.datetime object
                summary = new_event["name"] )

Before calling the create-function, I'm setting the timezone with "astimezone()":

    from dateutil import tz
    timezone = tz.tzlocal()
    new_event = {'name': name,                                        
        'start': datetime.strptime(start, '%Y-%m-%d %H:%M').astimezone(timezone),
        'end': datetime.strptime(end, '%Y-%m-%d %H:%M').astimezone(timezone)
        }

After creating the event, it will be shown correctly inside nextcloud but false via the "date_search()" function :

So the first line was created manually inside the nextcloud GUI.
The second line was created via the "save_event()"-function.

Also completely removing "astimezone()" did not matter, the event was still 2 hours in the future when retrieving via "date_search()".
I "fixed" the problem by using another library -> tzlocal.get_localzone().

For better visualization, the first one is created with "get_localzone()" compared to the second one created with "tz.tzlocal()". Both are pulled from nextcloud at the same time via "date_search()".

{'name': 'created via function', 'start': datetime.datetime(2022, 5, 19, 14, 0, tzinfo=[...] 'Europe/Berlin')), 'end': datetime.datetime(2022, 5, 19, 15, 0 tzinfo=[...] 'Europe/Berlin'))}
-> 2022-05-19 14:00 - 15:00 created via function

{'name': 'event at 14:00 created via function', 'start': datetime.datetime(2022, 5, 19, 14, 0, tzinfo=tzlocal()), 'end': datetime.datetime(2022, 5, 19, 15, 0, tzinfo=tzlocal())}
-> 2022-05-19 16:00 - 17:00 event at 14:00 created via function

Seems like "tzlocal()" is not being called properly hence the "missing" info by tzinfo.
I found a solution for me but others could be having the same issue.

@tobixen
Copy link
Member

tobixen commented May 30, 2022

Sorry for not looking into this yet. Timezones in python is difficult - and at least as for the short term got only more messy after they tried to do things properly in the standard libraries now. And I would even claim you're lucky - some of my unit tests gave a 42 minute offset on the timestamp, all until I upgraded the tzlocal python library :-)

I think that perhaps the datetime.strptime(start, '%Y-%m-%d %H:%M').astimezone(timezone)-construct does not do what you expect it to do and that the bug is not in the python caldav library - but I may be wrong.

While debugging this, first thing to do is to ensure the datetime object you pass into the function is correct. Can you print out new_event["start"] and see if it is what you expect it to be? Next, I'd like to see the ical data sent to the calendar ... I suppose this should work out:

foo = calendars[0].save_event(
                dtstart = new_event["start"], # datetime.datetime object
                dtend = new_event["end"], # datetime.datetime object
                summary = new_event["name"] )
print(foo.data)

@tobixen
Copy link
Member

tobixen commented May 30, 2022

Well, I can probably do some research on my side.

Using python 3.10 and dateutil 2.8.2

from dateutil import tz
import datetime
mytz = tz.tzlocal()
evening = datetime.datetime.strptime('2022-06-01 20:00', '%Y-%m-%d %H:%M').astimezone(mytz)
print(evening)

It gives "2022-06-01 20:00:00+02:00", which is correct for me as well.

from tests.conf import client
myclient = client()
myevent = mycalendar.save_event(
    dtstart = evening, summary = "test")
print(myevent.data)

At this point I'm actually getting an error:

ERROR:root:'CEST'

The output includes this line:

DTSTART;TZID=CEST;VALUE=DATE-TIME:20220601T200000

... which looks correct, except, CEST is not a valid TZID. It should be Europe/Berlin (in your case), not CEST. How this is handled depends on the server ... and eventually, on the other clients accessing the calendar if the server keeps the ical data unchanged.

This seems to be related to collective/icalendar#336 - it's a bug in the icalendar python library that timezones created from the dateutil.tz package aren't supported. In python 3.10 the proper way to create timezone objects is to use the zoneinfo package, which is included in the python standard libraries from 3.6 (if I remember correct).

Unfortunately, as I've reported in collective/icalendar#333 the icalendar library also doesn't support zoneinfo objects, so it probably won't help.

myevent.load()
print(myevent.data)
myevent.delete()

... apparently my calendar server gives me back exactly the same ical data as I saved to it, but different calendars behaves differently.

So I conclude that the caldav library works as it should, but that bug 333 and 336 in the icalendar python is still relevant, I've written up a patch at least for 333 I think.

The workaround is to use the tzlocal library:

from tzlocal import get_localzone
import datetime
from tests.conf import client

mytz = get_localzone()
evening = datetime.datetime.strptime('2022-06-01 20:00', '%Y-%m-%d %H:%M').astimezone(mytz)
print(evening)
myclient = client()
myevent = mycalendar.save_event(
    dtstart = evening, summary = "test")
print(myevent.data)
myevent.delete()

The code above spits out some warnings that the pytz library is deprecated and that the icalendar library depends on some pytz-specific logics, but it works. For me the event data comes with DTSTART;TZID=Europe/Oslo;VALUE=DATE-TIME:20220601T200000 as well as a long blob explicitly explaining the time zone.

I hope this helps, and I will close the issue.

@tobixen tobixen closed this as completed May 30, 2022
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants