taichino / croniter

croniter is a python module to provide iteration for datetime object.
http://github.com/taichino/croniter
387 stars 105 forks source link

Invalid results when crossing DST transition #82

Closed mateuszf closed 7 years ago

mateuszf commented 7 years ago

First of all thanks for fixing #35

Still, there are more issues with local time and DST transition. CRON uses local system time on Linux machines and users expect cron masks to work using local system time. However there are problems with e.g. daily masks when crossing DST transition time. In the simplest scenario I want to use hourly mask 0 * * * * and daily masks 0 0 * * * in local time and users expect them to just work in their local time. Examples using: croniter 0.3.15, Python 3.5.2

import json, calendar, time, pytz
from datetime import datetime
from croniter import croniter

tz = pytz.timezone('Europe/Warsaw')
standard -> DST transition at 2017-03-26 01:59+1:00 -> 03:00+2:00
dt_day1 = tz.localize(datetime(2017, 3, 26))
print(dt_day1)                                            # 2017-03-26 00:00:00+01:00
print(croniter('0 0 * * *', dt_day1).get_next(datetime))  # 2017-03-27 01:00:00+02:00 - INVALID
dt_hour1 = tz.localize(datetime(2017, 3, 26, 1))
print(dt_hour1)                                            # 2017-03-26 01:00:00+01:00
print(croniter('0 * * * *', dt_hour1).get_next(datetime))  # 2017-03-26 03:00:00+02:00 - OK
DST-> standard transition at 2017-10-29 02:59+2:00 -> 02:00+1:00
dt_day1 = tz.localize(datetime(2017, 10, 29))
print(dt_day1)                                            # 2017-10-29 00:00:00+02:00
print(croniter('0 0 * * *', dt_day1).get_next(datetime))  # 2017-10-29 23:00:00+01:00 - INVALID
dt_hour1 = tz.localize(datetime(2017, 10, 29, 2), is_dst=True)
print(dt_hour1)                                            # 2017-10-29 02:00:00+02:00
print(croniter('0 * * * *', dt_hour1).get_next(datetime))  # 2017-10-29 02:00:00+01:00 - OK

The reason this gives invalid results is that croniter converts datetime to timestamp internally, does arithmetics and converts back to datetime. This way information about local time is lost at conversion point and this method gives bad results after converting back to local. The only way to do this work correctly with local time is to do the arithmetics on localized datetime object using timedeltas and tz.normalize(...) - to check if e.g. midnight is still midnight after post-arithmetics normalization.

kiorky commented 7 years ago

Making a test case to explain how to handle this, i ll post you the link

kiorky commented 7 years ago

There is a bug, well, more of what i call a non implementation of DST handling. I will try to do something betteR.

kiorky commented 7 years ago

fixed!

kiorky commented 7 years ago

0.3.16 released on pypi