taichino / croniter Goto Github PK
View Code? Open in Web Editor NEWcroniter is a python module to provide iteration for datetime object.
Home Page: http://github.com/taichino/croniter
croniter is a python module to provide iteration for datetime object.
Home Page: http://github.com/taichino/croniter
here's the error that I am getting :
Traceback (most recent call last):
File "./backup.py", line 363, in
days_to_check.append(iter.get_prev(datetime))
File "./backup.py", line 95, in get_prev
return self._get_next(ret_type, is_prev=True)
File "./backup.py", line 114, in _get_next
result = self._calc(self.cur, expanded, is_prev)
File "./backup.py", line 175, in _calc
raise "failed to find prev date"
TypeError: exceptions must be old-style classes or derived from BaseException, not str
it happen when we set a cron at 10 0 * * 0 using the get_prev
but it only work if we set the time to midnight : 0 0 * * 0
base = datetime(2010, 8, 7, 0, 0)
iter = croniter('0 9 * * mon,tue,wed,thu,fri', base)
print iter.get_next(datetime) # 2010-08-07 09:00:00 => is a saturday
print iter.get_next(datetime) # 2010-08-10 09:00:00 => is a tuesday => where is monday...
Two Issues:
bootstrap.py: error: no such option: -d
-bash: bin/test: No such file or directory
I was following the wiki to develop but encountered issues with running bootstrap as per instructions.
python bootstrap.py -d
Usage: [DESIRED PYTHON FOR BUILDOUT] bootstrap.py [options]
Bootstraps a buildout-based project.
Simply run this script in a directory containing a buildout.cfg, using the
Python that you want bin/buildout to use.
Note that by using --find-links to point to local resources, you can keep
this script from going over the network.
bootstrap.py: error: no such option: -d
So I ignored the option and ran python bootstrap
, which completed fine.
Then followed this by running bin/buildout -vvvvvvN
, which completed just fine.
But I don't see bin/test script in the repo. -bash: bin/test: No such file or directory
This github repository appears to be on release 3.2 but the pypi package is currently on 3.3. Is this repo no longer the master source repo? Can this github repo stay current?
An untested croniter condition fails, the hourly in a month
Ex.
0 * * 3 *
When a test case was added to croniter_test for such a case, the test failed.
By requiring setuptools as part of installation of the package, you're forcing everyone to upgrade their setuptools package. This may be a hold-over from 2011 but setuptools and distutils have now merged into setuptools. Most systems will have it installed. It's also generally frowned upon by the community unless the package itself is a packaging project.
This module does not generate the correct datetime on time shifts during daylight-saving times.
>>> dt = datetime.datetime(2013, 10, 27, 0, 0, 0)
>>> dt_tz = pytz.timezone('Europe/Athens').localize(dt)
>>> dt_tz
datetime.datetime(2013, 10, 27, 0, 0, tzinfo=<DstTzInfo 'Europe/Athens' EEST+3:00:00 DST>)
>>> ct = croniter.croniter('*/30 * * * *', dt_tz)
>>> for i in range(12):
... ct.get_next(datetime.datetime)
...
datetime.datetime(2013, 10, 27, 0, 30)
datetime.datetime(2013, 10, 27, 1, 0)
datetime.datetime(2013, 10, 27, 1, 30)
datetime.datetime(2013, 10, 27, 2, 0)
datetime.datetime(2013, 10, 27, 2, 30)
datetime.datetime(2013, 10, 27, 3, 0)
datetime.datetime(2013, 10, 27, 3, 30)
# <----- missing hour 3:00 - 4:00 in Europe/Athens TZ
datetime.datetime(2013, 10, 27, 4, 0)
datetime.datetime(2013, 10, 27, 4, 30)
datetime.datetime(2013, 10, 27, 5, 0)
datetime.datetime(2013, 10, 27, 5, 30)
datetime.datetime(2013, 10, 27, 6, 0)
>>>
Currently, call croniter.get_next() will return a second later.
Can you upload a .whl for the next release ?
https://zestreleaser.readthedocs.org/en/latest/uploading.html#uploading-wheels
It should raise documented/expected exception like ValueError
for invalid input.
croniter('* * * * 7')
should fail as day of the week is from 0-6 based on http://en.wikipedia.org/wiki/Cron#Format
I created a crontab that should run each monday at 8am, which works perfectly except for the first monday after the DST change in october (the clock is shifted an hour backwards). There is an extra schedule scheduled at 7am (as well as 8am), which you can see in the output.
This is my code
import croniter
import datetime
import pytz
#current time
current_time = datetime.datetime.now(tz=pytz.timezone('Europe/Amsterdam'))
crontab_string = "0 8 * * 1" # every day monday at 0800
# create a job
job = croniter.croniter(crontab_string, current_time)
# print
for i in range(0,200):
print job.get_next(datetime.datetime)
This is my output
....
2017-10-16 08:00:00.685732+02:00
2017-10-23 08:00:00.685732+02:00
2017-10-30 07:00:00.685732+01:00
2017-10-30 08:00:00.685732+01:00
2017-11-06 08:00:00.685732+01:00
2017-11-13 08:00:00.685732+01:00
...
2018-10-15 08:00:00.685732+02:00
2018-10-22 08:00:00.685732+02:00
2018-10-29 07:00:00.685732+01:00
2018-10-29 08:00:00.685732+01:00
2018-11-05 08:00:00.685732+01:00
...
2019-10-07 08:00:00.685732+02:00
2019-10-14 08:00:00.685732+02:00
2019-10-21 08:00:00.685732+02:00
2019-10-28 07:00:00.685732+01:00
2019-10-28 08:00:00.685732+01:00
2019-11-04 08:00:00.685732+01:00
2019-11-11 08:00:00.685732+01:00
This bug doesn't appear when removing the timezone from the current time
Hello,
This is mostly a feature request...
Would it be possible to implement a simple function that would tell if a datetime match a crontab expression ?
In some cases, datetime given in crontiter constructor exactly match the crontab expression. Il that case the behavior needed for the get_next() or get_prev() methods may be ambiguous.
I would like to be able to code something like this :
iter = croniter("0 0 * * *", dtetime)
if iter.match(dtetime):
next = dtetime
else:
next = iter.get_next()
Do you think such a feature could be possible ?
Best regards,
In the croniter.py file, line 89 should be changed from:
for j in xrange(int(low), int(high)+1):
if j % int(step) == 0:
e_list.append(j)
To:
for j in xrange(int(low), int(high)+1, int(step)):
#if j % int(step) == 0:
e_list.append(j)
Because, % returns intervals that are divisible by denominator between the start and end value whereas it should actually return start value plus the interval and next value should be result plus the interval and goes on.
After the change, the result will be like : [[1, 3, 5, 7, 9], [''], [''], [''], ['']]
I think the best way to handle DST transition is to "smear" them. That means:
I think the most important aspect of DST shift handling is that tasks run as often as expected. So a task that is scheduled for a fixed time of day should run every day โ independent of whether a DST shift happened at that day. A task that is scheduled to run once per hour should always run exactly 24 times a day.
An alternative to my suggestion is what cron
is doing:
Local time changes of less than three hours, such as those caused by the Daylight Saving Time changes, are handled in a special way. This only applies to jobs that run at a specific time and jobs that run with a granularity greater than one hour. Jobs that run more frequently are scheduled normally.
If time was adjusted one hour forward, those jobs that would have run in the interval that has been skipped will be run immediately. Conversely, if time was adjusted backward, running the same job twice is avoided.
The smearing method has two advantages:
cron
way, there is an hour in which no tasks will be started.cron
will smush all events that would have happened over an our into a single second, so instead of happening after one another, they all happen at once.If the maintainers agree with this approach, I will prepare a PR. This should fix #90 and #91.
When using an exact date and time without day of week the behavior is the expected:
In [10]: c = croniter('44 17 2 10 * 49', ret_type=datetime)
In [11]: c.get_next()
Out[11]: datetime.datetime(2016, 10, 2, 17, 44, 49)
In [12]: c.get_next()
Out[12]: datetime.datetime(2017, 10, 2, 17, 44, 49)
In [13]: c.get_next()
Out[13]: datetime.datetime(2018, 10, 2, 17, 44, 49)
In [14]: c.get_next()
Out[14]: datetime.datetime(2019, 10, 2, 17, 44, 49)
In [15]: c.get_next()
Out[15]: datetime.datetime(2020, 10, 2, 17, 44, 49)
In [16]: c.get_next()
Out[16]: datetime.datetime(2021, 10, 2, 17, 44, 49)
In [17]: c.get_next()
Out[17]: datetime.datetime(2022, 10, 2, 17, 44, 49)
In [18]: c.get_next()
Out[18]: datetime.datetime(2023, 10, 2, 17, 44, 49)
In [19]: c.get_next()
Out[19]: datetime.datetime(2024, 10, 2, 17, 44, 49)
In [20]: c.get_next()
Out[20]: datetime.datetime(2025, 10, 2, 17, 44, 49)
In [21]: c.get_next()
Out[21]: datetime.datetime(2026, 10, 2, 17, 44, 49)
In [22]: c.get_next()
Out[22]: datetime.datetime(2027, 10, 2, 17, 44, 49)
In [23]: c.get_next()
Out[23]: datetime.datetime(2028, 10, 2, 17, 44, 49)
But, if the DOW is given, the behavior is erratic:
In [5]: c = croniter('44 17 2 10 6 49', ret_type=datetime)
In [6]: c.get_next()
Out[6]: datetime.datetime(2016, 10, 2, 17, 44, 49)
In [7]: c.get_next()
Out[7]: datetime.datetime(2016, 10, 8, 17, 44, 49)
In [8]: c.get_next()
Out[8]: datetime.datetime(2016, 10, 15, 17, 44, 49)
In [9]: c.get_next()
Out[9]: datetime.datetime(2016, 10, 22, 17, 44, 49)
As per http://en.wikipedia.org/wiki/Cron#Format.
Example:
>>> itr = croniter('1 2 L * *', datetime(2013, 10, 1))
>>> print itr.get_next(datetime)
2013-10-31 02:01:00
>>> print itr.get_next(datetime)
2013-11-30 02:01:00
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')
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
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.
I am calculating the last date range based on a cron schedule. The functionality needs to be DST aware, and the cron schedule is specified in "local time", although the range values should be UTC for consistency throughout the rest of the app.
Here's a brief script to illustrate this behaviour
from datetime import datetime
import pytz
from croniter import croniter
def main():
base = '00 00 * * 6'
now = datetime(2017, 10, 3, 2, 07).replace(tzinfo=pytz.UTC)
iter = localtz_iterator(now, base)
last_window_utc(iter)
def localtz_iterator(now, base):
tz = pytz.timezone('Australia/Sydney') # usually coming from some other database field, not
print 'initialising cron-iterator for {}'.format(tz)
reference_localtz = now.astimezone(pytz.timezone('Australia/Sydney'))
print 'local time is {} ({})'.format(reference_localtz, now)
return croniter(base, reference_localtz)
def last_window_utc(iter):
closing_marker = iter.get_prev(datetime)
opening_marker = iter.get_prev(datetime)
print 'opening record marker {} ({})'.format(opening_marker, opening_marker.astimezone(pytz.UTC))
print 'closing record marker {} ({})'.format(closing_marker, closing_marker.astimezone(pytz.UTC))
recalc_closing_marker = iter.get_next(datetime)
assert closing_marker == recalc_closing_marker
return (opening_marker, closing_marker)
if __name__ == '__main__':
main()
So, my date range is expected to go from opening_marker
to closing_marker
- if the assertion is commented out, the closing marker is at 23:00 (i.e. 11pm) local time, even though the cron schedule is explicit that it should be at midnight.
The recalc_closing_marker
produces the correct value, by moving the marker forward once again...
All the same, thanks for an awesome module - this exactly fills the requirements I have in my project!
croniter('* * 0 * *')
gives object that considers day of the month to be 1. I believe it should fail allowing entry between 1 to 31 only.
I am trying to run a job every 5 sec by adding a 6th *. Not sure if it is supposed to work
from croniter import croniter
from datetime import datetime
base = datetime(2010, 1, 25, 4, 46)
iter = croniter('*/5 * * * * *', base) # every 5 sec
print iter.get_next(datetime) # Should return 2010-01-25 04:46:05 but returns 2010-01-25 04:50:00
print iter.get_next(datetime) # Should return 2010-01-25 04:46:10 but returns 2010-01-25 04:50:01
print iter.get_next(datetime) # Should return 2010-01-25 04:46:15 but returns 2010-01-25 04:50:02
Am I missing something here?
On line 62 of croniter.py there is a greater than comparison between two string integers (low > high
). When a range such as '5-15 * * * *' is passed in to croniter
this condition returns False
and thus ValueError("[%s] is not acceptable" % expr_format)
is raised.
I fixed this locally with int(low) > int(high)
, but I haven't tested if this itself might raise a ValueError in some cases.
croniter will not raise erorr , but it will be error when i call get_next.
The most important: this crontab string will cause a long loop before the error happened.
I want to execute a job at 6 AM on 2nd, Feb 2018
only once. The corresponding cron expression would be "0 6 2 2 * 0 2018"
.
In croniter, seconds is the 6th part of the cron expression. But I am getting CroniterBadError
when I try to provide year
also. Is there any way to accomplish this. Or this might be a new feature request.
Hi,
the following test fails with ValueError: [30 22 * * sun-thu] is not acceptable:
Cron accepts this format.
import unittest
from croniter import croniter
from datetime import datetime
class CroniterTest(unittest.TestCase):
def sunday_to_thursday(self):
base = datetime.today()
exp = "30 22 * * sun-thu"
my_iter = croniter(exp, base)
next_execution = my_iter.get_next(datetime)
print(next_execution.time())
if __name__ == '__main__':
unittest.main()
This example shows the issue:
from croniter.croniter import croniter
from datetime import datetime
import pytz
tz = pytz.timezone('Europe/Vienna')
def test_croniter(start_time, repeat=40):
start_time = tz.localize(start_time)
print("-" * 100)
print(f"start_time = {str(start_time)}")
print("-" * 100)
test_cron = croniter('30 * * * *', start_time=start_time)
for i in range(repeat):
print(test_cron.get_next(datetime))
print("-" * 100)
# First test: cron timestamps start repeating after the DST change from 03:00 AM back to 02:00 AM
test_croniter(datetime(2017, 10, 28, 10, 9))
# Second test: It get's even weirder when the start_time is at the same day as the DST change,
# because the timestamps start repeating at around 21:00 (i.e. 09:00 PM)
test_croniter(datetime(2017, 10, 29, 0, 5))
# Third test: Here's another test that shows repeating timestamps after a DST change
# that changes the clock from 02:00 AM to 03:00 AM
test_croniter(datetime(2017, 3, 25, 20, 5))
It just creates some croniter instances with different start_time values that are all around this year's DST changes in Europe and then repeatedly calls get_next on them and prints the returned datetime objects.
Just for background information:
Europe always switches from standard time to DST at 02:00 AM (switching to 03:00 AM) on the last Sunday of March (was the 26th this year).
The switch from DST back to standard time then happens at 03:00 AM (back to 02:00 AM) on the last Sunday of October (was the 29th this year).
The first test_croniter call produces output like this:
----------------------------------------------------------------------------------------------------
start_time = 2017-10-28 10:09:00+02:00
----------------------------------------------------------------------------------------------------
2017-10-28 10:30:00+02:00
2017-10-28 11:30:00+02:00
...
2017-10-28 23:30:00+02:00
2017-10-29 00:30:00+02:00
2017-10-29 01:30:00+02:00
2017-10-29 02:30:00+02:00
2017-10-29 01:30:00+01:00
2017-10-29 01:30:00+01:00
2017-10-29 01:30:00+01:00
2017-10-29 01:30:00+01:00
... (stays the same after that)
Here's the second one:
----------------------------------------------------------------------------------------------------
start_time = 2017-10-29 00:05:00+02:00
----------------------------------------------------------------------------------------------------
2017-10-29 00:30:00+02:00
2017-10-29 01:30:00+02:00
2017-10-29 02:30:00+02:00
2017-10-29 02:30:00+01:00
2017-10-29 03:30:00+01:00
2017-10-29 04:30:00+01:00
...
2017-10-29 20:30:00+01:00
2017-10-29 21:30:00+01:00
2017-10-29 21:30:00+01:00
2017-10-29 21:30:00+01:00
2017-10-29 21:30:00+01:00
... (stays the same after that)
And here the third one:
----------------------------------------------------------------------------------------------------
start_time = 2017-03-25 20:05:00+01:00
----------------------------------------------------------------------------------------------------
2017-03-25 20:30:00+01:00
2017-03-25 21:30:00+01:00
2017-03-25 22:30:00+01:00
2017-03-25 23:30:00+01:00
2017-03-26 00:30:00+01:00
2017-03-26 01:30:00+01:00
2017-03-26 02:30:00+02:00
2017-03-26 02:30:00+02:00
2017-03-26 02:30:00+02:00
2017-03-26 02:30:00+02:00
... (stays the same after that)
This is also not limited to the "Europe/Vienna" time zone. The issue also comes up when using "Europe/Kiev", "Europe/Lisbon", "Europe/London", ...
Tested against croniter 0.3.19
a=croniter("*/5 * * * *", datetime.datetime(2012, 04, 01, 20, 25, 00))
a.get_prev(datetime.datetime)
datetime.datetime(2012, 4, 1, 20, 20)a=croniter("*/5 * * * *", datetime.datetime(2012, 04, 01, 20, 26, 01))
a.get_prev(datetime.datetime)
datetime.datetime(2012, 4, 1, 20, 25)a=croniter("0 * * * *", datetime.datetime(2012, 04, 01, 20, 00, 59))
a.get_prev(datetime.datetime)
datetime.datetime(2012, 4, 1, 19, 0)a=croniter("0 * * * *", datetime.datetime(2012, 04, 01, 20, 01, 00))
a.get_prev(datetime.datetime)
datetime.datetime(2012, 4, 1, 20, 0)
as linux crontab instance
when i set cron as "*/5 * * * *
and the time is datetime.datetime(2012, 04, 01, 20, 25, 00)
i think it may better to retun get_prev as datetime.datetime(2012, 4, 1, 20, 25)
but not datetime.datetime(2012, 4, 1, 20, 20) ?
or, i have the wrong usage? thanks
Hi guys!
Found issue with specific cron syntax. Could anybody help me with that?
croniter==0.3.16
run on Python2.7
croniter('0 0 29-31 * 3-4', day_or=False).get_prev(datetime.datetime)
It will go in infinite loop.
Hi I am trying to get cron for "every month on first sunday" using following cron 0 0 * */1 0#1 * which give me key error at "0#1"
man 5 crontab:
field allowed values
----- --------------
minute 0-59
hour 0-23
day of month 1-31
month 1-12 (or names, see below)
day of week 0-7 (0 or 7 is Sun, or use names)
but when use croniter:
>>> from croniter import croniter
>>> c = croniter('15 16 12 12 3')
>>> c.get_next()
1417594500.0
>>> c.get_next()
1418199300.0
>>> import datetime
>>> datetime.datetime.fromtimestamp(1417594500)
datetime.datetime(2014, 12, 3, 16, 15)
>>> datetime.datetime(2014, 12, 10, 16, 15)
datetime.datetime(2014, 12, 10, 16, 15)
>>>
Hello,
I am trying to get next date, but this method return it a previous date to the current date
Example
cron = croniter.croniter("%s %s %s %s %s" % (x.recurrence.minute, x.recurrence.hour, x.recurrence.day, x.recurrence.month, x.recurrence.weekday), x.recurrence.datestart)
cron.get_next() -> This method return a previous date to the current date
datestart : 2015-09-18 15:40:00
get_next: 2015-09-18 15:41:00
System:
$ lsb_release -a
No LSB modules are available.
Distributor ID: Ubuntu
Description: Ubuntu 16.04.3 LTS
Release: 16.04
Codename: xenial
Versions:
$ python --version
Python 3.5.2
$ pip list | grep croniter
croniter (0.3.20)
Test code (name it test.py
):
from croniter import croniter
from datetime import datetime
import pandas as pd
import tzlocal
import pytz
import sys
local = pytz.timezone('America/Los_Angeles')
print(local)
c = croniter('* * * * *', datetime(2017, 3, 12, tzinfo=local))
s = set()
l = list()
while True:
t = c.get_next(datetime)
ms = int(t.timestamp() * 1000)
if ms >= 1489309140000:
s.add(ms)
l.append(ms)
print(t, ms)
if ms >= 1489312800000:
break
print(len(s))
print(len(l))
assert(len(s) == len(l))
Expected behavior:
Observed behavior:
Observed console output:
$ python test.py
America/Los_Angeles
2017-03-12 00:59:00-08:00 1489309140000
2017-03-12 01:00:00-08:00 1489309200000
2017-03-12 01:01:00-08:00 1489309260000
2017-03-12 01:02:00-08:00 1489309320000
2017-03-12 01:03:00-08:00 1489309380000
2017-03-12 01:04:00-08:00 1489309440000
2017-03-12 01:05:00-08:00 1489309500000
2017-03-12 01:06:00-08:00 1489309560000
2017-03-12 01:07:00-08:00 1489309620000
2017-03-12 01:08:00-08:00 1489309680000
2017-03-12 01:09:00-08:00 1489309740000
2017-03-12 01:10:00-08:00 1489309800000
2017-03-12 01:11:00-08:00 1489309860000
2017-03-12 01:12:00-08:00 1489309920000
2017-03-12 01:13:00-08:00 1489309980000
2017-03-12 01:14:00-08:00 1489310040000
2017-03-12 01:15:00-08:00 1489310100000
2017-03-12 01:16:00-08:00 1489310160000
2017-03-12 01:17:00-08:00 1489310220000
2017-03-12 01:18:00-08:00 1489310280000
2017-03-12 01:19:00-08:00 1489310340000
2017-03-12 01:20:00-08:00 1489310400000
2017-03-12 01:21:00-08:00 1489310460000
2017-03-12 01:22:00-08:00 1489310520000
2017-03-12 01:23:00-08:00 1489310580000
2017-03-12 01:24:00-08:00 1489310640000
2017-03-12 01:25:00-08:00 1489310700000
2017-03-12 01:26:00-08:00 1489310760000
2017-03-12 01:27:00-08:00 1489310820000
2017-03-12 01:28:00-08:00 1489310880000
2017-03-12 01:29:00-08:00 1489310940000
2017-03-12 01:30:00-08:00 1489311000000
2017-03-12 01:31:00-08:00 1489311060000
2017-03-12 01:32:00-08:00 1489311120000
2017-03-12 01:33:00-08:00 1489311180000
2017-03-12 01:34:00-08:00 1489311240000
2017-03-12 01:35:00-08:00 1489311300000
2017-03-12 01:36:00-08:00 1489311360000
2017-03-12 01:37:00-08:00 1489311420000
2017-03-12 01:38:00-08:00 1489311480000
2017-03-12 01:39:00-08:00 1489311540000
2017-03-12 01:40:00-08:00 1489311600000
2017-03-12 01:41:00-08:00 1489311660000
2017-03-12 01:42:00-08:00 1489311720000
2017-03-12 01:43:00-08:00 1489311780000
2017-03-12 01:44:00-08:00 1489311840000
2017-03-12 01:45:00-08:00 1489311900000
2017-03-12 01:46:00-08:00 1489311960000
2017-03-12 01:47:00-08:00 1489312020000
2017-03-12 01:48:00-08:00 1489312080000
2017-03-12 01:49:00-08:00 1489312140000
2017-03-12 01:50:00-08:00 1489312200000
2017-03-12 01:51:00-08:00 1489312260000
2017-03-12 01:52:00-08:00 1489312320000
2017-03-12 01:53:00-08:00 1489312380000
2017-03-12 01:54:00-08:00 1489312440000
2017-03-12 01:55:00-08:00 1489312500000
2017-03-12 01:56:00-08:00 1489312560000
2017-03-12 01:57:00-08:00 1489312620000
2017-03-12 01:58:00-08:00 1489312680000
2017-03-12 01:59:00-08:00 1489312740000
2017-03-12 02:00:00-07:00 1489309200000
2017-03-12 01:01:00-08:00 1489309260000
2017-03-12 01:02:00-08:00 1489309320000
2017-03-12 01:03:00-08:00 1489309380000
2017-03-12 01:04:00-08:00 1489309440000
2017-03-12 01:05:00-08:00 1489309500000
2017-03-12 01:06:00-08:00 1489309560000
2017-03-12 01:07:00-08:00 1489309620000
2017-03-12 01:08:00-08:00 1489309680000
2017-03-12 01:09:00-08:00 1489309740000
2017-03-12 01:10:00-08:00 1489309800000
2017-03-12 01:11:00-08:00 1489309860000
2017-03-12 01:12:00-08:00 1489309920000
2017-03-12 01:13:00-08:00 1489309980000
2017-03-12 01:14:00-08:00 1489310040000
2017-03-12 01:15:00-08:00 1489310100000
2017-03-12 01:16:00-08:00 1489310160000
2017-03-12 01:17:00-08:00 1489310220000
2017-03-12 01:18:00-08:00 1489310280000
2017-03-12 01:19:00-08:00 1489310340000
2017-03-12 01:20:00-08:00 1489310400000
2017-03-12 01:21:00-08:00 1489310460000
2017-03-12 01:22:00-08:00 1489310520000
2017-03-12 01:23:00-08:00 1489310580000
2017-03-12 01:24:00-08:00 1489310640000
2017-03-12 01:25:00-08:00 1489310700000
2017-03-12 01:26:00-08:00 1489310760000
2017-03-12 01:27:00-08:00 1489310820000
2017-03-12 01:28:00-08:00 1489310880000
2017-03-12 01:29:00-08:00 1489310940000
2017-03-12 01:30:00-08:00 1489311000000
2017-03-12 01:31:00-08:00 1489311060000
2017-03-12 01:32:00-08:00 1489311120000
2017-03-12 01:33:00-08:00 1489311180000
2017-03-12 01:34:00-08:00 1489311240000
2017-03-12 01:35:00-08:00 1489311300000
2017-03-12 01:36:00-08:00 1489311360000
2017-03-12 01:37:00-08:00 1489311420000
2017-03-12 01:38:00-08:00 1489311480000
2017-03-12 01:39:00-08:00 1489311540000
2017-03-12 01:40:00-08:00 1489311600000
2017-03-12 01:41:00-08:00 1489311660000
2017-03-12 01:42:00-08:00 1489311720000
2017-03-12 01:43:00-08:00 1489311780000
2017-03-12 01:44:00-08:00 1489311840000
2017-03-12 01:45:00-08:00 1489311900000
2017-03-12 01:46:00-08:00 1489311960000
2017-03-12 01:47:00-08:00 1489312020000
2017-03-12 01:48:00-08:00 1489312080000
2017-03-12 01:49:00-08:00 1489312140000
2017-03-12 01:50:00-08:00 1489312200000
2017-03-12 01:51:00-08:00 1489312260000
2017-03-12 01:52:00-08:00 1489312320000
2017-03-12 01:53:00-08:00 1489312380000
2017-03-12 01:54:00-08:00 1489312440000
2017-03-12 01:55:00-08:00 1489312500000
2017-03-12 01:56:00-08:00 1489312560000
2017-03-12 01:57:00-08:00 1489312620000
2017-03-12 01:58:00-08:00 1489312680000
2017-03-12 01:59:00-08:00 1489312740000
2017-03-12 03:00:00-07:00 1489312800000
62
122
Traceback (most recent call last):
File "test.py", line 26, in <module>
assert(len(s) == len(l))
AssertionError
Notice that every minute between 2017-03-12 01:00:00-08:00
and 2017-03-12 01:59:00-08:00
are repeated once, apparently at the hour of daylight saving time switch-over.
Hi, we are using croniter at our project for IT inventory at github.com/stic-ull/arritranco
The thing is that all our backups scheduled on days 29 and 30 failed to schedule today.
Look what happens:
datetime.now() >> datetime.datetime(2013, 3, 1, 12, 17, 34, 257877)
c = croniter('00 03 16,30 * *',datetime.now())
c.get_next(datetime) >>>> datetime.datetime(2013, 3, 16, 3, 0)
c.get_next(datetime) >>>> datetime.datetime(2013, 3, 30, 3, 0)
c.get_next(datetime) >>>> datetime.datetime(2013, 4, 16, 3, 0)
Then i go backwards:
c.get_prev(datetime) >>>> datetime.datetime(2013, 3, 30, 3, 0)
c.get_prev(datetime) >>>> datetime.datetime(2013, 3, 16, 3, 0)
c.get_prev(datetime) >>>> datetime.datetime(2013, 3, 2, 3, 0)
c.get_prev(datetime) >>>> datetime.datetime(2013, 3, 2, 3, 0)
c.get_prev(datetime) >>>> datetime.datetime(2013, 3, 2, 3, 0)
c.get_prev(datetime) >>>> datetime.datetime(2013, 3, 2, 3, 0)
Its get stalled at these date, that is, incorrect.
It doesn't look like travis is enabled, so builds aren't actually running for pull requests.
I wish to use: '0 0 * * 1-5'
but it gives the error:
File "/usr/local/lib/python2.7/dist-packages/croniter/croniter.py", line 69, in __init__
raise ValueError(self.bad_length)
ValueError: Exactly 5 or 6 columns has to be specified for iteratorexpression.
>>> '0 0 * * 1-5'.split()
['0', '0', '*', '*', '1-5']
I don't understand what's wrong here.
In [61]: croniter.croniter('32 16 * * *', datetime.datetime(2018, 5, 29, 16, 32, 0), ret_type=datetime.datetime).get_next()
Out[61]: datetime.datetime(2018, 5, 30, 16, 32)
In [62]: croniter.croniter('32 16 * * *', datetime.datetime(2018, 5, 29, 16, 32, 1), ret_type=datetime.datetime).get_next()
Out[62]: datetime.datetime(2018, 5, 29, 16, 32)
The second command should return the same value as the first one.
Try falsy expression 0 * * * * *
in is_valid function and it returns True
croniter 0.3.5, Python 2.7.8
Consider test case executed on machine with 'Europe/Warsaw' (UTC+1) timezone, change from DST to normal occurs on 2014-10-26 02:59 +2:00 (UTC 00:59) -> 02:00 +1:00 (UTC 01:00):
from datetime import datetime
from croniter import croniter
import pytz
cr = croniter('0 * * * *', datetime(2014, 10, 26, 0))
print 'next = ', cr.get_next(datetime) # 2014-10-26 01:00:00, OK
print 'prev = ', cr.get_prev(datetime) # 2014-10-26 00:00:00, OK
cr = croniter('0 * * * *', datetime(2014, 10, 26, 0).replace(tzinfo=pytz.utc))
print 'next = ', cr.get_next(datetime) # 2014-10-26 02:00:00+00:00, INVALID
print 'prev = ', cr.get_prev(datetime) # 2014-10-26 02:00:00+00:00, INVALID
cr = croniter('0 * * * *', datetime(2014, 10, 26, 1))
print 'next = ', cr.get_next(datetime) # 2014-10-26 02:00:00, OK
print 'prev = ', cr.get_prev(datetime) # 2014-10-26 02:00:00, INVALID
cr = croniter('0 * * * *', datetime(2014, 10, 26, 1).replace(tzinfo=pytz.utc))
print 'next = ', cr.get_next(datetime) # 2014-10-26 03:00:00+00:00, INVALID
print 'prev = ', cr.get_prev(datetime) # 2014-10-26 02:00:00+00:00, INVALID
cr = croniter('0 * * * *', 1414281600) # UTC 00:00
print 'next = ', cr.get_next() # 1414288800.0 (UTC 02:00), INVALID
print 'prev = ', cr.get_prev() # 1414285200.0 (UTC 01:00), INVALID
cr = croniter('0 * * * *', 1414285200) # UTC 01:00
print 'next = ', cr.get_next() # 1414288800.0 (UTC 02:00), OK
print 'prev = ', cr.get_prev() # 1414285200.0 (UTC 01:00), OK
Despite the fact that timestamps are UTC hardcoded all of test cases give much better results when system timezone is changed to e.g. Fiji UTC+12:00
I think this is related to #96.
The following fails at 2019-03-31 01:00:00+01:00 when going from winter- to summer-time?
I know the value should be 0-59 but it passes as valid.
import datetime
import dateutil
from croniter import croniter
TZ = dateutil.tz.gettz('Europe/Berlin')
start_time = datetime.datetime(year=2018, month=5, day=10, hour=0, minute=0, second=0, microsecond=0, tzinfo=TZ)
cron = '*/60 * * * *'
print('chron string is valid:', croniter.is_valid(cron))
_iter = croniter(expr_format=cron, start_time=start_time, ret_type=datetime.datetime, day_or=True)
last = next(_iter)
i = 0
for current in _iter:
if last == current:
print('fail', i, current)
break
last = current
i += 1
Output:
chron string is valid: True
fail 7799 2019-03-31 01:00:00+01:00
The following expression gives an exception with 0.3.11:
from croniter import croniter
from datetime import datetime
croniter("0 9 29 2 *", datetime(2016, 2, 29, 9, 0)).get_next(datetime)
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "C:\Python27\lib\site-packages\croniter\croniter.py", line 141, in get_next
return self._get_next(ret_type, is_prev=False)
File "C:\Python27\lib\site-packages\croniter\croniter.py", line 224, in _get_next
result = self._calc(self.cur, expanded, is_prev)
File "C:\Python27\lib\site-packages\croniter\croniter.py", line 362, in _calc
raise Exception("failed to find prev date")
Exception: failed to find prev date
The same in 0.3.8 worked, gave datetime.datetime(2020, 2, 29, 9, 0)
as the answer.
In croniter.py the function timedelta.total_seconds
is used. This function was added in 2.7 so support for 2.6 is broken by using this.
@petervtzand, I believe you mentioned that there's a simple fallback ((td.microseconds + (td.seconds + td.days * 24 * 3600) * 10**6) / 10**6
)?
This is a valid cron expression. http://crontab.guru/#0_4/6___*
>>> iter = croniter('0 4/6 * * *', base)
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "/usr/local/lib/python2.7/site-packages/croniter/croniter.py", line 113, in __init__
t = self.ALPHACONV[i][t.lower()]
KeyError: '4/6'
In 0.3.20
version, the croniter.get_next()
returns correct value.
>>> from datetime import datetime
>>> import croniter
>>>
>>> dt = datetime.strptime('2018/05/15 09:00:30+0000', '%Y/%m/%d %H:%M:%S%z')
>>> print(dt)
# outputs: 2018-05-15 09:00:30+00:00
>>> print(croniter.croniter('0,30 * * * *', dt).get_next(datetime))
# outputs: 2018-05-15 09:30:00+00:00
However, with 0.3.22
, the croniter.get_next()
returns incorrect value. Calling get_next() at 9:00:30
time with 0,30 * * * *
cron returns 9:00:00
:
>>> from datetime import datetime
>>> import croniter
>>>
>>> dt = datetime.strptime('2018/05/15 09:00:30+0000', '%Y/%m/%d %H:%M:%S%z')
>>> print(dt)
# outputs: 2018-05-15 09:00:30+00:00
>>> print(croniter.croniter('0,30 * * * *', dt).get_next(datetime))
# outputs: 2018-05-15 09:00:00+00:00
Hi, when i try, if it is proper solution fo mre, i find this:
base = datetime(2014, 6, 10, 12, 0, 10)
iter = croniter('0 */2 * * *', base)
print "current: %s" % iter.get_current(datetime).time()
iter = croniter('0 */2 * * *', base)
print "prev: %s" % iter.get_prev(datetime).time()
iter = croniter('0 */2 * * *', base)
print "next: %s" % iter.get_next(datetime).time()
Which prints:
current: 12:00:10
prev: 10:00:00
next: 14:00:00
When base time is 12:00:10, i expect that the get_prev() returns the "12:00:00", not the "10:00:00". When the base time is changed to "12:01:10" all is as expected:
current: 12:01:10
prev: 12:00:00
next: 14:00:00
To i get really previous time (and date) of the execution, i need to use:
iter.get_next(datetime)
print "prev: %s" % iter.get_prev(datetime).time()
regards
The entry setuptools
entry in the install_requires
parameter of setup()
in setup.py
isn't needed and actual counter productive when using different package solution such as Distribute (a successor of setuptools). I recommend removing it.
Consider the following example:
import pytz
from datetime import datetime
from croniter import croniter
tz = pytz.timezone("America/Sao_Paulo")
local_dates = [tz.localize(datetime(2018, 2, 17, 0, 0, 0)),
tz.localize(datetime(2018, 2, 17, 1, 0, 0)),
tz.localize(datetime(2018, 2, 17, 2, 0, 0)),
tz.localize(datetime(2018, 2, 17, 3, 0, 0))]
[print(d, '=>', croniter("0 0 * * *", d).get_next(datetime)) for d in local_dates]
Output:
2018-02-17 00:00:00-02:00 => 2018-02-18 00:00:00-03:00
2018-02-17 01:00:00-02:00 => 2018-02-18 00:00:00-03:00
2018-02-17 02:00:00-02:00 => 2018-02-18 00:00:00-03:00
2018-02-17 03:00:00-02:00 => 2018-02-18 00:00:00-03:00
-This is correct (note the DST changes).
Now lets add 5 minutes to each local date...
local_dates = [tz.localize(datetime(2018, 2, 17, 0, 5, 0)),
tz.localize(datetime(2018, 2, 17, 1, 5, 0)),
tz.localize(datetime(2018, 2, 17, 2, 5, 0)),
tz.localize(datetime(2018, 2, 17, 3, 5, 0))]
[print(d, '=>', croniter("0 0 * * *", d).get_next(datetime)) for d in local_dates]
Output:
2018-02-17 00:05:00-02:00 => 2018-02-17 23:00:00-03:00
2018-02-17 01:05:00-02:00 => 2018-02-17 23:00:00-03:00
2018-02-17 02:05:00-02:00 => 2018-02-17 23:00:00-03:00
2018-02-17 03:05:00-02:00 => 2018-02-17 23:00:00-03:00
-This is clearly a BUG!
Just add 1 second, or even 1 microseconds and it brakes.
Note, that DST in Brazil changes on Feb 18th 2018 at midnight 00:00:00.
The problem disappears for another timezones, e.g., its all good for 2017-03-26 00:05 in 'Europe/Warsaw'. I suggest adding test cases like this one for other timezones as well.
>> croniter.croniter("*/15 * * * *", datetime(2017, 10, 23, 14, 50)).get_next(ret_type=datetime)
datetime.datetime(2017, 10, 23, 15, 0)
croniter==0.3.20
Version: 0.3.23
System: macOS 10.12.6
Python: 2.7.10
I tested croniter in a very time sensitive application and discovered that get_prev() is pointing to the wrong date in a period of one second after the cron time.
Example:
date = datetime.datetime(
year=2018,
month=1,
day=2,
hour=10,
minute=0,
second=0,
microsecond=0
)
c = croniter.croniter("0 10 * * * ", start_time=date)
print(datetime.datetime.utcfromtimestamp(c.get_prev()))
--> 2018-01-01 10:00:00
croniter is pointing to the date from the day before.
date = datetime.datetime(
year=2018,
month=1,
day=2,
hour=10,
minute=0,
second=0,
microsecond=500000
)
c = croniter.croniter("0 10 * * * ", start_time=date)
print(datetime.datetime.utcfromtimestamp(c.get_prev()))
--> 2018-01-01 10:00:00
croniter is still pointing to the day before, but it should point to 2018-01-02 10:00:00
date = datetime.datetime(
year=2018,
month=1,
day=2,
hour=10,
minute=0,
second=1,
microsecond=0
)
c = croniter.croniter("0 10 * * * ", start_time=date)
print(datetime.datetime.utcfromtimestamp(c.get_prev()))
--> 2018-01-02 10:00:00
Not before 1 second after the date croniter switches to the right one.
Is it possible to increase the time resolution to a millisecond base?
Kind regards
Nils
Hi tanks for the fix.
I've found another bug with the get prev :
5 0 */2 * * every 2 days the script froze.
A declarative, efficient, and flexible JavaScript library for building user interfaces.
๐ Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. ๐๐๐
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google โค๏ธ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.