{"id":26641042,"url":"https://github.com/pallets-eco/croniter","last_synced_at":"2025-12-11T21:03:38.053Z","repository":{"id":38416749,"uuid":"420394097","full_name":"pallets-eco/croniter","owner":"pallets-eco","description":"Parses cron schedules to iterate over datetime objects.","archived":false,"fork":false,"pushed_at":"2025-04-13T12:24:21.000Z","size":601,"stargazers_count":448,"open_issues_count":11,"forks_count":112,"subscribers_count":13,"default_branch":"main","last_synced_at":"2025-05-14T00:13:30.384Z","etag":null,"topics":["cron","python"],"latest_commit_sha":null,"homepage":"","language":"Python","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/pallets-eco.png","metadata":{"files":{"readme":"README.rst","changelog":"CHANGELOG.rst","contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null},"funding":{"github":"pallets","custom":"https://palletsprojects.com/donate","tidelift":"pypi/Flask"}},"created_at":"2021-10-23T11:31:33.000Z","updated_at":"2025-05-07T07:55:13.000Z","dependencies_parsed_at":"2024-06-18T13:48:53.625Z","dependency_job_id":"dd77a216-b1e8-4737-89a8-e37d173f27cd","html_url":"https://github.com/pallets-eco/croniter","commit_stats":{"total_commits":547,"total_committers":74,"mean_commits":7.391891891891892,"dds":0.4131627056672761,"last_synced_commit":"43a3f6ef22ae649628b284c1271bda213ee1b3d7"},"previous_names":["corpusops/croniter","kiorky/croniter","pallets-eco/croniter"],"tags_count":85,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/pallets-eco%2Fcroniter","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/pallets-eco%2Fcroniter/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/pallets-eco%2Fcroniter/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/pallets-eco%2Fcroniter/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/pallets-eco","download_url":"https://codeload.github.com/pallets-eco/croniter/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":254313712,"owners_count":22050089,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["cron","python"],"created_at":"2025-03-24T18:12:05.251Z","updated_at":"2025-12-11T21:03:38.047Z","avatar_url":"https://github.com/pallets-eco.png","language":"Python","funding_links":["https://github.com/sponsors/pallets","https://palletsprojects.com/donate","https://tidelift.com/funding/github/pypi/Flask"],"categories":["Python"],"sub_categories":[],"readme":"Introduction\n============\n\n.. contents::\n\n\ncroniter provides iteration for the datetime object with a cron like format.\n\n::\n\n                          _ _\n      ___ _ __ ___  _ __ (_) |_ ___ _ __\n     / __| '__/ _ \\| '_ \\| | __/ _ \\ '__|\n    | (__| | | (_) | | | | | ||  __/ |\n     \\___|_|  \\___/|_| |_|_|\\__\\___|_|\n\n\nWebsite: https://github.com/pallets-eco/croniter\n\nBuild Badge\n===========\n.. image:: https://github.com/pallets-eco/croniter/actions/workflows/cicd.yml/badge.svg\n    :target: https://github.com/pallets-eco/croniter/actions/workflows/cicd.yml\n\n\nPallets Community Ecosystem\n===========================\n\n.. important::\n   This project is part of the Pallets Community Ecosystem. Pallets is the open\n   source organization that maintains Flask; Pallets-Eco enables community\n   maintenance of Flask extensions. If you are interested in helping maintain\n   this project, please reach out on the `Pallets Discord server\n   \u003chttps://discord.gg/pallets\u003e`_.\n\n\nUsage\n============\n\nA simple example::\n\n    \u003e\u003e\u003e from croniter import croniter\n    \u003e\u003e\u003e from datetime import datetime\n    \u003e\u003e\u003e base = datetime(2010, 1, 25, 4, 46)\n    \u003e\u003e\u003e iter = croniter('*/5 * * * *', base)  # every 5 minutes\n    \u003e\u003e\u003e print(iter.get_next(datetime))   # 2010-01-25 04:50:00\n    \u003e\u003e\u003e print(iter.get_next(datetime))   # 2010-01-25 04:55:00\n    \u003e\u003e\u003e print(iter.get_next(datetime))   # 2010-01-25 05:00:00\n    \u003e\u003e\u003e\n    \u003e\u003e\u003e iter = croniter('2 4 * * mon,fri', base)  # 04:02 on every Monday and Friday\n    \u003e\u003e\u003e print(iter.get_next(datetime))   # 2010-01-26 04:02:00\n    \u003e\u003e\u003e print(iter.get_next(datetime))   # 2010-01-30 04:02:00\n    \u003e\u003e\u003e print(iter.get_next(datetime))   # 2010-02-02 04:02:00\n    \u003e\u003e\u003e\n    \u003e\u003e\u003e iter = croniter('2 4 1 * wed', base)  # 04:02 on every Wednesday OR on 1st day of month\n    \u003e\u003e\u003e print(iter.get_next(datetime))   # 2010-01-27 04:02:00\n    \u003e\u003e\u003e print(iter.get_next(datetime))   # 2010-02-01 04:02:00\n    \u003e\u003e\u003e print(iter.get_next(datetime))   # 2010-02-03 04:02:00\n    \u003e\u003e\u003e\n    \u003e\u003e\u003e iter = croniter('2 4 1 * wed', base, day_or=False)  # 04:02 on every 1st day of the month if it is a Wednesday\n    \u003e\u003e\u003e print(iter.get_next(datetime))   # 2010-09-01 04:02:00\n    \u003e\u003e\u003e print(iter.get_next(datetime))   # 2010-12-01 04:02:00\n    \u003e\u003e\u003e print(iter.get_next(datetime))   # 2011-06-01 04:02:00\n    \u003e\u003e\u003e\n    \u003e\u003e\u003e iter = croniter('0 0 * * sat#1,sun#2', base)  # 1st Saturday, and 2nd Sunday of the month\n    \u003e\u003e\u003e print(iter.get_next(datetime))   # 2010-02-06 00:00:00\n    \u003e\u003e\u003e\n    \u003e\u003e\u003e iter = croniter('0 0 * * 5#3,L5', base)  # 3rd and last Friday of the month\n    \u003e\u003e\u003e print(iter.get_next(datetime))   # 2010-01-29 00:00:00\n    \u003e\u003e\u003e print(iter.get_next(datetime))   # 2010-02-19 00:00:00\n\n\nAll you need to know is how to use the constructor and the ``get_next``\nmethod, the signature of these methods are listed below::\n\n    \u003e\u003e\u003e def __init__(self, cron_format, start_time=time.time(), day_or=True)\n\ncroniter iterates along with ``cron_format`` from ``start_time``.\n``cron_format`` is **min hour day month day_of_week**, you can refer to\nhttp://en.wikipedia.org/wiki/Cron for more details. The ``day_or``\nswitch is used to control how croniter handles **day** and **day_of_week**\nentries. Default option is the cron behaviour, which connects those\nvalues using **OR**. If the switch is set to False, the values are connected\nusing **AND**. This behaves like fcron and enables you to e.g. define a job that\nexecutes each 2nd Friday of a month by setting the days of month and the\nweekday.\n::\n\n    \u003e\u003e\u003e def get_next(self, ret_type=float)\n\nget_next calculates the next value according to the cron expression and\nreturns an object of type ``ret_type``. ``ret_type`` should be a ``float`` or a\n``datetime`` object.\n\nSupported added for ``get_prev`` method. (\u003e= 0.2.0)::\n\n    \u003e\u003e\u003e base = datetime(2010, 8, 25)\n    \u003e\u003e\u003e itr = croniter('0 0 1 * *', base)\n    \u003e\u003e\u003e print(itr.get_prev(datetime))  # 2010-08-01 00:00:00\n    \u003e\u003e\u003e print(itr.get_prev(datetime))  # 2010-07-01 00:00:00\n    \u003e\u003e\u003e print(itr.get_prev(datetime))  # 2010-06-01 00:00:00\n\nYou can validate your crons using ``is_valid`` class method. (\u003e= 0.3.18)::\n\n    \u003e\u003e\u003e croniter.is_valid('0 0 1 * *')  # True\n    \u003e\u003e\u003e croniter.is_valid('0 wrong_value 1 * *')  # False\n\nAbout DST\n=========\nBe sure to init your croniter instance with a TZ aware datetime for this to work!\n\nExample using zoneinfo::\n\n    \u003e\u003e\u003e import zoneinfo\n    \u003e\u003e\u003e tz = zoneinfo.ZoneInfo(\"Europe/Berlin\")\n    \u003e\u003e\u003e local_date = datetime(2017, 3, 26, tzinfo=tz)\n    \u003e\u003e\u003e val = croniter('0 0 * * *', local_date).get_next(datetime)\n\nExample using pytz::\n\n    \u003e\u003e\u003e import pytz\n    \u003e\u003e\u003e tz = pytz.timezone(\"Europe/Paris\")\n    \u003e\u003e\u003e local_date = tz.localize(datetime(2017, 3, 26))\n    \u003e\u003e\u003e val = croniter('0 0 * * *', local_date).get_next(datetime)\n\nExample using python_dateutil::\n\n    \u003e\u003e\u003e import dateutil.tz\n    \u003e\u003e\u003e tz = dateutil.tz.gettz('Asia/Tokyo')\n    \u003e\u003e\u003e local_date = datetime(2017, 3, 26, tzinfo=tz)\n    \u003e\u003e\u003e val = croniter('0 0 * * *', local_date).get_next(datetime)\n\nExample using python built in module::\n\n    \u003e\u003e\u003e from datetime import datetime, timezone\n    \u003e\u003e\u003e local_date = datetime(2017, 3, 26, tzinfo=timezone.utc)\n    \u003e\u003e\u003e val = croniter('0 0 * * *', local_date).get_next(datetime)\n\nAbout second repeats\n=====================\nCroniter is able to do second repetition crontabs form and by default seconds are the 6th field::\n\n    \u003e\u003e\u003e base = datetime(2012, 4, 6, 13, 26, 10)\n    \u003e\u003e\u003e itr = croniter('* * * * * 15,25', base)\n    \u003e\u003e\u003e itr.get_next(datetime) # 4/6 13:26:15\n    \u003e\u003e\u003e itr.get_next(datetime) # 4/6 13:26:25\n    \u003e\u003e\u003e itr.get_next(datetime) # 4/6 13:27:15\n\nYou can also note that this expression will repeat every second from the start datetime.::\n\n    \u003e\u003e\u003e croniter('* * * * * *', local_date).get_next(datetime)\n\nYou can also use seconds as first field::\n\n    \u003e\u003e\u003e itr = croniter('15,25 * * * * *', base, second_at_beginning=True)\n\n\nAbout year\n===========\nCroniter also support year field.\nYear presents at the seventh field, which is after second repetition.\nThe range of year field is from 1970 to 2099.\nTo ignore second repetition, simply set second to ``0`` or any other const::\n\n    \u003e\u003e\u003e base = datetime(2012, 4, 6, 2, 6, 59)\n    \u003e\u003e\u003e itr = croniter('0 0 1 1 * 0 2020/2', base)\n    \u003e\u003e\u003e itr.get_next(datetime) # 2020 1/1 0:0:0\n    \u003e\u003e\u003e itr.get_next(datetime) # 2022 1/1 0:0:0\n    \u003e\u003e\u003e itr.get_next(datetime) # 2024 1/1 0:0:0\n\nSupport for start_time shifts\n==============================\nSee https://github.com/pallets-eco/croniter/pull/76,\nYou can set start_time=, then expand_from_start_time=True for your generations to be computed from start_time instead of calendar days::\n\n    \u003e\u003e\u003e from pprint import pprint\n    \u003e\u003e\u003e iter = croniter('0 0 */7 * *', start_time=datetime(2024, 7, 11), expand_from_start_time=True);pprint([iter.get_next(datetime) for a in range(10)])\n    [datetime.datetime(2024, 7, 18, 0, 0),\n     datetime.datetime(2024, 7, 25, 0, 0),\n     datetime.datetime(2024, 8, 4, 0, 0),\n     datetime.datetime(2024, 8, 11, 0, 0),\n     datetime.datetime(2024, 8, 18, 0, 0),\n     datetime.datetime(2024, 8, 25, 0, 0),\n     datetime.datetime(2024, 9, 4, 0, 0),\n     datetime.datetime(2024, 9, 11, 0, 0),\n     datetime.datetime(2024, 9, 18, 0, 0),\n     datetime.datetime(2024, 9, 25, 0, 0)]\n    \u003e\u003e\u003e # INSTEAD OF THE DEFAULT BEHAVIOR:\n    \u003e\u003e\u003e iter = croniter('0 0 */7 * *', start_time=datetime(2024, 7, 11), expand_from_start_time=False);pprint([iter.get_next(datetime) for a in range(10)])\n    [datetime.datetime(2024, 7, 15, 0, 0),\n     datetime.datetime(2024, 7, 22, 0, 0),\n     datetime.datetime(2024, 7, 29, 0, 0),\n     datetime.datetime(2024, 8, 1, 0, 0),\n     datetime.datetime(2024, 8, 8, 0, 0),\n     datetime.datetime(2024, 8, 15, 0, 0),\n     datetime.datetime(2024, 8, 22, 0, 0),\n     datetime.datetime(2024, 8, 29, 0, 0),\n     datetime.datetime(2024, 9, 1, 0, 0),\n     datetime.datetime(2024, 9, 8, 0, 0)]\n\n\nTesting if a date matches a crontab\n===================================\nTest for a match with (\u003e=0.3.32)::\n\n    \u003e\u003e\u003e croniter.match(\"0 0 * * *\", datetime(2019, 1, 14, 0, 0, 0, 0))\n    True\n    \u003e\u003e\u003e croniter.match(\"0 0 * * *\", datetime(2019, 1, 14, 0, 2, 0, 0))\n    False\n    \u003e\u003e\u003e\n    \u003e\u003e\u003e 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\n    True\n    \u003e\u003e\u003e 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\n    False\n\nTesting if a crontab matches in datetime range\n==============================================\nTest for a match_range with (\u003e=2.0.3)::\n\n    \u003e\u003e\u003e croniter.match_range(\"0 0 * * *\", datetime(2019, 1, 13, 0, 59, 0, 0), datetime(2019, 1, 14, 0, 1, 0, 0))\n    True\n    \u003e\u003e\u003e croniter.match_range(\"0 0 * * *\", datetime(2019, 1, 13, 0, 1, 0, 0), datetime(2019, 1, 13, 0, 59, 0, 0))\n    False\n    \u003e\u003e\u003e croniter.match_range(\"2 4 1 * wed\", datetime(2019, 1, 1, 3, 2, 0, 0), datetime(2019, 1, 1, 5, 1, 0, 0))\n    # 04:02 on every Wednesday OR on 1st day of month\n    True\n    \u003e\u003e\u003e croniter.match_range(\"2 4 1 * wed\", datetime(2019, 1, 1, 3, 2, 0, 0), datetime(2019, 1, 1, 5, 2, 0, 0), day_or=False)\n    # 04:02 on every 1st day of the month if it is a Wednesday\n    False\n\nGaps between date matches\n=========================\nFor performance reasons, croniter limits the amount of CPU cycles spent attempting to find the next match.\nStarting 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.\n\nThe defaults should be fine for many use cases.\nApplications that evaluate multiple cron expressions or handle cron expressions from untrusted sources or end-users should use this parameter.\nIterating 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.\nExplicitly specifying ``max_years_between_matches`` provides a way to limit CPU utilization and simplifies the iterable interface by eliminating the need for ``CroniterBadDateError``.\nThe 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.\n\nThis example matches 4 AM Friday, January 1st.\nSince January 1st isn't often a Friday, there may be a few years between each occurrence.\nSetting the limit to 15 years ensures all matches::\n\n    \u003e\u003e\u003e it = croniter(\"0 4 1 1 fri\", datetime(2000,1,1), day_or=False, max_years_between_matches=15).all_next(datetime)\n    \u003e\u003e\u003e for i in range(5):\n    ...     print(next(it))\n    ...\n    2010-01-01 04:00:00\n    2016-01-01 04:00:00\n    2021-01-01 04:00:00\n    2027-01-01 04:00:00\n    2038-01-01 04:00:00\n\nHowever, when only concerned with dates within the next 5 years, simply set ``max_years_between_matches=5`` in the above example.\nThis will result in no matches found, but no additional cycles will be wasted on unwanted matches far in the future.\n\nIterating over a range using cron\n=================================\nFind 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.\nAdded in (\u003e=0.3.34)\n\nList the first Saturday of every month in 2019::\n\n    \u003e\u003e\u003e from croniter import croniter_range\n    \u003e\u003e\u003e for dt in croniter_range(datetime(2019, 1, 1), datetime(2019, 12, 31), \"0 0 * * sat#1\"):\n    \u003e\u003e\u003e     print(dt)\n\n\nHashed expressions\n==================\n\ncroniter supports Jenkins-style hashed expressions, using the \"H\" definition keyword and the required hash_id keyword argument.\nHashed expressions remain consistent, given the same hash_id, but different hash_ids will evaluate completely different to each other.\nThis allows, for example, for an even distribution of differently-named jobs without needing to manually spread them out.\n\n    \u003e\u003e\u003e itr = croniter(\"H H * * *\", hash_id=\"hello\")\n    \u003e\u003e\u003e itr.get_next(datetime)\n    datetime.datetime(2021, 4, 10, 11, 10)\n    \u003e\u003e\u003e itr.get_next(datetime)\n    datetime.datetime(2021, 4, 11, 11, 10)\n    \u003e\u003e\u003e itr = croniter(\"H H * * *\", hash_id=\"hello\")\n    \u003e\u003e\u003e itr.get_next(datetime)\n    datetime.datetime(2021, 4, 10, 11, 10)\n    \u003e\u003e\u003e itr = croniter(\"H H * * *\", hash_id=\"bonjour\")\n    \u003e\u003e\u003e itr.get_next(datetime)\n    datetime.datetime(2021, 4, 10, 20, 52)\n\n\nRandom expressions\n==================\n\nRandom \"R\" definition keywords are supported, and remain consistent only within their croniter() instance.\n\n    \u003e\u003e\u003e itr = croniter(\"R R * * *\")\n    \u003e\u003e\u003e itr.get_next(datetime)\n    datetime.datetime(2021, 4, 10, 22, 56)\n    \u003e\u003e\u003e itr.get_next(datetime)\n    datetime.datetime(2021, 4, 11, 22, 56)\n    \u003e\u003e\u003e itr = croniter(\"R R * * *\")\n    \u003e\u003e\u003e itr.get_next(datetime)\n    datetime.datetime(2021, 4, 11, 4, 19)\n\n\nNote about Ranges\n=================\n\nNote that as a deviation from cron standard, croniter is somehow laxist with ranges and will allow ranges of ``Jan-Dec``, \u0026 ``Sun-Sat`` in reverse way and interpret them as following examples:\n\n    - ``Apr-Jan``: from April to january\n    - ``Sat-Sun``: Saturday, Sunday\n    - ``Wed-Sun``: Wednesday to Saturday, Sunday\n\nPlease note that if a /step is given, it will be respected.\n\nNote about Sunday\n=================\n\nNote that as a deviation from cron standard, croniter like numerous cron implementations supports ``SUNDAY`` to be expressed as ``DAY7``, allowing such expressions:\n\n    - ``0 0 * * 7``\n    - ``0 0 * * 6-7``\n    - ``0 0 * * 6,7``\n\n\nKeyword expressions\n===================\n\nVixie cron-style \"@\" keyword expressions are supported.\nWhat they evaluate to depends on whether you supply hash_id: no hash_id corresponds to Vixie cron definitions (exact times, minute resolution), while with hash_id corresponds to Jenkins definitions (hashed within the period, second resolution).\n\n    ============ ============ ================\n    Keyword      No hash_id   With hash_id\n    ============ ============ ================\n    @midnight    0 0 * * *    H H(0-2) * * * H\n    @hourly      0 * * * *    H * * * * H\n    @daily       0 0 * * *    H H * * * H\n    @weekly      0 0 * * 0    H H * * H H\n    @monthly     0 0 1 * *    H H H * * H\n    @yearly      0 0 1 1 *    H H H H * H\n    @annually    0 0 1 1 *    H H H H * H\n    ============ ============ ================\n\nUpgrading\n==========\n\nTo 2.0.0\n---------\n\n- Install or upgrade pytz by using version specified  requirements/base.txt if you have it installed `\u003c=2021.1`.\n\nDevelop this package\n====================\n\n::\n\n    git clone https://github.com/pallets-eco/croniter.git\n    cd croniter\n    virtualenv --no-site-packages venv3\n    venv3/bin/pip install --upgrade -r requirements/test.txt -r requirements/lint.txt -r requirements/format.txt -r requirements/tox.txt\n    venv3/bin/black src/\n    venv3/bin/isort src/\n    venv3/bin/tox --current-env -e fmt,lint,test\n\n\nMake a new release\n====================\nWe use zest.fullreleaser, a great release infrastructure.\n\nDo and follow these instructions\n::\n\n    venv3/bin/pip install --upgrade -r requirements/release.txt\n    ./release.sh\n\n\nContributors\n===============\nThanks to all who have contributed to this project!\nIf you have contributed and your name is not listed below please let us know.\n\n    - Aarni Koskela (akx)\n    - ashb\n    - bdrung\n    - chris-baynes\n    - djmitche\n    - evanpurkhiser\n    - GreatCombinator\n    - Hinnack\n    - ipartola\n    - jlsandell\n    - kiorky\n    - lowell80 (Kintyre)\n    - mag009\n    - mrmachine\n    - potiuk\n    - Ryan Finnie (rfinnie)\n    - salitaba\n    - scop\n    - shazow\n    - yuzawa-san\n    - zed2015\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fpallets-eco%2Fcroniter","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fpallets-eco%2Fcroniter","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fpallets-eco%2Fcroniter/lists"}