croniter provides iteration for datetime object with cron like format
Project description
Introduction
croniter provides iteration for the datetime object with a cron like format.
_ _ ___ _ __ ___ _ __ (_) |_ ___ _ __ / __| '__/ _ \| '_ \| | __/ _ \ '__| | (__| | | (_) | | | | | || __/ | \___|_| \___/|_| |_|_|\__\___|_|
Website: https://github.com/kiorky/croniter
Travis badge
Usage
A simple example:
>>> from croniter import croniter >>> from datetime import datetime >>> base = datetime(2010, 1, 25, 4, 46) >>> iter = croniter('*/5 * * * *', base) # every 5 minutes >>> print(iter.get_next(datetime)) # 2010-01-25 04:50:00 >>> print(iter.get_next(datetime)) # 2010-01-25 04:55:00 >>> print(iter.get_next(datetime)) # 2010-01-25 05:00:00 >>> >>> iter = croniter('2 4 * * mon,fri', base) # 04:02 on every Monday and Friday >>> print(iter.get_next(datetime)) # 2010-01-26 04:02:00 >>> print(iter.get_next(datetime)) # 2010-01-30 04:02:00 >>> print(iter.get_next(datetime)) # 2010-02-02 04:02:00 >>> >>> iter = croniter('2 4 1 * wed', base) # 04:02 on every Wednesday OR on 1st day of month >>> print(iter.get_next(datetime)) # 2010-01-27 04:02:00 >>> print(iter.get_next(datetime)) # 2010-02-01 04:02:00 >>> print(iter.get_next(datetime)) # 2010-02-03 04:02:00 >>> >>> iter = croniter('2 4 1 * wed', base, day_or=False) # 04:02 on every 1st day of the month if it is a Wednesday >>> print(iter.get_next(datetime)) # 2010-09-01 04:02:00 >>> print(iter.get_next(datetime)) # 2010-12-01 04:02:00 >>> print(iter.get_next(datetime)) # 2011-06-01 04:02:00 >>> iter = croniter('0 0 * * sat#1,sun#2', base) >>> print(iter.get_next(datetime)) # datetime.datetime(2010, 2, 6, 0, 0)
All you need to know is how to use the constructor and the get_next method, the signature of these methods are listed below:
>>> def __init__(self, cron_format, start_time=time.time(), day_or=True)
croniter iterates along with cron_format from start_time. cron_format is min hour day month day_of_week, you can refer to http://en.wikipedia.org/wiki/Cron for more details. The day_or switch is used to control how croniter handles day and day_of_week entries. Default option is the cron behaviour, which connects those values using OR. If the switch is set to False, the values are connected using AND. This behaves like fcron and enables you to e.g. define a job that executes each 2nd friday of a month by setting the days of month and the weekday.
>>> def get_next(self, ret_type=float)
get_next calculates the next value according to the cron expression and returns an object of type ret_type. ret_type should be a float or a datetime object.
Supported added for get_prev method. (>= 0.2.0):
>>> base = datetime(2010, 8, 25) >>> itr = croniter('0 0 1 * *', base) >>> print(itr.get_prev(datetime)) # 2010-08-01 00:00:00 >>> print(itr.get_prev(datetime)) # 2010-07-01 00:00:00 >>> print(itr.get_prev(datetime)) # 2010-06-01 00:00:00
You can validate your crons using is_valid class method. (>= 0.3.18):
>>> croniter.is_valid('0 0 1 * *') # True >>> croniter.is_valid('0 wrong_value 1 * *') # False
About DST
Be sure to init your croniter instance with a TZ aware datetime for this to work!
Example using pytz:
>>> import pytz >>> tz = pytz.timezone("Europe/Paris") >>> local_date = tz.localize(datetime(2017, 3, 26)) >>> val = croniter('0 0 * * *', local_date).get_next(datetime)
Example using python_dateutil:
>>> import dateutil.tz >>> tz = dateutil.tz.gettz('Asia/Tokyo') >>> local_date = datetime(2017, 3, 26, tzinfo=tz) >>> val = croniter('0 0 * * *', local_date).get_next(datetime)
About second repeats
Croniter is able to do second repeatition crontabs form:
>>> croniter('* * * * * 1', local_date).get_next(datetime) >>> base = datetime(2012, 4, 6, 13, 26, 10) >>> itr = croniter('* * * * * 15,25', base) >>> itr.get_next(datetime) # 4/6 13:26:15 >>> itr.get_next(datetime) # 4/6 13:26:25 >>> itr.get_next(datetime) # 4/6 13:27:15
You can also note that this expression will repeat every second from the start datetime.:
>>> croniter('* * * * * *', local_date).get_next(datetime)
Testing if a date matchs a crontab
Test for a match with (>=0.3.32):
>>> croniter.match("0 0 * * *", datetime(2019, 1, 14, 0, 0, 0, 0)) True >>> croniter.match("0 0 * * *", datetime(2019, 1, 14, 0, 2, 0, 0)) False >>> >>> croniter.match("2 4 1 * wed", datetime(2019, 1, 1, 4, 2, 0, 0)) # 04:02 on every Wednesday OR on 1st day of month True >>> croniter.match("2 4 1 * wed", datetime(2019, 1, 1, 4, 2, 0, 0), day_or=False) # 04:02 on every 1st day of the month if it is a Wednesday False
Gaps between date matches
For performance reasons, croniter limits the amount of CPU cycles spent attempting to find the next match. Starting in v0.3.35, this behavior is configurable via the max_years_between_matches parameter, and the default window has been increased from 1 year to 50 years.
The defaults should be fine for many use cases. Applications that evaluate multiple cron expressions or handle cron expressions from untrusted sources or end-users should use this parameter. Iterating over sparse cron expressions can result in increased CPU consumption or a raised CroniterBadDateError exception which indicates that croniter has given up attempting to find the next (or previous) match. Explicitly specifying max_years_between_matches provides a way to limit CPU utilization and simplifies the iterable interface by eliminating the need for CroniterBadDateError. The difference in the iterable interface is based on the reasoning that whenever max_years_between_matches is explicitly agreed upon, there is no need for croniter to signal that it has given up; simply stopping the iteration is preferable.
This example matches 4 AM Friday, January 1st. Since January 1st isn’t often a Friday, there may be a few years between each occurrence. Setting the limit to 15 years ensures all matches:
>>> it = croniter("0 4 1 1 fri", datetime(2000,1,1), day_or=False, max_years_between_matches=15).all_next(datetime) >>> for i in range(5): ... print(next(it)) ... 2010-01-01 04:00:00 2016-01-01 04:00:00 2021-01-01 04:00:00 2027-01-01 04:00:00 2038-01-01 04:00:00
However, when only concerned with dates within the next 5 years, simply set max_years_between_matches=5 in the above example. This will result in no matches found, but no additional cycles will be wasted on unwanted matches far in the future.
Iterating over a range using cron
Find matches within a range using the croniter_range() function. This is much like the builtin range(start,stop,step) function, but for dates. The step argument is a cron expression. Added in (>=0.3.34)
List the first Saturday of every month in 2019:
>>> from croniter import croniter_range >>> for dt in croniter_range(datetime(2019, 1, 1), datetime(2019, 12, 31), "0 0 * * sat#1"): >>> print(dt)
Develop this package
git clone https://github.com/kiorky/croniter.git cd croniter virtualenv --no-site-packages venv . venv/bin/activate pip install --upgrade -r requirements/test.txt py.test src
Make a new release
We use zest.fullreleaser, a great release infrastructure.
Do and follow these instructions
. venv/bin/activate pip install --upgrade -r requirements/release.txt ./release.sh
Contributors
Thanks to all who have contributed to this project! If you have contributed and your name is not listed below please let me know.
mrmachine
Hinnack
shazow
kiorky
jlsandell
mag009
djmitche
GreatCombinator
chris-baynes
ipartola
yuzawa-san
lowell80 (Kintyre)
scop
Changelog
0.3.37 (2020-12-31)
Added Python 3.8 and 3.9 support [eumiro]
0.3.36 (2020-11-02)
Updated docs section regarding max_years_between_matches to be more shorter and hopefully more relevant. [Kintyre]
Don’t install tests [scop]
0.3.35 (2020-10-11)
Handle L in ranges. This fixes #142. [kiorky]
Add a new initialization parameter max_years_between_matches to support finding the next/previous date beyond the default 1 year window, if so desired. Updated README to include additional notes and example of this usage. Fixes #145. [Kintyre]
The croniter_range() function was updated to automatically determines the appropriate max_years_between_matches value, this preventing handling of the CroniterBadDateError exception. [Kintyre]
Updated exception handling classes: CroniterBadDateError now only applies during date finding operations (next/prev), and all parsing errors can now be caught using CroniterBadCronError. The CroniterNotAlphaError exception is now a subclass of CroniterBadCronError. A brief description of each exception class was added as an inline docstring. [Kintyre]
Updated iterable interfaces to replace the CroniterBadDateError with StopIteration if (and only if) the max_years_between_matches argument is provided. The rationale here is that if the user has specified the max tolerance between matches, then there’s no need to further inform them of no additional matches. Just stop the iteration. This also keeps backwards compatibility. [Kintyre]
Minor docs update [Kintyre]
0.3.34 (2020-06-19)
Feat croniter_range(start, stop, cron) [Kintyre]
Optimization for poorly written cron expression [Kintyre]
0.3.33 (2020-06-15)
Make dateutil tz support more official [lowell80]
Feat/support for day or [田口信元]
0.3.32 (2020-05-27)
document seconds repeats, fixes #122 [kiorky]
Implement match method, fixes #54 [kiorky]
Adding tests for #127 (test more DSTs and croniter behavior around) [kiorky]
Changed lag_hours comparison to absolute to manage dst boundary when getting previous [Sokkka]
0.3.31 (2020-01-02)
Fix get_next() when start_time less then 1s before next instant [AlexHill]
0.3.30 (2019-04-20)
credits
0.3.29 (2019-03-26)
0.3.25 (2018-08-07)
Pypi hygiene [hugovk]
0.3.24 (2018-06-20)
fix #107: microsecond threshold [kiorky]
0.3.23 (2018-05-23)
fix get_next while preserving the fix of get_prev in 7661c2aaa [Avikam Agur <avikam@pagaya-inv.com>]
0.3.22 (2018-05-16)
Don’t count previous minute if now is dynamic If the code is triggered from 5-asterisk based cron get_prev based on datetime.now() is expected to return current cron iteration and not previous execution. [Igor Khrol <igor.khrol@toptal.com>]
0.3.20 (2017-11-06)
More DST fixes [Kevin Rose <kbrose@github>]
0.3.19 (2017-08-31)
fix #87: backward dst changes [kiorky]
0.3.18 (2017-08-31)
Add is valid method, refactor errors [otherpirate, Mauro Murari <mauro_murari@hotmail.com>]
0.3.17 (2017-05-22)
DOW occurence sharp style support. [kiorky, Kengo Seki <sekikn@apache.org>]
0.3.16 (2017-03-15)
Better test suite [mrcrilly@github]
DST support [kiorky]
0.3.15 (2017-02-16)
fix bug around multiple conditions and range_val in _get_prev_nearest_diff. [abeja-yuki@github]
0.3.14 (2017-01-25)
issue #69: added day_or option to change behavior when day-of-month and day-of-week is given [Andreas Vogl <a.vogl@hackner-security.com>]
0.3.13 (2016-11-01)
0.3.12 (2016-03-10)
support setting ret_type in __init__ [Brent Tubbs <brent.tubbs@gmail.com>]
0.3.11 (2016-01-13)
Bug fix: The get_prev API crashed when last day of month token was used. Some essential logic was missing. [Iddo Aviram <iddo.aviram@similarweb.com>]
0.3.10 (2015-11-29)
The functionality of ‘l’ as day of month was broken, since the month variable was not properly updated [Iddo Aviram <iddo.aviram@similarweb.com>]
0.3.9 (2015-11-19)
Don’t use datetime functions python 2.6 doesn’t support [petervtzand]
0.3.8 (2015-06-23)
Truncate microseconds by setting to 0 [Corey Wright]
0.3.7 (2015-06-01)
converting sun in range sun-thu transforms to int 0 which is recognized as empty string; the solution was to convert sun to string “0”
0.3.6 (2015-05-29)
Fix default behavior when no start_time given Default value for start_time parameter is calculated at module init time rather than call time.
Fix timezone support and stop depending on the system time zone
0.3.5 (2014-08-01)
support for ‘l’ (last day of month)
0.3.4 (2014-01-30)
Python 3 compat
QA Release
0.3.3 (2012-09-29)
proper packaging
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Hashes for croniter-0.3.37-py2.py3-none-any.whl
Algorithm | Hash digest | |
---|---|---|
SHA256 | 8f573a889ca9379e08c336193435c57c02698c2dd22659cdbe04fee57426d79b |
|
MD5 | 170718365e852cfc4d96decaa9242b26 |
|
BLAKE2b-256 | 05e51d2b7e3c36c444e78a31d7f0b8a1799acf4f8d869693f2232a3555a5dc6f |