[
  {
    "path": ".github/workflows/ci.yml",
    "content": "name: Tests\n\non:\n  push:\n    branches:\n      - master\n  pull_request:\n    branches:\n      - master\n\njobs:\n  test:\n    runs-on: ubuntu-latest\n    strategy:\n      max-parallel: 6\n      matrix:\n        python-version: ['3.7', '3.8', '3.9', '3.10', '3.11', '3.12']\n    steps:\n      - uses: actions/checkout@v3\n      - name: Set up Python ${{ matrix.python-version }}\n        uses: actions/setup-python@v4\n        with:\n          python-version: ${{ matrix.python-version }}\n      - name: Install dependencies\n        run: pip install tox tox-gh-actions\n      - name: Tests\n        run: tox\n      - name: Coveralls\n        env:\n          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}\n          COVERALLS_FLAG_NAME: python-${{ matrix.python-version }}\n          COVERALLS_PARALLEL: true\n        run: |\n          pip3 install coveralls\n          coveralls --service=github\n  coveralls:\n    # Notify coveralls that the built has finished so they can\n    # combine the results and post a comment with the summary.\n    name: coverage push\n    needs: test\n    runs-on: ubuntu-latest\n    steps:\n      - name: Set up Python 3.11\n        uses: actions/setup-python@v4\n        with:\n          python-version: 3.11\n      - name: Finished\n        run: |\n          pip3 install coveralls\n          coveralls --finish --service=github\n        env:\n          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}\n\n  docs:\n    runs-on: ubuntu-latest\n    steps:\n      - uses: actions/checkout@v3\n      - name: Set up Python 3.11\n        uses: actions/setup-python@v4\n        with:\n          python-version: 3.11\n      - name: Install dependencies\n        run: pip install tox\n      - name: Check docs\n        run: tox -e docs\n\n  formatting:\n    runs-on: ubuntu-latest\n    steps:\n      - uses: actions/checkout@v3\n      - name: Set up Python 3.11\n        uses: actions/setup-python@v4\n        with:\n          python-version: 3.11\n      - name: Install dependencies\n        run: pip install tox\n      - name: Check formatting\n        run: tox -e format\n\n  setuppy:\n    runs-on: ubuntu-latest\n    steps:\n      - uses: actions/checkout@v3\n      - name: Set up Python 3.11\n        uses: actions/setup-python@v4\n        with:\n          python-version: 3.11\n      - name: Install dependencies\n        run: pip install tox\n      - name: Check docs\n        run: tox -e setuppy\n"
  },
  {
    "path": ".gitignore",
    "content": "*.py[cod]\n\n# C extensions\n*.so\n\n# Packages\n*.egg\n*.egg-info\ndist\nbuild\neggs\nparts\nbin\nvar\nsdist\ndevelop-eggs\n.installed.cfg\nlib\nlib64\nMANIFEST\n\n# Installer logs\npip-log.txt\n\n# Unit test / coverage reports\n.coverage\n.tox\nnosetests.xml\n\n# Translations\n*.mo\n\n# Mr Developer\n.mr.developer.cfg\n.project\n.pydevproject\n\nenv\nenv3\n__pycache__\nvenv\n\n.cache\ndocs/_build\n\n# For Idea (e.g. PyCharm) users\n.idea\n*.iml\n"
  },
  {
    "path": "AUTHORS.rst",
    "content": "Thanks to all the wonderful folks who have contributed to schedule over the years:\n\n- mattss <https://github.com/mattss>\n- mrhwick <https://github.com/mrhwick>\n- cfrco <https://github.com/cfrco>\n- matrixise <https://github.com/matrixise>\n- abultman <https://github.com/abultman>\n- mplewis <https://github.com/mplewis>\n- WoLfulus <https://github.com/WoLfulus>\n- dylwhich <https://github.com/dylwhich>\n- fkromer <https://github.com/fkromer>\n- alaingilbert <https://github.com/alaingilbert>\n- Zerrossetto <https://github.com/Zerrossetto>\n- yetingsky <https://github.com/yetingsky>\n- schnepp <https://github.com/schnepp> <https://bitbucket.org/saschaschnepp>\n- grampajoe <https://github.com/grampajoe>\n- gilbsgilbs <https://github.com/gilbsgilbs>\n- Nathan Wailes <https://github.com/NathanWailes>\n- Connor Skees <https://github.com/ConnorSkees>\n- qmorek <https://github.com/qmorek>\n- aisk <https://github.com/aisk>\n- MichaelCorleoneLi <https://github.com/MichaelCorleoneLi>\n- sijmenhuizenga <https://github.com/SijmenHuizenga>\n- eladbi <https://github.com/eladbi>\n- chankeypathak <https://github.com/chankeypathak>\n- vubon <https://github.com/vubon>\n- gaguirregabiria <https://github.com/gaguirregabiria>\n- rhagenaars <https://github.com/RHagenaars>\n- Skenvy <https://github.com/skenvy>\n- zcking <https://github.com/zcking>\n- Martin Thoma <https://github.com/MartinThoma>\n- ebllg <https://github.com/ebllg>\n- fredthomsen <https://github.com/fredthomsen>\n- biggerfisch <https://github.com/biggerfisch>\n- sosolidkk <https://github.com/sosolidkk>\n- rudSarkar <https://github.com/rudSarkar>\n- chrimaho <https://github.com/chrimaho>\n- jweijers <https://github.com/jweijers>\n- Akuli <https://github.com/Akuli>\n- NaelsonDouglas <https://github.com/NaelsonDouglas>\n- SergBobrovsky <https://github.com/SergBobrovsky>\n- CPickens42 <https://github.com/CPickens42>\n- emollier <https://github.com/emollier>\n- sunpro108 <https://github.com/sunpro108>\n- kurtasov <https://github.com/kurtasov>\n- AnezeR <https://github.com/AnezeR>\n- a-detiste <https://github.com/a-detiste>\n"
  },
  {
    "path": "HISTORY.rst",
    "content": ".. :changelog:\n\nHistory\n-------\n\n1.2.2 (2024-05-25)\n++++++++++++++++++\n\n- Fix bugs in cross-timezone scheduling (#601, #602, #604, #623)\n- Add support for python 3.12 (#606)\n- Remove dependency on old mock (#622) Thanks @a-detiste!\n\n\n1.2.1 (2023-11-01)\n++++++++++++++++++\n\n-  Fix bug where schedule was off when using .at with timezone (#583) Thanks @AnezeR!\n\n\n1.2.0 (2023-04-10)\n++++++++++++++++++\n\n- Dropped support for Python 3.6, add support for Python 3.10 and 3.11.\n- Add timezone support for .at(). See #517. Thanks @chrimaho!\n- Get next run by tag (#463) Thanks @jweijers!\n- Add py.typed file. See #521. Thanks @Akuli!\n\n- Fix the re pattern of the 'days'. See #506 Thanks @sunpro108!\n- Fix test_until_time failure when run early. See #563. Thanks @emollier!\n- Fix crash repr on partially constructed job. See #569. Thanks @CPickens42!\n- Code cleanup and modernization. See #567, #536. Thanks @masa-08 and @SergBobrovsky!\n- Documentation improvements and fix typos. See #469, #479, #493, #519, #520. Thanks to @NaelsonDouglas, @chrimaho, @rudSarkar\n\n1.1.0 (2021-04-09)\n++++++++++++++++++\n\n- Added @repeat() decorator. See #148. Thanks @rhagenaars!\n- Added execute .until(). See #195. Thanks @fredthomsen!\n- Added job retrieval filtered by tags using get_jobs('tag'). See #419. Thanks @skenvy!\n- Added type annotations. See #427. Thanks @martinthoma!\n\n- Bugfix: str() of job when there is no __name__. See #430. Thanks @biggerfisch!\n- Improved error messages. See #280, #439. Thanks @connorskees and @sosolidkk!\n- Improved logging. See #193. Thanks @zcking!\n- Documentation improvements and fix typos. See #424, #435, #436, #453, #437, #448. Thanks @ebllg!\n\n1.0.0 (2021-01-20)\n++++++++++++++++++\n\nDepending on your configuration, the following bugfixes might change schedule's behaviour:\n\n- Fix: idle_seconds crashes when no jobs are scheduled. See #401. Thanks @yoonghm!\n- Fix: day.at('HH:MM:SS') where HMS=now+10s doesn't run today. See #331. Thanks @qmorek!\n- Fix: hour.at('MM:SS'), the seconds are set to 00. See #290. Thanks @eladbi!\n- Fix: Long-running jobs skip a day when they finish in the next day #404. Thanks @4379711!\n\nOther changes:\n\n- Dropped Python 2.7 and 3.5 support, added 3.8 and 3.9 support. See #409\n- Fix RecursionError when the job is passed to the do function as an arg. See #190. Thanks @connorskees!\n- Fix DeprecationWarning of 'collections'. See #296. Thanks @gaguirregabiria!\n- Replaced Travis with Github Actions for automated testing\n- Revamp and extend documentation. See #395\n- Improved tests. Thanks @connorskees and @Jamim!\n- Changed log messages to DEBUG level. Thanks @aisk!\n\n\n0.6.0 (2019-01-20)\n++++++++++++++++++\n\n- Make at() accept timestamps with 1 second precision (#267). Thanks @NathanWailes!\n- Introduce proper exception hierarchy (#271). Thanks @ConnorSkees!\n\n\n0.5.0 (2017-11-16)\n++++++++++++++++++\n\n- Keep partially scheduled jobs from breaking the scheduler (#125)\n- Add support for random intervals (Thanks @grampajoe and @gilbsgilbs)\n\n\n0.4.3 (2017-06-10)\n++++++++++++++++++\n\n- Improve docs & clean up docstrings\n\n\n0.4.2 (2016-11-29)\n++++++++++++++++++\n\n- Publish to PyPI as a universal (py2/py3) wheel\n\n\n0.4.0 (2016-11-28)\n++++++++++++++++++\n\n- Add proper HTML (Sphinx) docs available at https://schedule.readthedocs.io/\n- CI builds now run against Python 2.7 and 3.5 (3.3 and 3.4 should work fine but are untested)\n- Fixed an issue with ``run_all()`` and having more than one job that deletes itself in the same iteration. Thanks @alaingilbert.\n- Add ability to tag jobs and to cancel jobs by tag. Thanks @Zerrossetto.\n- Improve schedule docs. Thanks @Zerrossetto.\n- Additional docs fixes by @fkromer and @yetingsky.\n\n0.3.2 (2015-07-02)\n++++++++++++++++++\n\n- Fixed issues where scheduling a job with a functools.partial as the job function fails. Thanks @dylwhich.\n- Fixed an issue where scheduling a job to run every >= 2 days would cause the initial execution to happen one day early. Thanks @WoLfulus for identifying this and providing a fix.\n- Added a FAQ item to describe how to schedule a job that runs only once.\n\n0.3.1 (2014-09-03)\n++++++++++++++++++\n\n- Fixed an issue with unicode handling in setup.py that was causing trouble on Python 3 and Debian (https://github.com/dbader/schedule/issues/27). Thanks to @waghanza for reporting it.\n- Added an FAQ item to describe how to deal with job functions that throw exceptions. Thanks @mplewis.\n\n0.3.0 (2014-06-14)\n++++++++++++++++++\n\n- Added support for scheduling jobs on specific weekdays. Example: ``schedule.every().tuesday.do(job)`` or ``schedule.every().wednesday.at(\"13:15\").do(job)`` (Thanks @abultman.)\n- Run tests against Python 2.7 and 3.4. Python 3.3 should continue to work but we're not actively testing it on CI anymore.\n\n0.2.1 (2013-11-20)\n++++++++++++++++++\n\n- Fixed history (no code changes).\n\n0.2.0 (2013-11-09)\n++++++++++++++++++\n\n- This release introduces two new features in a backwards compatible way:\n- Allow jobs to cancel repeated execution: Jobs can be cancelled by calling ``schedule.cancel_job()`` or by returning ``schedule.CancelJob`` from the job function. (Thanks to @cfrco and @matrixise.)\n- Updated ``at_time()`` to allow running jobs at a particular time every hour. Example: ``every().hour.at(':15').do(job)`` will run ``job`` 15 minutes after every full hour. (Thanks @mattss.)\n- Refactored unit tests to mock ``datetime`` in a cleaner way. (Thanks @matts.)\n\n0.1.11 (2013-07-30)\n+++++++++++++++++++\n\n- Fixed an issue with ``next_run()`` throwing a ``ValueError`` exception when the job queue is empty. Thanks to @dpagano for pointing this out and thanks to @mrhwick for quickly providing a fix.\n\n0.1.10 (2013-06-07)\n+++++++++++++++++++\n\n- Fixed issue with ``at_time`` jobs not running on the same day the job is created (Thanks to @mattss)\n\n0.1.9 (2013-05-27)\n++++++++++++++++++\n\n- Added ``schedule.next_run()``\n- Added ``schedule.idle_seconds()``\n- Args passed into ``do()`` are forwarded to the job function at call time\n- Increased test coverage to 100%\n\n\n0.1.8 (2013-05-21)\n++++++++++++++++++\n\n- Changed default ``delay_seconds`` for ``schedule.run_all()`` to 0 (from 60)\n- Increased test coverage\n\n0.1.7 (2013-05-20)\n++++++++++++++++++\n\n- API change: renamed ``schedule.run_all_jobs()`` to ``schedule.run_all()``\n- API change: renamed ``schedule.run_pending_jobs()`` to ``schedule.run_pending()``\n- API change: renamed ``schedule.clear_all_jobs()`` to ``schedule.clear()``\n- Added ``schedule.jobs``\n\n0.1.6 (2013-05-20)\n++++++++++++++++++\n\n- Fix packaging\n- README fixes\n\n0.1.4 (2013-05-20)\n++++++++++++++++++\n\n- API change: renamed ``schedule.tick()`` to ``schedule.run_pending_jobs()``\n- Updated README and ``setup.py`` packaging\n\n0.1.0 (2013-05-19)\n++++++++++++++++++\n\n- Initial release\n"
  },
  {
    "path": "LICENSE.txt",
    "content": "The MIT License (MIT)\n\nCopyright (c) 2013 Daniel Bader (http://dbader.org)\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in\nall copies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\nTHE SOFTWARE.\n"
  },
  {
    "path": "MANIFEST.in",
    "content": "include README.rst\ninclude HISTORY.rst\ninclude LICENSE.txt\n\ninclude test_schedule.py\n\nrecursive-exclude * __pycache__\nrecursive-exclude * *.py[co]\n"
  },
  {
    "path": "README.rst",
    "content": "`schedule <https://schedule.readthedocs.io/>`__\n===============================================\n\n\n.. image:: https://github.com/dbader/schedule/workflows/Tests/badge.svg\n        :target: https://github.com/dbader/schedule/actions?query=workflow%3ATests+branch%3Amaster\n\n.. image:: https://coveralls.io/repos/dbader/schedule/badge.svg?branch=master\n        :target: https://coveralls.io/r/dbader/schedule\n\n.. image:: https://img.shields.io/pypi/v/schedule.svg\n        :target: https://pypi.python.org/pypi/schedule\n\nPython job scheduling for humans. Run Python functions (or any other callable) periodically using a friendly syntax.\n\n- A simple to use API for scheduling jobs, made for humans.\n- In-process scheduler for periodic jobs. No extra processes needed!\n- Very lightweight and no external dependencies.\n- Excellent test coverage.\n- Tested on Python and 3.7, 3.8, 3.9, 3.10, 3.11, 3.12\n\nUsage\n-----\n\n.. code-block:: bash\n\n    $ pip install schedule\n\n.. code-block:: python\n\n    import schedule\n    import time\n\n    def job():\n        print(\"I'm working...\")\n\n    schedule.every(10).seconds.do(job)\n    schedule.every(10).minutes.do(job)\n    schedule.every().hour.do(job)\n    schedule.every().day.at(\"10:30\").do(job)\n    schedule.every(5).to(10).minutes.do(job)\n    schedule.every().monday.do(job)\n    schedule.every().wednesday.at(\"13:15\").do(job)\n    schedule.every().day.at(\"12:42\", \"Europe/Amsterdam\").do(job)\n    schedule.every().minute.at(\":17\").do(job)\n\n    def job_with_argument(name):\n        print(f\"I am {name}\")\n\n    schedule.every(10).seconds.do(job_with_argument, name=\"Peter\")\n\n    while True:\n        schedule.run_pending()\n        time.sleep(1)\n\nDocumentation\n-------------\n\nSchedule's documentation lives at `schedule.readthedocs.io <https://schedule.readthedocs.io/>`_.\n\n\nMeta\n----\n\nDaniel Bader - `@dbader_org <https://twitter.com/dbader_org>`_ - mail@dbader.org\n\nInspired by `Adam Wiggins' <https://github.com/adamwiggins>`_ article `\"Rethinking Cron\" <https://adam.herokuapp.com/past/2010/4/13/rethinking_cron/>`_ and the `clockwork <https://github.com/Rykian/clockwork>`_ Ruby module.\n\nDistributed under the MIT license. See `LICENSE.txt <https://github.com/dbader/schedule/blob/master/LICENSE.txt>`_ for more information.\n\nhttps://github.com/dbader/schedule\n"
  },
  {
    "path": "docs/Makefile",
    "content": "# Makefile for Sphinx documentation\n#\n\n# You can set these variables from the command line.\nSPHINXOPTS    =\nSPHINXBUILD   = sphinx-build\nPAPER         =\nBUILDDIR      = _build\n\n# Internal variables.\nPAPEROPT_a4     = -D latex_paper_size=a4\nPAPEROPT_letter = -D latex_paper_size=letter\nALLSPHINXOPTS   = -d $(BUILDDIR)/doctrees $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) .\n# the i18n builder cannot share the environment and doctrees with the others\nI18NSPHINXOPTS  = $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) .\n\n.PHONY: help\nhelp:\n\t@echo \"Please use \\`make <target>' where <target> is one of\"\n\t@echo \"  html       to make standalone HTML files\"\n\t@echo \"  dirhtml    to make HTML files named index.html in directories\"\n\t@echo \"  singlehtml to make a single large HTML file\"\n\t@echo \"  pickle     to make pickle files\"\n\t@echo \"  json       to make JSON files\"\n\t@echo \"  htmlhelp   to make HTML files and a HTML help project\"\n\t@echo \"  qthelp     to make HTML files and a qthelp project\"\n\t@echo \"  applehelp  to make an Apple Help Book\"\n\t@echo \"  devhelp    to make HTML files and a Devhelp project\"\n\t@echo \"  epub       to make an epub\"\n\t@echo \"  epub3      to make an epub3\"\n\t@echo \"  latex      to make LaTeX files, you can set PAPER=a4 or PAPER=letter\"\n\t@echo \"  latexpdf   to make LaTeX files and run them through pdflatex\"\n\t@echo \"  latexpdfja to make LaTeX files and run them through platex/dvipdfmx\"\n\t@echo \"  text       to make text files\"\n\t@echo \"  man        to make manual pages\"\n\t@echo \"  texinfo    to make Texinfo files\"\n\t@echo \"  info       to make Texinfo files and run them through makeinfo\"\n\t@echo \"  gettext    to make PO message catalogs\"\n\t@echo \"  changes    to make an overview of all changed/added/deprecated items\"\n\t@echo \"  xml        to make Docutils-native XML files\"\n\t@echo \"  pseudoxml  to make pseudoxml-XML files for display purposes\"\n\t@echo \"  linkcheck  to check all external links for integrity\"\n\t@echo \"  doctest    to run all doctests embedded in the documentation (if enabled)\"\n\t@echo \"  coverage   to run coverage check of the documentation (if enabled)\"\n\t@echo \"  dummy      to check syntax errors of document sources\"\n\n.PHONY: clean\nclean:\n\trm -rf $(BUILDDIR)/*\n\n.PHONY: html\nhtml:\n\t$(SPHINXBUILD) -b html $(ALLSPHINXOPTS) $(BUILDDIR)/html\n\t@echo\n\t@echo \"Build finished. The HTML pages are in $(BUILDDIR)/html.\"\n\n.PHONY: dirhtml\ndirhtml:\n\t$(SPHINXBUILD) -b dirhtml $(ALLSPHINXOPTS) $(BUILDDIR)/dirhtml\n\t@echo\n\t@echo \"Build finished. The HTML pages are in $(BUILDDIR)/dirhtml.\"\n\n.PHONY: singlehtml\nsinglehtml:\n\t$(SPHINXBUILD) -b singlehtml $(ALLSPHINXOPTS) $(BUILDDIR)/singlehtml\n\t@echo\n\t@echo \"Build finished. The HTML page is in $(BUILDDIR)/singlehtml.\"\n\n.PHONY: pickle\npickle:\n\t$(SPHINXBUILD) -b pickle $(ALLSPHINXOPTS) $(BUILDDIR)/pickle\n\t@echo\n\t@echo \"Build finished; now you can process the pickle files.\"\n\n.PHONY: json\njson:\n\t$(SPHINXBUILD) -b json $(ALLSPHINXOPTS) $(BUILDDIR)/json\n\t@echo\n\t@echo \"Build finished; now you can process the JSON files.\"\n\n.PHONY: htmlhelp\nhtmlhelp:\n\t$(SPHINXBUILD) -b htmlhelp $(ALLSPHINXOPTS) $(BUILDDIR)/htmlhelp\n\t@echo\n\t@echo \"Build finished; now you can run HTML Help Workshop with the\" \\\n\t      \".hhp project file in $(BUILDDIR)/htmlhelp.\"\n\n.PHONY: qthelp\nqthelp:\n\t$(SPHINXBUILD) -b qthelp $(ALLSPHINXOPTS) $(BUILDDIR)/qthelp\n\t@echo\n\t@echo \"Build finished; now you can run \"qcollectiongenerator\" with the\" \\\n\t      \".qhcp project file in $(BUILDDIR)/qthelp, like this:\"\n\t@echo \"# qcollectiongenerator $(BUILDDIR)/qthelp/schedule.qhcp\"\n\t@echo \"To view the help file:\"\n\t@echo \"# assistant -collectionFile $(BUILDDIR)/qthelp/schedule.qhc\"\n\n.PHONY: applehelp\napplehelp:\n\t$(SPHINXBUILD) -b applehelp $(ALLSPHINXOPTS) $(BUILDDIR)/applehelp\n\t@echo\n\t@echo \"Build finished. The help book is in $(BUILDDIR)/applehelp.\"\n\t@echo \"N.B. You won't be able to view it unless you put it in\" \\\n\t      \"~/Library/Documentation/Help or install it in your application\" \\\n\t      \"bundle.\"\n\n.PHONY: devhelp\ndevhelp:\n\t$(SPHINXBUILD) -b devhelp $(ALLSPHINXOPTS) $(BUILDDIR)/devhelp\n\t@echo\n\t@echo \"Build finished.\"\n\t@echo \"To view the help file:\"\n\t@echo \"# mkdir -p $$HOME/.local/share/devhelp/schedule\"\n\t@echo \"# ln -s $(BUILDDIR)/devhelp $$HOME/.local/share/devhelp/schedule\"\n\t@echo \"# devhelp\"\n\n.PHONY: epub\nepub:\n\t$(SPHINXBUILD) -b epub $(ALLSPHINXOPTS) $(BUILDDIR)/epub\n\t@echo\n\t@echo \"Build finished. The epub file is in $(BUILDDIR)/epub.\"\n\n.PHONY: epub3\nepub3:\n\t$(SPHINXBUILD) -b epub3 $(ALLSPHINXOPTS) $(BUILDDIR)/epub3\n\t@echo\n\t@echo \"Build finished. The epub3 file is in $(BUILDDIR)/epub3.\"\n\n.PHONY: latex\nlatex:\n\t$(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex\n\t@echo\n\t@echo \"Build finished; the LaTeX files are in $(BUILDDIR)/latex.\"\n\t@echo \"Run \\`make' in that directory to run these through (pdf)latex\" \\\n\t      \"(use \\`make latexpdf' here to do that automatically).\"\n\n.PHONY: latexpdf\nlatexpdf:\n\t$(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex\n\t@echo \"Running LaTeX files through pdflatex...\"\n\t$(MAKE) -C $(BUILDDIR)/latex all-pdf\n\t@echo \"pdflatex finished; the PDF files are in $(BUILDDIR)/latex.\"\n\n.PHONY: latexpdfja\nlatexpdfja:\n\t$(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex\n\t@echo \"Running LaTeX files through platex and dvipdfmx...\"\n\t$(MAKE) -C $(BUILDDIR)/latex all-pdf-ja\n\t@echo \"pdflatex finished; the PDF files are in $(BUILDDIR)/latex.\"\n\n.PHONY: text\ntext:\n\t$(SPHINXBUILD) -b text $(ALLSPHINXOPTS) $(BUILDDIR)/text\n\t@echo\n\t@echo \"Build finished. The text files are in $(BUILDDIR)/text.\"\n\n.PHONY: man\nman:\n\t$(SPHINXBUILD) -b man $(ALLSPHINXOPTS) $(BUILDDIR)/man\n\t@echo\n\t@echo \"Build finished. The manual pages are in $(BUILDDIR)/man.\"\n\n.PHONY: texinfo\ntexinfo:\n\t$(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo\n\t@echo\n\t@echo \"Build finished. The Texinfo files are in $(BUILDDIR)/texinfo.\"\n\t@echo \"Run \\`make' in that directory to run these through makeinfo\" \\\n\t      \"(use \\`make info' here to do that automatically).\"\n\n.PHONY: info\ninfo:\n\t$(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo\n\t@echo \"Running Texinfo files through makeinfo...\"\n\tmake -C $(BUILDDIR)/texinfo info\n\t@echo \"makeinfo finished; the Info files are in $(BUILDDIR)/texinfo.\"\n\n.PHONY: gettext\ngettext:\n\t$(SPHINXBUILD) -b gettext $(I18NSPHINXOPTS) $(BUILDDIR)/locale\n\t@echo\n\t@echo \"Build finished. The message catalogs are in $(BUILDDIR)/locale.\"\n\n.PHONY: changes\nchanges:\n\t$(SPHINXBUILD) -b changes $(ALLSPHINXOPTS) $(BUILDDIR)/changes\n\t@echo\n\t@echo \"The overview file is in $(BUILDDIR)/changes.\"\n\n.PHONY: linkcheck\nlinkcheck:\n\t$(SPHINXBUILD) -b linkcheck $(ALLSPHINXOPTS) $(BUILDDIR)/linkcheck\n\t@echo\n\t@echo \"Link check complete; look for any errors in the above output \" \\\n\t      \"or in $(BUILDDIR)/linkcheck/output.txt.\"\n\n.PHONY: doctest\ndoctest:\n\t$(SPHINXBUILD) -b doctest $(ALLSPHINXOPTS) $(BUILDDIR)/doctest\n\t@echo \"Testing of doctests in the sources finished, look at the \" \\\n\t      \"results in $(BUILDDIR)/doctest/output.txt.\"\n\n.PHONY: coverage\ncoverage:\n\t$(SPHINXBUILD) -b coverage $(ALLSPHINXOPTS) $(BUILDDIR)/coverage\n\t@echo \"Testing of coverage in the sources finished, look at the \" \\\n\t      \"results in $(BUILDDIR)/coverage/python.txt.\"\n\n.PHONY: xml\nxml:\n\t$(SPHINXBUILD) -b xml $(ALLSPHINXOPTS) $(BUILDDIR)/xml\n\t@echo\n\t@echo \"Build finished. The XML files are in $(BUILDDIR)/xml.\"\n\n.PHONY: pseudoxml\npseudoxml:\n\t$(SPHINXBUILD) -b pseudoxml $(ALLSPHINXOPTS) $(BUILDDIR)/pseudoxml\n\t@echo\n\t@echo \"Build finished. The pseudo-XML files are in $(BUILDDIR)/pseudoxml.\"\n\n.PHONY: dummy\ndummy:\n\t$(SPHINXBUILD) -b dummy $(ALLSPHINXOPTS) $(BUILDDIR)/dummy\n\t@echo\n\t@echo \"Build finished. Dummy builder generates no files.\"\n"
  },
  {
    "path": "docs/_static/custom.css",
    "content": ".toctree-l1 {\n    padding-bottom: 4px;\n}"
  },
  {
    "path": "docs/_templates/sidebarintro.html",
    "content": "<h3>📰 Useful Links</h3>\n<ul>\n  <li><a href=\"http://github.com/dbader/schedule\">Schedule @ GitHub</a></li>\n  <li><a href=\"http://pypi.python.org/pypi/schedule\">Schedule @ PyPI</a></li>\n  <li><a href=\"http://github.com/dbader/schedule/issues\">Issue Tracker</a></li>\n</ul>\n\n<h3>🐍 More Python</h3>\n\n<p><a href=\"https://twitter.com/dbader_org\" class=\"twitter-follow-button\" data-show-count=\"false\">Follow @dbader_org</a> <script>!function(d,s,id){var js,fjs=d.getElementsByTagName(s)[0],p=/^http:/.test(d.location)?'http':'https';if(!d.getElementById(id)){js=d.createElement(s);js.id=id;js.src=p+'://platform.twitter.com/widgets.js';fjs.parentNode.insertBefore(js,fjs);}}(document, 'script', 'twitter-wjs');</script></p>\n<ul>\n  <li><a href=\"https://dbader.org/screencasts/\">Dan's Python Screencasts</a></li>\n  <li><a href=\"https://dbader.org/newsletter\">Dan's Python Newsletter</a></li>\n  <li><a href=\"https://dbader.org/\">Dan's Python Tutorials</a></li>\n</ul>\n"
  },
  {
    "path": "docs/background-execution.rst",
    "content": "Run in the background\n=====================\n\nOut of the box it is not possible to run the schedule in the background.\nHowever, you can create a thread yourself and use it to run jobs without blocking the main thread.\nThis is an example of how you could do this:\n\n.. code-block:: python\n\n    import threading\n    import time\n\n    import schedule\n\n\n    def run_continuously(interval=1):\n        \"\"\"Continuously run, while executing pending jobs at each\n        elapsed time interval.\n        @return cease_continuous_run: threading. Event which can\n        be set to cease continuous run. Please note that it is\n        *intended behavior that run_continuously() does not run\n        missed jobs*. For example, if you've registered a job that\n        should run every minute and you set a continuous run\n        interval of one hour then your job won't be run 60 times\n        at each interval but only once.\n        \"\"\"\n        cease_continuous_run = threading.Event()\n\n        class ScheduleThread(threading.Thread):\n            @classmethod\n            def run(cls):\n                while not cease_continuous_run.is_set():\n                    schedule.run_pending()\n                    time.sleep(interval)\n\n        continuous_thread = ScheduleThread()\n        continuous_thread.start()\n        return cease_continuous_run\n\n\n    def background_job():\n        print('Hello from the background thread')\n\n\n    schedule.every().second.do(background_job)\n\n    # Start the background thread\n    stop_run_continuously = run_continuously()\n\n    # Do some other things...\n    time.sleep(10)\n\n    # Stop the background thread\n    stop_run_continuously.set()\n\n\n"
  },
  {
    "path": "docs/changelog.rst",
    "content": ".. include:: ../HISTORY.rst\n"
  },
  {
    "path": "docs/conf.py",
    "content": "# -*- coding: utf-8 -*-\n#\n# schedule documentation build configuration file, created by\n# sphinx-quickstart on Mon Nov  7 15:14:48 2016.\n#\n# This file is execfile()d with the current directory set to its\n# containing dir.\n#\n# Note that not all possible configuration values are present in this\n# autogenerated file.\n#\n# All configuration values have a default; values that are commented out\n# serve to show the default.\n\n# If extensions (or modules to document with autodoc) are in another directory,\n# add these directories to sys.path here. If the directory is relative to the\n# documentation root, use os.path.abspath to make it absolute, like shown here.\n#\n# (schedule modules lives up one level from docs/)\n#\nimport os\nimport sys\n\nsys.path.insert(0, os.path.abspath(\"..\"))\n\n# -- General configuration ------------------------------------------------\n\n# If your documentation needs a minimal Sphinx version, state it here.\n#\n# needs_sphinx = '1.0'\n\n# Add any Sphinx extension module names here, as strings. They can be\n# extensions coming with Sphinx (named 'sphinx.ext.*') or your custom\n# ones.\nextensions = [\n    \"sphinx.ext.autodoc\",\n    \"sphinx.ext.todo\",\n    \"sphinx.ext.coverage\",\n    \"sphinx.ext.viewcode\",\n    # 'sphinx.ext.githubpages',  # This breaks the ReadTheDocs build\n]\n\n# Add any paths that contain templates here, relative to this directory.\ntemplates_path = [\"_templates\"]\n\n# The suffix(es) of source filenames.\n# You can specify multiple suffix as a list of string:\n#\n# source_suffix = ['.rst', '.md']\nsource_suffix = \".rst\"\n\n# The encoding of source files.\n#\n# source_encoding = 'utf-8-sig'\n\n# The master toctree document.\nmaster_doc = \"index\"\n\n# General information about the project.\nproject = u\"schedule\"\ncopyright = u'2020, <a href=\"https://dbader.org/\">Daniel Bader</a>'\nauthor = u'<a href=\"https://dbader.org/\">Daniel Bader</a>'\n\n# The version info for the project you're documenting, acts as replacement for\n# |version| and |release|, also used in various other places throughout the\n# built documents.\n#\n# The short X.Y version.\nversion = u\"1.2.2\"\n# The full version, including alpha/beta/rc tags.\nrelease = u\"1.2.2\"\n\n# The language for content autogenerated by Sphinx. Refer to documentation\n# for a list of supported languages.\n#\n# This is also used if you do content translation via gettext catalogs.\n# Usually you set \"language\" from the command line for these cases.\nlanguage = \"en\"\n\n# There are two options for replacing |today|: either, you set today to some\n# non-false value, then it is used:\n#\n# today = ''\n#\n# Else, today_fmt is used as the format for a strftime call.\n#\n# today_fmt = '%B %d, %Y'\n\n# List of patterns, relative to source directory, that match files and\n# directories to ignore when looking for source files.\n# This patterns also effect to html_static_path and html_extra_path\nexclude_patterns = [\"_build\", \"Thumbs.db\", \".DS_Store\"]\n\n# The reST default role (used for this markup: `text`) to use for all\n# documents.\n#\n# default_role = None\n\n# If true, '()' will be appended to :func: etc. cross-reference text.\n#\n# add_function_parentheses = True\n\n# If true, the current module name will be prepended to all description\n# unit titles (such as .. function::).\n#\n# add_module_names = True\n\n# If true, sectionauthor and moduleauthor directives will be shown in the\n# output. They are ignored by default.\n#\n# show_authors = False\n\n# The name of the Pygments (syntax highlighting) style to use.\n# pygments_style = 'flask_theme_support.FlaskyStyle'\n\n# A list of ignored prefixes for module index sorting.\n# modindex_common_prefix = []\n\n# If true, keep warnings as \"system message\" paragraphs in the built documents.\n# keep_warnings = False\n\n# If true, `todo` and `todoList` produce output, else they produce nothing.\ntodo_include_todos = True\n\n\n# -- Options for HTML output ----------------------------------------------\n\n# The theme to use for HTML and HTML Help pages.  See the documentation for\n# a list of builtin themes.\n#\nhtml_theme = \"alabaster\"\n\n# Theme options are theme-specific and customize the look and feel of a theme\n# further.  For a list of options available for each theme, see the\n# documentation.\n#\nhtml_theme_options = {\n    \"show_powered_by\": False,\n    \"github_user\": \"dbader\",\n    \"github_repo\": \"schedule\",\n    \"github_banner\": True,\n    \"github_button\": True,\n    \"github_type\": \"star\",\n    \"show_related\": False,\n}\n\n# Add any paths that contain custom themes here, relative to this directory.\n# html_theme_path = []\n\n# The name for this set of Sphinx documents.\n# \"<project> v<release> documentation\" by default.\n#\n# html_title = u'schedule v0.4.0'\n\n# A shorter title for the navigation bar.  Default is the same as html_title.\n#\n# html_short_title = None\n\n# The name of an image file (relative to this directory) to place at the top\n# of the sidebar.\n#\n# html_logo = None\n\n# The name of an image file (relative to this directory) to use as a favicon of\n# the docs.  This file should be a Windows icon file (.ico) being 16x16 or 32x32\n# pixels large.\n#\n# html_favicon = None\n\n# Add any paths that contain custom static files (such as style sheets) here,\n# relative to this directory. They are copied after the builtin static files,\n# so a file named \"default.css\" will overwrite the builtin \"default.css\".\nhtml_static_path = [\"_static\"]\n\n# Add any extra paths that contain custom files (such as robots.txt or\n# .htaccess) here, relative to this directory. These files are copied\n# directly to the root of the documentation.\n#\n# html_extra_path = []\n\n# If not None, a 'Last updated on:' timestamp is inserted at every page\n# bottom, using the given strftime format.\n# The empty string is equivalent to '%b %d, %Y'.\n#\n# html_last_updated_fmt = None\n\n# If true, SmartyPants will be used to convert quotes and dashes to\n# typographically correct entities.\n#\nhtml_use_smartypants = True\n\n# Custom sidebar templates, maps document names to template names.\n#\nhtml_sidebars = {\n    \"**\": [\n        \"about.html\",\n        \"globaltoc.html\",\n        \"sidebarintro.html\",\n        \"relations.html\",\n        \"searchbox.html\",\n    ],\n}\n\n# Additional templates that should be rendered to pages, maps page names to\n# template names.\n#\n# html_additional_pages = {}\n\n# If false, no module index is generated.\n#\n# html_domain_indices = True\n\n# If false, no index is generated.\n#\n# html_use_index = True\n\n# If true, the index is split into individual pages for each letter.\n#\n# html_split_index = False\n\n# If true, links to the reST sources are added to the pages.\n#\nhtml_show_sourcelink = False\n\n# If true, \"Created using Sphinx\" is shown in the HTML footer. Default is True.\n#\nhtml_show_sphinx = False\n\n# If true, \"(C) Copyright ...\" is shown in the HTML footer. Default is True.\n#\nhtml_show_copyright = True\n\n# If true, an OpenSearch description file will be output, and all pages will\n# contain a <link> tag referring to it.  The value of this option must be the\n# base URL from which the finished HTML is served.\n#\n# html_use_opensearch = ''\n\n# This is the file name suffix for HTML files (e.g. \".xhtml\").\n# html_file_suffix = None\n\n# Language to be used for generating the HTML full-text search index.\n# Sphinx supports the following languages:\n#   'da', 'de', 'en', 'es', 'fi', 'fr', 'hu', 'it', 'ja'\n#   'nl', 'no', 'pt', 'ro', 'ru', 'sv', 'tr', 'zh'\n#\n# html_search_language = 'en'\n\n# A dictionary with options for the search language support, empty by default.\n# 'ja' uses this config value.\n# 'zh' user can custom change `jieba` dictionary path.\n#\n# html_search_options = {'type': 'default'}\n\n# The name of a javascript file (relative to the configuration directory) that\n# implements a search results scorer. If empty, the default will be used.\n#\n# html_search_scorer = 'scorer.js'\n\n# Output file base name for HTML help builder.\nhtmlhelp_basename = \"scheduledoc\"\n\n# -- Options for LaTeX output ---------------------------------------------\n\nlatex_elements = {\n    # The paper size ('letterpaper' or 'a4paper').\n    #\n    # 'papersize': 'letterpaper',\n    # The font size ('10pt', '11pt' or '12pt').\n    #\n    # 'pointsize': '10pt',\n    # Additional stuff for the LaTeX preamble.\n    #\n    # 'preamble': '',\n    # Latex figure (float) alignment\n    #\n    # 'figure_align': 'htbp',\n}\n\n# Grouping the document tree into LaTeX files. List of tuples\n# (source start file, target name, title,\n#  author, documentclass [howto, manual, or own class]).\nlatex_documents = [\n    (\n        master_doc,\n        \"schedule.tex\",\n        u\"schedule Documentation\",\n        u\"Daniel Bader\",\n        \"manual\",\n    ),\n]\n\n# The name of an image file (relative to this directory) to place at the top of\n# the title page.\n#\n# latex_logo = None\n\n# For \"manual\" documents, if this is true, then toplevel headings are parts,\n# not chapters.\n#\n# latex_use_parts = False\n\n# If true, show page references after internal links.\n#\n# latex_show_pagerefs = False\n\n# If true, show URL addresses after external links.\n#\n# latex_show_urls = False\n\n# Documents to append as an appendix to all manuals.\n#\n# latex_appendices = []\n\n# It false, will not define \\strong, \\code, \titleref, \\crossref ... but only\n# \\sphinxstrong, ..., \\sphinxtitleref, ... To help avoid clash with user added\n# packages.\n#\n# latex_keep_old_macro_names = True\n\n# If false, no module index is generated.\n#\n# latex_domain_indices = True\n\n\n# -- Options for manual page output ---------------------------------------\n\n# One entry per manual page. List of tuples\n# (source start file, name, description, authors, manual section).\nman_pages = [(master_doc, \"schedule\", u\"schedule Documentation\", [author], 1)]\n\n# If true, show URL addresses after external links.\n#\n# man_show_urls = False\n\n\n# -- Options for Texinfo output -------------------------------------------\n\n# Grouping the document tree into Texinfo files. List of tuples\n# (source start file, target name, title, author,\n#  dir menu entry, description, category)\ntexinfo_documents = [\n    (\n        master_doc,\n        \"schedule\",\n        u\"schedule Documentation\",\n        author,\n        \"schedule\",\n        \"One line description of project.\",\n        \"Miscellaneous\",\n    ),\n]\n\n# Documents to append as an appendix to all manuals.\n#\n# texinfo_appendices = []\n\n# If false, no module index is generated.\n#\n# texinfo_domain_indices = True\n\n# How to display URL addresses: 'footnote', 'no', or 'inline'.\n#\n# texinfo_show_urls = 'footnote'\n\n# If true, do not generate a @detailmenu in the \"Top\" node's menu.\n#\n# texinfo_no_detailmenu = False\n\nautodoc_member_order = \"bysource\"\n\n# We're pulling in some external images like CI badges.\nsuppress_warnings = [\"image.nonlocal_uri\"]\n"
  },
  {
    "path": "docs/development.rst",
    "content": "Development\n===========\n\nThese instructions are geared towards people who want to help develop this library.\n\nPreparing for development\n-------------------------\nAll required tooling and libraries can be installed using the ``requirements-dev.txt`` file:\n\n.. code-block:: bash\n\n    pip install -r requirements-dev.txt\n\n\nRunning tests\n-------------\n\n``pytest`` is used to run tests. Run all tests with coverage and formatting checks:\n\n.. code-block:: bash\n\n    py.test test_schedule.py --flake8 schedule -v --cov schedule --cov-report term-missing\n\nFormatting the code\n-------------------\nThis project uses `black formatter <https://black.readthedocs.io/en/stable/>`_.\nTo format the code, run:\n\n.. code-block:: bash\n\n    black .\n\nMake sure you use version 20.8b1 of black.\n\nCompiling documentation\n-----------------------\n\nThe documentation is written in `reStructuredText <https://docutils.sourceforge.io/rst.html>`_.\nIt is processed using `Sphinx <http://www.sphinx-doc.org/en/1.4.8/tutorial.html>`_ using the `alabaster <https://alabaster.readthedocs.io/en/latest/>`_ theme.\nAfter installing the development requirements it is just a matter of running:\n\n.. code-block:: bash\n\n    cd docs\n    make html\n\nThe resulting html can be found in ``docs/_build/html``\n\nPublish a new version\n---------------------\n\nUpdate the ``HISTORY.rst`` and ``AUTHORS.rst`` files.\nBump the version in ``setup.py`` and ``docs/conf.py``.\nMerge these changes into master. Finally:\n\n.. code-block:: bash\n\n    git tag X.Y.Z -m \"Release X.Y.Z\"\n    git push --tags\n\n    pip install --upgrade setuptools twine wheel\n    python3 -m build --wheel\n    # For https://test.pypi.org/project/schedule/\n    twine upload --repository schedule-test dist/*\n    # For https://pypi.org/project/schedule/\n    twine upload --repository schedule dist/*\n\nThis project follows `semantic versioning <https://semver.org/>`_.`\n"
  },
  {
    "path": "docs/examples.rst",
    "content": "Examples\n========\n\nEager to get started? This page gives a good introduction to Schedule.\nIt assumes you already have Schedule installed. If you do not, head over to :doc:`installation`.\n\nRun a job every x minute\n~~~~~~~~~~~~~~~~~~~~~~~~\n\n.. code-block:: python\n\n    import schedule\n    import time\n\n    def job():\n        print(\"I'm working...\")\n\n    # Run job every 3 second/minute/hour/day/week,\n    # Starting 3 second/minute/hour/day/week from now\n    schedule.every(3).seconds.do(job)\n    schedule.every(3).minutes.do(job)\n    schedule.every(3).hours.do(job)\n    schedule.every(3).days.do(job)\n    schedule.every(3).weeks.do(job)\n\n    # Run job every minute at the 23rd second\n    schedule.every().minute.at(\":23\").do(job)\n\n    # Run job every hour at the 42nd minute\n    schedule.every().hour.at(\":42\").do(job)\n\n    # Run jobs every 5th hour, 20 minutes and 30 seconds in.\n    # If current time is 02:00, first execution is at 06:20:30\n    schedule.every(5).hours.at(\"20:30\").do(job)\n\n    # Run job every day at specific HH:MM and next HH:MM:SS\n    schedule.every().day.at(\"10:30\").do(job)\n    schedule.every().day.at(\"10:30:42\").do(job)\n    schedule.every().day.at(\"12:42\", \"Europe/Amsterdam\").do(job)\n\n    # Run job on a specific day of the week\n    schedule.every().monday.do(job)\n    schedule.every().wednesday.at(\"13:15\").do(job)\n\n    while True:\n        schedule.run_pending()\n        time.sleep(1)\n\nUse a decorator to schedule a job\n~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n\nUse the ``@repeat`` to schedule a function.\nPass it an interval using the same syntax as above while omitting the ``.do()``.\n\n.. code-block:: python\n\n    from schedule import every, repeat, run_pending\n    import time\n\n    @repeat(every(10).minutes)\n    def job():\n        print(\"I am a scheduled job\")\n\n    while True:\n        run_pending()\n        time.sleep(1)\n\nThe ``@repeat`` decorator does not work on non-static class methods.\n\nPass arguments to a job\n~~~~~~~~~~~~~~~~~~~~~~~\n\n``do()`` passes extra arguments to the job function\n\n.. code-block:: python\n\n    import schedule\n\n    def greet(name):\n        print('Hello', name)\n\n    schedule.every(2).seconds.do(greet, name='Alice')\n    schedule.every(4).seconds.do(greet, name='Bob')\n\n    from schedule import every, repeat\n\n    @repeat(every().second, \"World\")\n    @repeat(every().day, \"Mars\")\n    def hello(planet):\n        print(\"Hello\", planet)\n\n\nCancel a job\n~~~~~~~~~~~~\nTo remove a job from the scheduler, use the ``schedule.cancel_job(job)`` method\n\n.. code-block:: python\n\n    import schedule\n\n    def some_task():\n        print('Hello world')\n\n    job = schedule.every().day.at('22:30').do(some_task)\n    schedule.cancel_job(job)\n\n\nRun a job once\n~~~~~~~~~~~~~~\n\nReturn ``schedule.CancelJob`` from a job to remove it from the scheduler.\n\n.. code-block:: python\n\n    import schedule\n    import time\n\n    def job_that_executes_once():\n        # Do some work that only needs to happen once...\n        return schedule.CancelJob\n\n    schedule.every().day.at('22:30').do(job_that_executes_once)\n\n    while True:\n        schedule.run_pending()\n        time.sleep(1)\n\n\nGet all jobs\n~~~~~~~~~~~~\nTo retrieve all jobs from the scheduler, use ``schedule.get_jobs()``\n\n.. code-block:: python\n\n    import schedule\n\n    def hello():\n        print('Hello world')\n\n    schedule.every().second.do(hello)\n\n    all_jobs = schedule.get_jobs()\n\n\nCancel all jobs\n~~~~~~~~~~~~~~~\nTo remove all jobs from the scheduler, use ``schedule.clear()``\n\n.. code-block:: python\n\n    import schedule\n\n    def greet(name):\n        print('Hello {}'.format(name))\n\n    schedule.every().second.do(greet)\n\n    schedule.clear()\n\n\nGet several jobs, filtered by tags\n~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n\nYou can retrieve a group of jobs from the scheduler, selecting them by a unique identifier.\n\n.. code-block:: python\n\n    import schedule\n\n    def greet(name):\n        print('Hello {}'.format(name))\n\n    schedule.every().day.do(greet, 'Andrea').tag('daily-tasks', 'friend')\n    schedule.every().hour.do(greet, 'John').tag('hourly-tasks', 'friend')\n    schedule.every().hour.do(greet, 'Monica').tag('hourly-tasks', 'customer')\n    schedule.every().day.do(greet, 'Derek').tag('daily-tasks', 'guest')\n\n    friends = schedule.get_jobs('friend')\n\nWill return a list of every job tagged as ``friend``.\n\n\nCancel several jobs, filtered by tags\n~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n\nYou can cancel the scheduling of a group of jobs selecting them by a unique identifier.\n\n.. code-block:: python\n\n    import schedule\n\n    def greet(name):\n        print('Hello {}'.format(name))\n\n    schedule.every().day.do(greet, 'Andrea').tag('daily-tasks', 'friend')\n    schedule.every().hour.do(greet, 'John').tag('hourly-tasks', 'friend')\n    schedule.every().hour.do(greet, 'Monica').tag('hourly-tasks', 'customer')\n    schedule.every().day.do(greet, 'Derek').tag('daily-tasks', 'guest')\n\n    schedule.clear('daily-tasks')\n\nWill prevent every job tagged as ``daily-tasks`` from running again.\n\n\nRun a job at random intervals\n~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n\n.. code-block:: python\n\n    def my_job():\n        print('Foo')\n\n    # Run every 5 to 10 seconds.\n    schedule.every(5).to(10).seconds.do(my_job)\n\n``every(A).to(B).seconds`` executes the job function every N seconds such that A <= N <= B.\n\n\nRun a job until a certain time\n~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n\n.. code-block:: python\n\n    import schedule\n    from datetime import datetime, timedelta, time\n\n    def job():\n        print('Boo')\n\n    # run job until a 18:30 today\n    schedule.every(1).hours.until(\"18:30\").do(job)\n\n    # run job until a 2030-01-01 18:33 today\n    schedule.every(1).hours.until(\"2030-01-01 18:33\").do(job)\n\n    # Schedule a job to run for the next 8 hours\n    schedule.every(1).hours.until(timedelta(hours=8)).do(job)\n\n    # Run my_job until today 11:33:42\n    schedule.every(1).hours.until(time(11, 33, 42)).do(job)\n\n    # run job until a specific datetime\n    schedule.every(1).hours.until(datetime(2020, 5, 17, 11, 36, 20)).do(job)\n\nThe ``until`` method sets the jobs deadline. The job will not run after the deadline.\n\nTime until the next execution\n~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\nUse ``schedule.idle_seconds()`` to get the number of seconds until the next job is scheduled to run.\nThe returned value is negative if the next scheduled jobs was scheduled to run in the past.\nReturns ``None`` if no jobs are scheduled.\n\n.. code-block:: python\n\n    import schedule\n    import time\n\n    def job():\n        print('Hello')\n\n    schedule.every(5).seconds.do(job)\n\n    while 1:\n        n = schedule.idle_seconds()\n        if n is None:\n            # no more jobs\n            break\n        elif n > 0:\n            # sleep exactly the right amount of time\n            time.sleep(n)\n        schedule.run_pending()\n\n\nRun all jobs now, regardless of their scheduling\n~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\nTo run all jobs regardless if they are scheduled to run or not, use ``schedule.run_all()``.\nJobs are re-scheduled after finishing, just like they would if they were executed using ``run_pending()``.\n\n.. code-block:: python\n\n    import schedule\n\n    def job_1():\n        print('Foo')\n\n    def job_2():\n        print('Bar')\n\n    schedule.every().monday.at(\"12:40\").do(job_1)\n    schedule.every().tuesday.at(\"16:40\").do(job_2)\n\n    schedule.run_all()\n\n    # Add the delay_seconds argument to run the jobs with a number\n    # of seconds delay in between.\n    schedule.run_all(delay_seconds=10)\n"
  },
  {
    "path": "docs/exception-handling.rst",
    "content": "Exception Handling\n##################\n\nSchedule doesn't catch exceptions that happen during job execution. Therefore any exceptions thrown during job execution will bubble up and interrupt schedule's run_xyz function.\n\nIf you want to guard against exceptions you can wrap your job function\nin a decorator like this:\n\n.. code-block:: python\n\n    import functools\n\n    def catch_exceptions(cancel_on_failure=False):\n        def catch_exceptions_decorator(job_func):\n            @functools.wraps(job_func)\n            def wrapper(*args, **kwargs):\n                try:\n                    return job_func(*args, **kwargs)\n                except:\n                    import traceback\n                    print(traceback.format_exc())\n                    if cancel_on_failure:\n                        return schedule.CancelJob\n            return wrapper\n        return catch_exceptions_decorator\n\n    @catch_exceptions(cancel_on_failure=True)\n    def bad_task():\n        return 1 / 0\n\n    schedule.every(5).minutes.do(bad_task)\n\nAnother option would be to subclass Schedule like @mplewis did in `this example <https://gist.github.com/mplewis/8483f1c24f2d6259aef6>`_.\n"
  },
  {
    "path": "docs/faq.rst",
    "content": "Frequently Asked Questions\n==========================\n\nFrequently asked questions on the usage of schedule.\nDid you get here using an 'old' link and expected to see more questions?\n\nAttributeError: 'module' object has no attribute 'every'\n--------------------------------------------------------\n\nI'm getting\n\n.. code-block:: text\n\n    AttributeError: 'module' object has no attribute 'every'\n\nwhen I try to use schedule.\n\nThis happens if your code imports the wrong ``schedule`` module.\nMake sure you don't have a ``schedule.py`` file in your project that overrides the ``schedule`` module provided by this library.\n\n\nModuleNotFoundError: No module named 'schedule'\n-----------------------------------------------\n\nIt seems python can't find the schedule package. Let's check some common causes.\n\nDid you install schedule? If not, follow :doc:`installation`. Validate installation:\n\n* Did you install using pip? Run ``pip3 list | grep schedule``. This should return ``schedule   0.6.0`` (or a higher version number)\n* Did you install using apt? Run ``dpkg -l | grep python3-schedule``. This should return something along the lines of ``python3-schedule     0.3.2-1.1     Job scheduling for humans (Python 3)`` (or a higher version number)\n\nAre you used python 3 to install Schedule, and are running the script using python 3?\nFor example, if you installed schedule using a version of pip that uses Python 2, and your code runs in Python 3, the package won't be found.\nIn this case the solution is to install Schedule using pip3: ``pip3 install schedule``.\n\nAre you using virtualenv? Check that you are running the script inside the same virtualenv where you installed schedule.\n\nIs this problem occurring when running the program from inside and IDE like PyCharm or VSCode?\nTry to run your program from a commandline outside of the IDE.\nIf it works there, the problem is with your IDE configuration.\nIt might be that your IDE uses a different Python interpreter installation.\n\nStill having problems? Use Google and StackOverflow before submitting an issue.\n\nModuleNotFoundError: ModuleNotFoundError: No module named 'pytz'\n----------------------------------------------------------------\n\nThis error happens when you try to set a timezone in ``.at()`` without having the `pytz <https://pypi.org/project/pytz/>`_ package installed.\nPytz is a required dependency when working with timezones.\nTo resolve this issue, install the ``pytz`` module by running ``pip install pytz``.\n\nDoes schedule support time zones?\n---------------------------------\nYes! See :doc:`Timezones <timezones>`.\n\nWhat if my task throws an exception?\n------------------------------------\nSee :doc:`Exception Handling <exception-handling>`.\n\nHow can I run a job only once?\n------------------------------\nSee :doc:`Examples <examples>`.\n\nHow can I cancel several jobs at once?\n--------------------------------------\nSee :doc:`Examples <examples>`.\n\nHow to execute jobs in parallel?\n--------------------------------\nSee :doc:`Parallel Execution <parallel-execution>`.\n\nHow to continuously run the scheduler without blocking the main thread?\n-----------------------------------------------------------------------\n:doc:`Background Execution<background-execution>`.\n\nAnother question?\n-----------------\nIf you are left with an unanswered question, `browse the issue tracker <http://github.com/dbader/schedule/issues>`_ to see if your question has been asked before.\nFeel free to create a new issue if that's not the case. Thank you 😃"
  },
  {
    "path": "docs/index.rst",
    "content": "schedule\n========\n\n\n.. image:: https://github.com/dbader/schedule/workflows/Tests/badge.svg\n        :target: https://github.com/dbader/schedule/actions?query=workflow%3ATests+branch%3Amaster\n\n.. image:: https://coveralls.io/repos/dbader/schedule/badge.svg?branch=master\n        :target: https://coveralls.io/r/dbader/schedule\n\n.. image:: https://img.shields.io/pypi/v/schedule.svg\n        :target: https://pypi.python.org/pypi/schedule\n\nPython job scheduling for humans. Run Python functions (or any other callable) periodically using a friendly syntax.\n\n- A simple to use API for scheduling jobs, made for humans.\n- In-process scheduler for periodic jobs. No extra processes needed!\n- Very lightweight and no external dependencies.\n- Excellent test coverage.\n- Tested on Python 3.7, 3.8, 3.9, 3.10, 3.11 and 3.12\n\n\n:doc:`Example <examples>`\n-------------------------\n\n.. code-block:: bash\n\n    $ pip install schedule\n\n.. code-block:: python\n\n    import schedule\n    import time\n\n    def job():\n        print(\"I'm working...\")\n\n    schedule.every(10).minutes.do(job)\n    schedule.every().hour.do(job)\n    schedule.every().day.at(\"10:30\").do(job)\n    schedule.every().monday.do(job)\n    schedule.every().wednesday.at(\"13:15\").do(job)\n    schedule.every().day.at(\"12:42\", \"Europe/Amsterdam\").do(job)\n    schedule.every().minute.at(\":17\").do(job)\n\n    while True:\n        schedule.run_pending()\n        time.sleep(1)\n\n\nMore :doc:`examples`\n\nWhen **not** to use Schedule\n----------------------------\nLet's be honest, Schedule is not a 'one size fits all' scheduling library.\nThis library is designed to be a simple solution for simple scheduling problems.\nYou should probably look somewhere else if you need:\n\n* Job persistence (remember schedule between restarts)\n* Exact timing (sub-second precision execution)\n* Concurrent execution (multiple threads)\n* Localization (workdays or holidays)\n\n\n**Schedule does not account for the time it takes for the job function to execute.**\nTo guarantee a stable execution schedule you need to move long-running jobs off the main-thread (where the scheduler runs).\nSee :doc:`parallel-execution` for a sample implementation.\n\n\nRead More\n---------\n.. toctree::\n   :maxdepth: 2\n\n   installation\n   examples\n   background-execution\n   parallel-execution\n   timezones\n   exception-handling\n   logging\n   multiple-schedulers\n   faq\n   reference\n   development\n\n.. toctree::\n   :maxdepth: 1\n\n   changelog\n\n\nIssues\n------\n\nIf you encounter any problems, please `file an issue <http://github.com/dbader/schedule/issues>`_ along with a detailed description.\nPlease also use the search feature in the issue tracker beforehand to avoid creating duplicates. Thank you 😃\n\nAbout Schedule\n--------------\n\nCreated by `Daniel Bader <https://dbader.org/>`__ - `@dbader_org <https://twitter.com/dbader_org>`_\n\nInspired by `Adam Wiggins' <https://github.com/adamwiggins>`_ article `\"Rethinking Cron\" <https://adam.herokuapp.com/past/2010/4/13/rethinking_cron/>`_ and the `clockwork <https://github.com/Rykian/clockwork>`_ Ruby module.\n\n\nDistributed under the MIT license. See ``LICENSE.txt`` for more information.\n\n.. include:: ../AUTHORS.rst\n"
  },
  {
    "path": "docs/installation.rst",
    "content": "Installation\n============\n\n\nPython version support\n######################\n\nWe recommend using the latest version of Python.\nSchedule is tested on Python 3.7, 3.8, 3.9, 3.10, 3.11 and 3.12\n\nWant to use Schedule on earlier Python versions? See the History.\n\n\nDependencies\n############\n\nSchedule has 1 optional dependency:\n\nOnly when you use ``.at()`` with a timezone, you must have `pytz <https://pypi.org/project/pytz/>`_ installed.\n\nInstallation instructions\n#########################\n\nProblems? Check out :doc:`faq`.\n\nPIP (preferred)\n***************\nThe recommended way to install this package is to use pip.\nUse the following command to install it:\n\n.. code-block:: bash\n\n    $ pip install schedule\n\nSchedule is now installed.\nCheck out the :doc:`examples </examples>` or go to the :doc:`the documentation overview </index>`.\n\n\nUsing another package manager\n*****************************\nSchedule is available through some linux package managers.\nThese packages are not maintained by the maintainers of this project.\nIt cannot be guarantee that these packages are up-to-date (and will stay up-to-date) with the latest released version.\nIf you don't mind having an old version, you can use it.\n\nUbuntu\n-------\n\n**OUTDATED!** At the time of writing, the packages for 20.04LTS and below use version 0.3.2 (2015).\n\n.. code-block:: bash\n\n    $ apt-get install python3-schedule\n\nSee `package page <https://packages.ubuntu.com/search?keywords=python3-schedule>`__.\n\nDebian\n------\n\n**OUTDATED!** At the time of writing, the packages for buster and below use version 0.3.2 (2015).\n\n.. code-block:: bash\n\n    $ apt-get install python3 schedule\n\nSee `package page <https://packages.debian.org/search?searchon=names&keywords=+python3-schedule>`__.\n\nArch\n----\n\nOn the Arch Linux User repository (AUR) the package is available using the name `python-schedule`.\nSee the package page `here <https://aur.archlinux.org/packages/python-schedule/>`__.\nFor yay users, run:\n\n.. code-block:: bash\n\n    $ yay -S python-schedule\n\nConda (Anaconda)\n----------------\n\nSchedule is `published <https://anaconda.org/conda-forge/schedule>`__ in conda (the Anaconda package manager).\n\nFor installation instructions, visit `the conda-forge Schedule repo <https://github.com/conda-forge/schedule-feedstock#installing-schedule>`__.\nThe release of Schedule on conda is maintained by the `conda-forge project <https://conda-forge.org/>`__.\n\nInstall manually\n**************************\nIf you don't have access to a package manager or need more control, you can manually copy the library into your project.\nThis is easy as the schedule library consists of a single sourcefile MIT licenced.\nHowever, this method is highly discouraged as you won't receive automatic updates.\n\n1. Go to the `Github repo <https://github.com/dbader/schedule>`_.\n2. Open file `schedule/__init__.py` and copy the code.\n3. In your project, create a packaged named `schedule` and paste the code in a file named `__init__.py`.\n"
  },
  {
    "path": "docs/logging.rst",
    "content": "Logging\n=======\n\nSchedule logs messages to the Python logger named ``schedule`` at ``DEBUG`` level.\nTo receive logs from Schedule, set the logging level to ``DEBUG``.\n\n.. code-block:: python\n\n    import schedule\n    import logging\n\n    logging.basicConfig()\n    schedule_logger = logging.getLogger('schedule')\n    schedule_logger.setLevel(level=logging.DEBUG)\n\n    def job():\n        print(\"Hello, Logs\")\n\n    schedule.every().second.do(job)\n\n    schedule.run_all()\n\n    schedule.clear()\n\nThis will result in the following log messages:\n\n.. code-block:: text\n\n    DEBUG:schedule:Running *all* 1 jobs with 0s delay in between\n    DEBUG:schedule:Running job Job(interval=1, unit=seconds, do=job, args=(), kwargs={})\n    Hello, Logs\n    DEBUG:schedule:Deleting *all* jobs\n\n\nCustomize logging\n-----------------\nThe easiest way to add reusable logging to jobs is to implement a decorator that handles logging.\nAs an example, below code adds the ``print_elapsed_time`` decorator:\n\n.. code-block:: python\n\n    import functools\n    import time\n    import schedule\n\n    # This decorator can be applied to any job function to log the elapsed time of each job\n    def print_elapsed_time(func):\n        @functools.wraps(func)\n        def wrapper(*args, **kwargs):\n            start_timestamp = time.time()\n            print('LOG: Running job \"%s\"' % func.__name__)\n            result = func(*args, **kwargs)\n            print('LOG: Job \"%s\" completed in %d seconds' % (func.__name__, time.time() - start_timestamp))\n            return result\n\n        return wrapper\n\n\n    @print_elapsed_time\n    def job():\n        print('Hello, Logs')\n        time.sleep(5)\n\n    schedule.every().second.do(job)\n\n    schedule.run_all()\n\nThis outputs:\n\n.. code-block:: text\n\n    LOG: Running job \"job\"\n    Hello, Logs\n    LOG: Job \"job\" completed in 5 seconds\n"
  },
  {
    "path": "docs/multiple-schedulers.rst",
    "content": "Multiple schedulers\n###################\n\nYou can run as many jobs from a single scheduler as you wish.\nHowever, for larger installations it might be desirable to have multiple schedulers.\nThis is supported:\n\n.. code-block:: python\n\n    import time\n    import schedule\n\n    def fooJob():\n        print(\"Foo\")\n\n    def barJob():\n        print(\"Bar\")\n\n    # Create a new scheduler\n    scheduler1 = schedule.Scheduler()\n\n    # Add jobs to the created scheduler\n    scheduler1.every().hour.do(fooJob)\n    scheduler1.every().hour.do(barJob)\n\n    # Create as many schedulers as you need\n    scheduler2 = schedule.Scheduler()\n    scheduler2.every().second.do(fooJob)\n    scheduler2.every().second.do(barJob)\n\n    while True:\n        # run_pending needs to be called on every scheduler\n        scheduler1.run_pending()\n        scheduler2.run_pending()\n        time.sleep(1)\n"
  },
  {
    "path": "docs/parallel-execution.rst",
    "content": "Parallel execution\n==========================\n\n*I am trying to execute 50 items every 10 seconds, but from the my logs it says it executes every item in 10 second schedule serially, is there a work around?*\n\nBy default, schedule executes all jobs serially. The reasoning behind this is that it would be difficult to find a model for parallel execution that makes everyone happy.\n\nYou can work around this limitation by running each of the jobs in its own thread:\n\n.. code-block:: python\n\n    import threading\n    import time\n    import schedule\n\n    def job():\n        print(\"I'm running on thread %s\" % threading.current_thread())\n\n    def run_threaded(job_func):\n        job_thread = threading.Thread(target=job_func)\n        job_thread.start()\n\n    schedule.every(10).seconds.do(run_threaded, job)\n    schedule.every(10).seconds.do(run_threaded, job)\n    schedule.every(10).seconds.do(run_threaded, job)\n    schedule.every(10).seconds.do(run_threaded, job)\n    schedule.every(10).seconds.do(run_threaded, job)\n\n\n    while 1:\n        schedule.run_pending()\n        time.sleep(1)\n\nIf you want tighter control on the number of threads use a shared jobqueue and one or more worker threads:\n\n.. code-block:: python\n\n    import time\n    import threading\n    import schedule\n    import queue\n\n    def job():\n        print(\"I'm working\")\n\n\n    def worker_main():\n        while 1:\n            job_func = jobqueue.get()\n            job_func()\n            jobqueue.task_done()\n\n    jobqueue = queue.Queue()\n\n    schedule.every(10).seconds.do(jobqueue.put, job)\n    schedule.every(10).seconds.do(jobqueue.put, job)\n    schedule.every(10).seconds.do(jobqueue.put, job)\n    schedule.every(10).seconds.do(jobqueue.put, job)\n    schedule.every(10).seconds.do(jobqueue.put, job)\n\n    worker_thread = threading.Thread(target=worker_main)\n    worker_thread.start()\n\n    while 1:\n        schedule.run_pending()\n        time.sleep(1)\n\nThis model also makes sense for a distributed application where the workers are separate processes that receive jobs from a distributed work queue. I like using beanstalkd with the beanstalkc Python library.\n"
  },
  {
    "path": "docs/reference.rst",
    "content": "Reference\n=========\n\n.. module:: schedule\n\nThis part of the documentation covers all the interfaces of schedule.\n\nMain Interface\n--------------\n\n.. autodata:: default_scheduler\n.. autodata:: jobs\n\n.. autofunction:: every\n.. autofunction:: run_pending\n.. autofunction:: run_all\n.. autofunction:: get_jobs\n.. autofunction:: clear\n.. autofunction:: cancel_job\n.. autofunction:: next_run\n.. autofunction:: idle_seconds\n\n\nClasses\n-------\n\n.. autoclass:: schedule.Scheduler\n   :members:\n   :undoc-members:\n\n.. autoclass:: schedule.Job\n   :members:\n   :undoc-members:\n\n\nExceptions\n----------\n\n.. autoexception:: schedule.CancelJob"
  },
  {
    "path": "docs/timezones.rst",
    "content": "Timezone & Daylight Saving Time\n===============================\n\nTimezone in .at()\n~~~~~~~~~~~~~~~~~\n\nSchedule supports setting the job execution time in another timezone using the ``.at`` method.\n\n**To work with timezones** `pytz <https://pypi.org/project/pytz/>`_ **must be installed!** Get it:\n\n.. code-block:: bash\n\n    pip install pytz\n\nTimezones are only available in the ``.at`` function, like so:\n\n.. code-block:: python\n\n    # Pass a timezone as a string\n    schedule.every().day.at(\"12:42\", \"Europe/Amsterdam\").do(job)\n\n    # Pass an pytz timezone object\n    from pytz import timezone\n    schedule.every().friday.at(\"12:42\", timezone(\"Africa/Lagos\")).do(job)\n\nSchedule uses the timezone to calculate the next runtime in local time.\nAll datetimes inside the library are stored `naive <https://docs.python.org/3/library/datetime.html>`_.\nThis causes the ``next_run`` and ``last_run`` to always be in Pythons local timezone.\n\nDaylight Saving Time\n~~~~~~~~~~~~~~~~~~~~\nScheduling jobs that do not specify a timezone do **not** take clock-changes into account.\nTimezone unaware jobs will use naive local times to calculate the next run.\nFor example, a job that is set to run every 4 hours might execute after 3 realtime hours when DST goes into effect.\n\nBut when passing a timezone to ``.at()``, DST **is** taken into account.\nThe job will run at the specified time, even when the clock changes.\n\nExample clock moves forward:\n~~~~~~~~~~~~~~~~~~~~~~~~~~~~\nWhen a job is scheduled in the gap that occurs when the clock moves forward, the job is scheduled after the gap.\n\nA job is scheduled ``.at(\"02:30\", \"Europe/Berlin\")``.\nWhen the clock moves from ``02:00`` to ``03:00``, the job will run once at ``03:30``.\nThe day after it will return to normal and run at ``02:30``.\n\nA job is scheduled ``.at(\"01:00\", \"Europe/London\")``.\nWhen the clock moves from ``01:00`` to ``02:00``, the job will run once at ``02:00``.\n\nExample clock moves backwards:\n~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\nA job is scheduled ``.at(\"02:30\", \"Europe/Berlin\")``.\nWhen the clock moves from ``02:00`` to ``03:00``, the job will run once at ``02:30``.\nIt will run only at the first time the clock hits ``02:30``, but not the second time.\nThe day after, it will return to normal and run at ``02:30``.\n\nExample scheduling across timezones\n~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\nLet's say we are in ``Europe/Berlin`` and local datetime is ``2022 march 20, 10:00:00``.\nAt this moment daylight saving time is not in effect in Berlin (UTC+1).\n\nWe schedule a job to run every day at 10:30:00 in America/New_York.\nAt this time, daylight saving time is in effect in New York (UTC-4).\n\n.. code-block:: python\n\n    s = every().day.at(\"10:30\", \"America/New_York\").do(job)\n\nBecause of the 5 hour time difference between Berlin and New York the job should effectively run at ``15:30:00``.\nSo the next run in Berlin time is ``2022 march 20, 15:30:00``:\n\n.. code-block:: python\n\n    print(s.next_run)\n    # 2022-03-20 15:30:00\n\n    print(repr(s))\n    # Every 1 day at 10:30:00 do job() (last run: [never], next run: 2022-03-20 15:30:00)\n"
  },
  {
    "path": "pyproject.toml",
    "content": "[build-system]\nrequires = [\"setuptools >= 61.0\"]\nbuild-backend = \"setuptools.build_meta\"\n\n[project]\nname = \"schedule\"\ndescription = \"Job scheduling for humans.\"\ndynamic = [\"version\", \"classifiers\", \"keywords\", \"authors\"]\nreadme = \"README.rst\"\nlicense = {text = \"MIT License\"}\n\nrequires-python = \">= 3.7\"\ndependencies = []\n\nmaintainers = [\n  {name = \"Sijmen Huizenga\"}\n]\n\n[project.optional-dependencies]\ntimezone = [\"pytz\"]\n\n[project.urls]\nDocumentation = \"https://schedule.readthedocs.io\"\nRepository = \"https://github.com/dbader/schedule.git\"\nIssues = \"https://github.com/dbader/schedule/issues\"\nChangelog = \"https://github.com/dbader/schedule/blob/master/HISTORY.rst\"\n"
  },
  {
    "path": "requirements-dev.txt",
    "content": "docutils\nPygments\npytest\npytest-cov\npytest-flake8\nSphinx\nblack==20.8b1\nclick==8.0.4\nmypy\npytz\ntypes-pytz"
  },
  {
    "path": "schedule/__init__.py",
    "content": "\"\"\"\nPython job scheduling for humans.\n\ngithub.com/dbader/schedule\n\nAn in-process scheduler for periodic jobs that uses the builder pattern\nfor configuration. Schedule lets you run Python functions (or any other\ncallable) periodically at pre-determined intervals using a simple,\nhuman-friendly syntax.\n\nInspired by Addam Wiggins' article \"Rethinking Cron\" [1] and the\n\"clockwork\" Ruby module [2][3].\n\nFeatures:\n    - A simple to use API for scheduling jobs.\n    - Very lightweight and no external dependencies.\n    - Excellent test coverage.\n    - Tested on Python 3.7, 3.8, 3.9, 3.10, 3.11 and 3.12\n\nUsage:\n    >>> import schedule\n    >>> import time\n\n    >>> def job(message='stuff'):\n    >>>     print(\"I'm working on:\", message)\n\n    >>> schedule.every(10).minutes.do(job)\n    >>> schedule.every(5).to(10).days.do(job)\n    >>> schedule.every().hour.do(job, message='things')\n    >>> schedule.every().day.at(\"10:30\").do(job)\n\n    >>> while True:\n    >>>     schedule.run_pending()\n    >>>     time.sleep(1)\n\n[1] https://adam.herokuapp.com/past/2010/4/13/rethinking_cron/\n[2] https://github.com/Rykian/clockwork\n[3] https://adam.herokuapp.com/past/2010/6/30/replace_cron_with_clockwork/\n\"\"\"\n\nfrom collections.abc import Hashable\nimport datetime\nimport functools\nimport logging\nimport random\nimport re\nimport time\nfrom typing import Set, List, Optional, Callable, Union\n\nlogger = logging.getLogger(\"schedule\")\n\n\nclass ScheduleError(Exception):\n    \"\"\"Base schedule exception\"\"\"\n\n    pass\n\n\nclass ScheduleValueError(ScheduleError):\n    \"\"\"Base schedule value error\"\"\"\n\n    pass\n\n\nclass IntervalError(ScheduleValueError):\n    \"\"\"An improper interval was used\"\"\"\n\n    pass\n\n\nclass CancelJob:\n    \"\"\"\n    Can be returned from a job to unschedule itself.\n    \"\"\"\n\n    pass\n\n\nclass Scheduler:\n    \"\"\"\n    Objects instantiated by the :class:`Scheduler <Scheduler>` are\n    factories to create jobs, keep record of scheduled jobs and\n    handle their execution.\n    \"\"\"\n\n    def __init__(self) -> None:\n        self.jobs: List[Job] = []\n\n    def run_pending(self) -> None:\n        \"\"\"\n        Run all jobs that are scheduled to run.\n\n        Please note that it is *intended behavior that run_pending()\n        does not run missed jobs*. For example, if you've registered a job\n        that should run every minute and you only call run_pending()\n        in one hour increments then your job won't be run 60 times in\n        between but only once.\n        \"\"\"\n        runnable_jobs = (job for job in self.jobs if job.should_run)\n        for job in sorted(runnable_jobs):\n            self._run_job(job)\n\n    def run_all(self, delay_seconds: int = 0) -> None:\n        \"\"\"\n        Run all jobs regardless if they are scheduled to run or not.\n\n        A delay of `delay` seconds is added between each job. This helps\n        distribute system load generated by the jobs more evenly\n        over time.\n\n        :param delay_seconds: A delay added between every executed job\n        \"\"\"\n        logger.debug(\n            \"Running *all* %i jobs with %is delay in between\",\n            len(self.jobs),\n            delay_seconds,\n        )\n        for job in self.jobs[:]:\n            self._run_job(job)\n            time.sleep(delay_seconds)\n\n    def get_jobs(self, tag: Optional[Hashable] = None) -> List[\"Job\"]:\n        \"\"\"\n        Gets scheduled jobs marked with the given tag, or all jobs\n        if tag is omitted.\n\n        :param tag: An identifier used to identify a subset of\n                    jobs to retrieve\n        \"\"\"\n        if tag is None:\n            return self.jobs[:]\n        else:\n            return [job for job in self.jobs if tag in job.tags]\n\n    def clear(self, tag: Optional[Hashable] = None) -> None:\n        \"\"\"\n        Deletes scheduled jobs marked with the given tag, or all jobs\n        if tag is omitted.\n\n        :param tag: An identifier used to identify a subset of\n                    jobs to delete\n        \"\"\"\n        if tag is None:\n            logger.debug(\"Deleting *all* jobs\")\n            del self.jobs[:]\n        else:\n            logger.debug('Deleting all jobs tagged \"%s\"', tag)\n            self.jobs[:] = (job for job in self.jobs if tag not in job.tags)\n\n    def cancel_job(self, job: \"Job\") -> None:\n        \"\"\"\n        Delete a scheduled job.\n\n        :param job: The job to be unscheduled\n        \"\"\"\n        try:\n            logger.debug('Cancelling job \"%s\"', str(job))\n            self.jobs.remove(job)\n        except ValueError:\n            logger.debug('Cancelling not-scheduled job \"%s\"', str(job))\n\n    def every(self, interval: int = 1) -> \"Job\":\n        \"\"\"\n        Schedule a new periodic job.\n\n        :param interval: A quantity of a certain time unit\n        :return: An unconfigured :class:`Job <Job>`\n        \"\"\"\n        job = Job(interval, self)\n        return job\n\n    def _run_job(self, job: \"Job\") -> None:\n        ret = job.run()\n        if isinstance(ret, CancelJob) or ret is CancelJob:\n            self.cancel_job(job)\n\n    def get_next_run(\n        self, tag: Optional[Hashable] = None\n    ) -> Optional[datetime.datetime]:\n        \"\"\"\n        Datetime when the next job should run.\n\n        :param tag: Filter the next run for the given tag parameter\n\n        :return: A :class:`~datetime.datetime` object\n                 or None if no jobs scheduled\n        \"\"\"\n        if not self.jobs:\n            return None\n        jobs_filtered = self.get_jobs(tag)\n        if not jobs_filtered:\n            return None\n        return min(jobs_filtered).next_run\n\n    next_run = property(get_next_run)\n\n    @property\n    def idle_seconds(self) -> Optional[float]:\n        \"\"\"\n        :return: Number of seconds until\n                 :meth:`next_run <Scheduler.next_run>`\n                 or None if no jobs are scheduled\n        \"\"\"\n        if not self.next_run:\n            return None\n        return (self.next_run - datetime.datetime.now()).total_seconds()\n\n\nclass Job:\n    \"\"\"\n    A periodic job as used by :class:`Scheduler`.\n\n    :param interval: A quantity of a certain time unit\n    :param scheduler: The :class:`Scheduler <Scheduler>` instance that\n                      this job will register itself with once it has\n                      been fully configured in :meth:`Job.do()`.\n\n    Every job runs at a given fixed time interval that is defined by:\n\n    * a :meth:`time unit <Job.second>`\n    * a quantity of `time units` defined by `interval`\n\n    A job is usually created and returned by :meth:`Scheduler.every`\n    method, which also defines its `interval`.\n    \"\"\"\n\n    def __init__(self, interval: int, scheduler: Optional[Scheduler] = None):\n        self.interval: int = interval  # pause interval * unit between runs\n        self.latest: Optional[int] = None  # upper limit to the interval\n        self.job_func: Optional[functools.partial] = None  # the job job_func to run\n\n        # time units, e.g. 'minutes', 'hours', ...\n        self.unit: Optional[str] = None\n\n        # optional time at which this job runs\n        self.at_time: Optional[datetime.time] = None\n\n        # optional time zone of the self.at_time field. Only relevant when at_time is not None\n        self.at_time_zone = None\n\n        # datetime of the last run\n        self.last_run: Optional[datetime.datetime] = None\n\n        # datetime of the next run\n        self.next_run: Optional[datetime.datetime] = None\n\n        # Weekday to run the job at. Only relevant when unit is 'weeks'.\n        # For example, when asking 'every week on tuesday' the start_day is 'tuesday'.\n        self.start_day: Optional[str] = None\n\n        # optional time of final run\n        self.cancel_after: Optional[datetime.datetime] = None\n\n        self.tags: Set[Hashable] = set()  # unique set of tags for the job\n        self.scheduler: Optional[Scheduler] = scheduler  # scheduler to register with\n\n    def __lt__(self, other) -> bool:\n        \"\"\"\n        PeriodicJobs are sortable based on the scheduled time they\n        run next.\n        \"\"\"\n        return self.next_run < other.next_run\n\n    def __str__(self) -> str:\n        if hasattr(self.job_func, \"__name__\"):\n            job_func_name = self.job_func.__name__  # type: ignore\n        else:\n            job_func_name = repr(self.job_func)\n\n        return (\"Job(interval={}, unit={}, do={}, args={}, kwargs={})\").format(\n            self.interval,\n            self.unit,\n            job_func_name,\n            \"()\" if self.job_func is None else self.job_func.args,\n            \"{}\" if self.job_func is None else self.job_func.keywords,\n        )\n\n    def __repr__(self):\n        def format_time(t):\n            return t.strftime(\"%Y-%m-%d %H:%M:%S\") if t else \"[never]\"\n\n        def is_repr(j):\n            return not isinstance(j, Job)\n\n        timestats = \"(last run: %s, next run: %s)\" % (\n            format_time(self.last_run),\n            format_time(self.next_run),\n        )\n\n        if hasattr(self.job_func, \"__name__\"):\n            job_func_name = self.job_func.__name__\n        else:\n            job_func_name = repr(self.job_func)\n\n        if self.job_func is not None:\n            args = [repr(x) if is_repr(x) else str(x) for x in self.job_func.args]\n            kwargs = [\"%s=%s\" % (k, repr(v)) for k, v in self.job_func.keywords.items()]\n            call_repr = job_func_name + \"(\" + \", \".join(args + kwargs) + \")\"\n        else:\n            call_repr = \"[None]\"\n\n        if self.at_time is not None:\n            return \"Every %s %s at %s do %s %s\" % (\n                self.interval,\n                self.unit[:-1] if self.interval == 1 else self.unit,\n                self.at_time,\n                call_repr,\n                timestats,\n            )\n        else:\n            fmt = (\n                \"Every %(interval)s \"\n                + (\"to %(latest)s \" if self.latest is not None else \"\")\n                + \"%(unit)s do %(call_repr)s %(timestats)s\"\n            )\n\n            return fmt % dict(\n                interval=self.interval,\n                latest=self.latest,\n                unit=(self.unit[:-1] if self.interval == 1 else self.unit),\n                call_repr=call_repr,\n                timestats=timestats,\n            )\n\n    @property\n    def second(self):\n        if self.interval != 1:\n            raise IntervalError(\"Use seconds instead of second\")\n        return self.seconds\n\n    @property\n    def seconds(self):\n        self.unit = \"seconds\"\n        return self\n\n    @property\n    def minute(self):\n        if self.interval != 1:\n            raise IntervalError(\"Use minutes instead of minute\")\n        return self.minutes\n\n    @property\n    def minutes(self):\n        self.unit = \"minutes\"\n        return self\n\n    @property\n    def hour(self):\n        if self.interval != 1:\n            raise IntervalError(\"Use hours instead of hour\")\n        return self.hours\n\n    @property\n    def hours(self):\n        self.unit = \"hours\"\n        return self\n\n    @property\n    def day(self):\n        if self.interval != 1:\n            raise IntervalError(\"Use days instead of day\")\n        return self.days\n\n    @property\n    def days(self):\n        self.unit = \"days\"\n        return self\n\n    @property\n    def week(self):\n        if self.interval != 1:\n            raise IntervalError(\"Use weeks instead of week\")\n        return self.weeks\n\n    @property\n    def weeks(self):\n        self.unit = \"weeks\"\n        return self\n\n    @property\n    def monday(self):\n        if self.interval != 1:\n            raise IntervalError(\n                \"Scheduling .monday() jobs is only allowed for weekly jobs. \"\n                \"Using .monday() on a job scheduled to run every 2 or more weeks \"\n                \"is not supported.\"\n            )\n        self.start_day = \"monday\"\n        return self.weeks\n\n    @property\n    def tuesday(self):\n        if self.interval != 1:\n            raise IntervalError(\n                \"Scheduling .tuesday() jobs is only allowed for weekly jobs. \"\n                \"Using .tuesday() on a job scheduled to run every 2 or more weeks \"\n                \"is not supported.\"\n            )\n        self.start_day = \"tuesday\"\n        return self.weeks\n\n    @property\n    def wednesday(self):\n        if self.interval != 1:\n            raise IntervalError(\n                \"Scheduling .wednesday() jobs is only allowed for weekly jobs. \"\n                \"Using .wednesday() on a job scheduled to run every 2 or more weeks \"\n                \"is not supported.\"\n            )\n        self.start_day = \"wednesday\"\n        return self.weeks\n\n    @property\n    def thursday(self):\n        if self.interval != 1:\n            raise IntervalError(\n                \"Scheduling .thursday() jobs is only allowed for weekly jobs. \"\n                \"Using .thursday() on a job scheduled to run every 2 or more weeks \"\n                \"is not supported.\"\n            )\n        self.start_day = \"thursday\"\n        return self.weeks\n\n    @property\n    def friday(self):\n        if self.interval != 1:\n            raise IntervalError(\n                \"Scheduling .friday() jobs is only allowed for weekly jobs. \"\n                \"Using .friday() on a job scheduled to run every 2 or more weeks \"\n                \"is not supported.\"\n            )\n        self.start_day = \"friday\"\n        return self.weeks\n\n    @property\n    def saturday(self):\n        if self.interval != 1:\n            raise IntervalError(\n                \"Scheduling .saturday() jobs is only allowed for weekly jobs. \"\n                \"Using .saturday() on a job scheduled to run every 2 or more weeks \"\n                \"is not supported.\"\n            )\n        self.start_day = \"saturday\"\n        return self.weeks\n\n    @property\n    def sunday(self):\n        if self.interval != 1:\n            raise IntervalError(\n                \"Scheduling .sunday() jobs is only allowed for weekly jobs. \"\n                \"Using .sunday() on a job scheduled to run every 2 or more weeks \"\n                \"is not supported.\"\n            )\n        self.start_day = \"sunday\"\n        return self.weeks\n\n    def tag(self, *tags: Hashable):\n        \"\"\"\n        Tags the job with one or more unique identifiers.\n\n        Tags must be hashable. Duplicate tags are discarded.\n\n        :param tags: A unique list of ``Hashable`` tags.\n        :return: The invoked job instance\n        \"\"\"\n        if not all(isinstance(tag, Hashable) for tag in tags):\n            raise TypeError(\"Tags must be hashable\")\n        self.tags.update(tags)\n        return self\n\n    def at(self, time_str: str, tz: Optional[str] = None):\n        \"\"\"\n        Specify a particular time that the job should be run at.\n\n        :param time_str: A string in one of the following formats:\n\n            - For daily jobs -> `HH:MM:SS` or `HH:MM`\n            - For hourly jobs -> `MM:SS` or `:MM`\n            - For minute jobs -> `:SS`\n\n            The format must make sense given how often the job is\n            repeating; for example, a job that repeats every minute\n            should not be given a string in the form `HH:MM:SS`. The\n            difference between `:MM` and `:SS` is inferred from the\n            selected time-unit (e.g. `every().hour.at(':30')` vs.\n            `every().minute.at(':30')`).\n\n        :param tz: The timezone that this timestamp refers to. Can be\n            a string that can be parsed by pytz.timezone(), or a pytz.BaseTzInfo object\n\n        :return: The invoked job instance\n        \"\"\"\n        if self.unit not in (\"days\", \"hours\", \"minutes\") and not self.start_day:\n            raise ScheduleValueError(\n                \"Invalid unit (valid units are `days`, `hours`, and `minutes`)\"\n            )\n\n        if tz is not None:\n            import pytz\n\n            if isinstance(tz, str):\n                self.at_time_zone = pytz.timezone(tz)  # type: ignore\n            elif isinstance(tz, pytz.BaseTzInfo):\n                self.at_time_zone = tz\n            else:\n                raise ScheduleValueError(\n                    \"Timezone must be string or pytz.timezone object\"\n                )\n\n        if not isinstance(time_str, str):\n            raise TypeError(\"at() should be passed a string\")\n        if self.unit == \"days\" or self.start_day:\n            if not re.match(r\"^[0-2]\\d:[0-5]\\d(:[0-5]\\d)?$\", time_str):\n                raise ScheduleValueError(\n                    \"Invalid time format for a daily job (valid format is HH:MM(:SS)?)\"\n                )\n        if self.unit == \"hours\":\n            if not re.match(r\"^([0-5]\\d)?:[0-5]\\d$\", time_str):\n                raise ScheduleValueError(\n                    \"Invalid time format for an hourly job (valid format is (MM)?:SS)\"\n                )\n\n        if self.unit == \"minutes\":\n            if not re.match(r\"^:[0-5]\\d$\", time_str):\n                raise ScheduleValueError(\n                    \"Invalid time format for a minutely job (valid format is :SS)\"\n                )\n        time_values = time_str.split(\":\")\n        hour: Union[str, int]\n        minute: Union[str, int]\n        second: Union[str, int]\n        if len(time_values) == 3:\n            hour, minute, second = time_values\n        elif len(time_values) == 2 and self.unit == \"minutes\":\n            hour = 0\n            minute = 0\n            _, second = time_values\n        elif len(time_values) == 2 and self.unit == \"hours\" and len(time_values[0]):\n            hour = 0\n            minute, second = time_values\n        else:\n            hour, minute = time_values\n            second = 0\n        if self.unit == \"days\" or self.start_day:\n            hour = int(hour)\n            if not (0 <= hour <= 23):\n                raise ScheduleValueError(\n                    \"Invalid number of hours ({} is not between 0 and 23)\"\n                )\n        elif self.unit == \"hours\":\n            hour = 0\n        elif self.unit == \"minutes\":\n            hour = 0\n            minute = 0\n        hour = int(hour)\n        minute = int(minute)\n        second = int(second)\n        self.at_time = datetime.time(hour, minute, second)\n        return self\n\n    def to(self, latest: int):\n        \"\"\"\n        Schedule the job to run at an irregular (randomized) interval.\n\n        The job's interval will randomly vary from the value given\n        to  `every` to `latest`. The range defined is inclusive on\n        both ends. For example, `every(A).to(B).seconds` executes\n        the job function every N seconds such that A <= N <= B.\n\n        :param latest: Maximum interval between randomized job runs\n        :return: The invoked job instance\n        \"\"\"\n        self.latest = latest\n        return self\n\n    def until(\n        self,\n        until_time: Union[datetime.datetime, datetime.timedelta, datetime.time, str],\n    ):\n        \"\"\"\n        Schedule job to run until the specified moment.\n\n        The job is canceled whenever the next run is calculated and it turns out the\n        next run is after the until_time. The job is also canceled right before it runs,\n        if the current time is after until_time. This latter case can happen when the\n        the job was scheduled to run before until_time, but runs after until_time.\n\n        If until_time is a moment in the past, ScheduleValueError is thrown.\n\n        :param until_time: A moment in the future representing the latest time a job can\n           be run. If only a time is supplied, the date is set to today.\n           The following formats are accepted:\n\n           - datetime.datetime\n           - datetime.timedelta\n           - datetime.time\n           - String in one of the following formats: \"%Y-%m-%d %H:%M:%S\",\n             \"%Y-%m-%d %H:%M\", \"%Y-%m-%d\", \"%H:%M:%S\", \"%H:%M\"\n             as defined by strptime() behaviour. If an invalid string format is passed,\n             ScheduleValueError is thrown.\n\n        :return: The invoked job instance\n        \"\"\"\n\n        if isinstance(until_time, datetime.datetime):\n            self.cancel_after = until_time\n        elif isinstance(until_time, datetime.timedelta):\n            self.cancel_after = datetime.datetime.now() + until_time\n        elif isinstance(until_time, datetime.time):\n            self.cancel_after = datetime.datetime.combine(\n                datetime.datetime.now(), until_time\n            )\n        elif isinstance(until_time, str):\n            cancel_after = self._decode_datetimestr(\n                until_time,\n                [\n                    \"%Y-%m-%d %H:%M:%S\",\n                    \"%Y-%m-%d %H:%M\",\n                    \"%Y-%m-%d\",\n                    \"%H:%M:%S\",\n                    \"%H:%M\",\n                ],\n            )\n            if cancel_after is None:\n                raise ScheduleValueError(\"Invalid string format for until()\")\n            if \"-\" not in until_time:\n                # the until_time is a time-only format. Set the date to today\n                now = datetime.datetime.now()\n                cancel_after = cancel_after.replace(\n                    year=now.year, month=now.month, day=now.day\n                )\n            self.cancel_after = cancel_after\n        else:\n            raise TypeError(\n                \"until() takes a string, datetime.datetime, datetime.timedelta, \"\n                \"datetime.time parameter\"\n            )\n        if self.cancel_after < datetime.datetime.now():\n            raise ScheduleValueError(\n                \"Cannot schedule a job to run until a time in the past\"\n            )\n        return self\n\n    def do(self, job_func: Callable, *args, **kwargs):\n        \"\"\"\n        Specifies the job_func that should be called every time the\n        job runs.\n\n        Any additional arguments are passed on to job_func when\n        the job runs.\n\n        :param job_func: The function to be scheduled\n        :return: The invoked job instance\n        \"\"\"\n        self.job_func = functools.partial(job_func, *args, **kwargs)\n        functools.update_wrapper(self.job_func, job_func)\n        self._schedule_next_run()\n        if self.scheduler is None:\n            raise ScheduleError(\n                \"Unable to a add job to schedule. \"\n                \"Job is not associated with an scheduler\"\n            )\n        self.scheduler.jobs.append(self)\n        return self\n\n    @property\n    def should_run(self) -> bool:\n        \"\"\"\n        :return: ``True`` if the job should be run now.\n        \"\"\"\n        assert self.next_run is not None, \"must run _schedule_next_run before\"\n        return datetime.datetime.now() >= self.next_run\n\n    def run(self):\n        \"\"\"\n        Run the job and immediately reschedule it.\n        If the job's deadline is reached (configured using .until()), the job is not\n        run and CancelJob is returned immediately. If the next scheduled run exceeds\n        the job's deadline, CancelJob is returned after the execution. In this latter\n        case CancelJob takes priority over any other returned value.\n\n        :return: The return value returned by the `job_func`, or CancelJob if the job's\n                 deadline is reached.\n\n        \"\"\"\n        if self._is_overdue(datetime.datetime.now()):\n            logger.debug(\"Cancelling job %s\", self)\n            return CancelJob\n\n        logger.debug(\"Running job %s\", self)\n        ret = self.job_func()\n        self.last_run = datetime.datetime.now()\n        self._schedule_next_run()\n\n        if self._is_overdue(self.next_run):\n            logger.debug(\"Cancelling job %s\", self)\n            return CancelJob\n        return ret\n\n    def _schedule_next_run(self) -> None:\n        \"\"\"\n        Compute the instant when this job should run next.\n        \"\"\"\n        if self.unit not in (\"seconds\", \"minutes\", \"hours\", \"days\", \"weeks\"):\n            raise ScheduleValueError(\n                \"Invalid unit (valid units are `seconds`, `minutes`, `hours`, \"\n                \"`days`, and `weeks`)\"\n            )\n        if self.latest is not None:\n            if not (self.latest >= self.interval):\n                raise ScheduleError(\"`latest` is greater than `interval`\")\n            interval = random.randint(self.interval, self.latest)\n        else:\n            interval = self.interval\n\n        # Do all computation in the context of the requested timezone\n        now = datetime.datetime.now(self.at_time_zone)\n\n        next_run = now\n\n        if self.start_day is not None:\n            if self.unit != \"weeks\":\n                raise ScheduleValueError(\"`unit` should be 'weeks'\")\n            next_run = _move_to_next_weekday(next_run, self.start_day)\n\n        if self.at_time is not None:\n            next_run = self._move_to_at_time(next_run)\n\n        period = datetime.timedelta(**{self.unit: interval})\n        if interval != 1:\n            next_run += period\n\n        while next_run <= now:\n            next_run += period\n\n        next_run = self._correct_utc_offset(\n            next_run, fixate_time=(self.at_time is not None)\n        )\n\n        # To keep the api consistent with older versions, we have to set the 'next_run' to a naive timestamp in the local timezone.\n        # Because we want to stay backwards compatible with older versions.\n        if self.at_time_zone is not None:\n            # Convert back to the local timezone\n            next_run = next_run.astimezone()\n\n            next_run = next_run.replace(tzinfo=None)\n\n        self.next_run = next_run\n\n    def _move_to_at_time(self, moment: datetime.datetime) -> datetime.datetime:\n        \"\"\"\n        Takes a datetime and moves the time-component to the job's at_time.\n        \"\"\"\n        if self.at_time is None:\n            return moment\n\n        kwargs = {\"second\": self.at_time.second, \"microsecond\": 0}\n\n        if self.unit == \"days\" or self.start_day is not None:\n            kwargs[\"hour\"] = self.at_time.hour\n\n        if self.unit in [\"days\", \"hours\"] or self.start_day is not None:\n            kwargs[\"minute\"] = self.at_time.minute\n\n        moment = moment.replace(**kwargs)  # type: ignore\n\n        # When we set the time elements, we might end up in a different UTC-offset than the current offset.\n        # This happens when we cross into or out of daylight saving time.\n        moment = self._correct_utc_offset(moment, fixate_time=True)\n\n        return moment\n\n    def _correct_utc_offset(\n        self, moment: datetime.datetime, fixate_time: bool\n    ) -> datetime.datetime:\n        \"\"\"\n        Given a datetime, corrects any mistakes in the utc offset.\n        This is similar to pytz' normalize, but adds the ability to attempt\n        keeping the time-component at the same hour/minute/second.\n        \"\"\"\n        if self.at_time_zone is None:\n            return moment\n        # Normalize corrects the utc-offset to match the timezone\n        # For example: When a date&time&offset does not exist within a timezone,\n        # the normalization will change the utc-offset to where it is valid.\n        # It does this while keeping the moment in time the same, by moving the\n        # time component opposite of the utc-change.\n        offset_before_normalize = moment.utcoffset()\n        moment = self.at_time_zone.normalize(moment)\n        offset_after_normalize = moment.utcoffset()\n\n        if offset_before_normalize == offset_after_normalize:\n            # There was no change in the utc-offset, datetime didn't change.\n            return moment\n\n        # The utc-offset and time-component has changed\n\n        if not fixate_time:\n            # No need to fixate the time.\n            return moment\n\n        offset_diff = offset_after_normalize - offset_before_normalize\n\n        # Adjust the time to reset the date-time to have the same HH:mm components\n        moment -= offset_diff\n\n        # Check if moving the timestamp back by the utc-offset-difference made it end up\n        # in a moment that does not exist within the current timezone/utc-offset\n        re_normalized_offset = self.at_time_zone.normalize(moment).utcoffset()\n        if re_normalized_offset != offset_after_normalize:\n            # We ended up in a DST Gap. The requested 'at' time does not exist\n            # within the current timezone/utc-offset. As a best effort, we will\n            # schedule the job 1 offset later than possible.\n            # For example, if 02:23 does not exist (because DST moves from 02:00\n            # to 03:00), this will schedule the job at 03:23.\n            moment += offset_diff\n        return moment\n\n    def _is_overdue(self, when: datetime.datetime):\n        return self.cancel_after is not None and when > self.cancel_after\n\n    def _decode_datetimestr(\n        self, datetime_str: str, formats: List[str]\n    ) -> Optional[datetime.datetime]:\n        for f in formats:\n            try:\n                return datetime.datetime.strptime(datetime_str, f)\n            except ValueError:\n                pass\n        return None\n\n\n# The following methods are shortcuts for not having to\n# create a Scheduler instance:\n\n#: Default :class:`Scheduler <Scheduler>` object\ndefault_scheduler = Scheduler()\n\n#: Default :class:`Jobs <Job>` list\njobs = default_scheduler.jobs  # todo: should this be a copy, e.g. jobs()?\n\n\ndef every(interval: int = 1) -> Job:\n    \"\"\"Calls :meth:`every <Scheduler.every>` on the\n    :data:`default scheduler instance <default_scheduler>`.\n    \"\"\"\n    return default_scheduler.every(interval)\n\n\ndef run_pending() -> None:\n    \"\"\"Calls :meth:`run_pending <Scheduler.run_pending>` on the\n    :data:`default scheduler instance <default_scheduler>`.\n    \"\"\"\n    default_scheduler.run_pending()\n\n\ndef run_all(delay_seconds: int = 0) -> None:\n    \"\"\"Calls :meth:`run_all <Scheduler.run_all>` on the\n    :data:`default scheduler instance <default_scheduler>`.\n    \"\"\"\n    default_scheduler.run_all(delay_seconds=delay_seconds)\n\n\ndef get_jobs(tag: Optional[Hashable] = None) -> List[Job]:\n    \"\"\"Calls :meth:`get_jobs <Scheduler.get_jobs>` on the\n    :data:`default scheduler instance <default_scheduler>`.\n    \"\"\"\n    return default_scheduler.get_jobs(tag)\n\n\ndef clear(tag: Optional[Hashable] = None) -> None:\n    \"\"\"Calls :meth:`clear <Scheduler.clear>` on the\n    :data:`default scheduler instance <default_scheduler>`.\n    \"\"\"\n    default_scheduler.clear(tag)\n\n\ndef cancel_job(job: Job) -> None:\n    \"\"\"Calls :meth:`cancel_job <Scheduler.cancel_job>` on the\n    :data:`default scheduler instance <default_scheduler>`.\n    \"\"\"\n    default_scheduler.cancel_job(job)\n\n\ndef next_run(tag: Optional[Hashable] = None) -> Optional[datetime.datetime]:\n    \"\"\"Calls :meth:`next_run <Scheduler.next_run>` on the\n    :data:`default scheduler instance <default_scheduler>`.\n    \"\"\"\n    return default_scheduler.get_next_run(tag)\n\n\ndef idle_seconds() -> Optional[float]:\n    \"\"\"Calls :meth:`idle_seconds <Scheduler.idle_seconds>` on the\n    :data:`default scheduler instance <default_scheduler>`.\n    \"\"\"\n    return default_scheduler.idle_seconds\n\n\ndef repeat(job, *args, **kwargs):\n    \"\"\"\n    Decorator to schedule a new periodic job.\n\n    Any additional arguments are passed on to the decorated function\n    when the job runs.\n\n    :param job: a :class:`Jobs <Job>`\n    \"\"\"\n\n    def _schedule_decorator(decorated_function):\n        job.do(decorated_function, *args, **kwargs)\n        return decorated_function\n\n    return _schedule_decorator\n\n\ndef _move_to_next_weekday(moment: datetime.datetime, weekday: str):\n    \"\"\"\n    Move the given timestamp to the nearest given weekday. May be this week\n    or next week. If the timestamp is already at the given weekday, it is not\n    moved.\n    \"\"\"\n    weekday_index = _weekday_index(weekday)\n\n    days_ahead = weekday_index - moment.weekday()\n    if days_ahead < 0:\n        # Target day already happened this week, move to next week\n        days_ahead += 7\n    return moment + datetime.timedelta(days=days_ahead)\n\n\ndef _weekday_index(day: str) -> int:\n    weekdays = (\n        \"monday\",\n        \"tuesday\",\n        \"wednesday\",\n        \"thursday\",\n        \"friday\",\n        \"saturday\",\n        \"sunday\",\n    )\n    if day not in weekdays:\n        raise ScheduleValueError(\n            \"Invalid start day (valid start days are {})\".format(weekdays)\n        )\n    return weekdays.index(day)\n"
  },
  {
    "path": "schedule/py.typed",
    "content": ""
  },
  {
    "path": "setup.cfg",
    "content": "[mypy]\nfiles=schedule\n"
  },
  {
    "path": "setup.py",
    "content": "import codecs\nfrom setuptools import setup\n\n\nSCHEDULE_VERSION = \"1.2.2\"\nSCHEDULE_DOWNLOAD_URL = \"https://github.com/dbader/schedule/tarball/\" + SCHEDULE_VERSION\n\n\ndef read_file(filename):\n    \"\"\"\n    Read a utf8 encoded text file and return its contents.\n    \"\"\"\n    with codecs.open(filename, \"r\", \"utf8\") as f:\n        return f.read()\n\n\nsetup(\n    name=\"schedule\",\n    packages=[\"schedule\"],\n    package_data={\"schedule\": [\"py.typed\"]},\n    version=SCHEDULE_VERSION,\n    description=\"Job scheduling for humans.\",\n    long_description=read_file(\"README.rst\"),\n    license=\"MIT\",\n    author=\"Daniel Bader\",\n    author_email=\"mail@dbader.org\",\n    url=\"https://github.com/dbader/schedule\",\n    download_url=SCHEDULE_DOWNLOAD_URL,\n    keywords=[\n        \"schedule\",\n        \"periodic\",\n        \"jobs\",\n        \"scheduling\",\n        \"clockwork\",\n        \"cron\",\n        \"scheduler\",\n        \"job scheduling\",\n    ],\n    classifiers=[\n        \"Intended Audience :: Developers\",\n        \"License :: OSI Approved :: MIT License\",\n        \"Development Status :: 5 - Production/Stable\",\n        \"Operating System :: OS Independent\",\n        \"Programming Language :: Python\",\n        \"Programming Language :: Python :: 3\",\n        \"Programming Language :: Python :: 3.7\",\n        \"Programming Language :: Python :: 3.8\",\n        \"Programming Language :: Python :: 3.9\",\n        \"Programming Language :: Python :: 3.10\",\n        \"Programming Language :: Python :: 3.11\",\n        \"Programming Language :: Python :: 3.12\",\n        \"Natural Language :: English\",\n    ],\n    python_requires=\">=3.7\",\n)\n"
  },
  {
    "path": "test_schedule.py",
    "content": "\"\"\"Unit tests for schedule.py\"\"\"\n\nimport datetime\nimport functools\nfrom unittest import mock, TestCase\nimport os\nimport time\n\n# Silence \"missing docstring\", \"method could be a function\",\n# \"class already defined\", and \"too many public methods\" messages:\n# pylint: disable-msg=R0201,C0111,E0102,R0904,R0901\n\nimport schedule\nfrom schedule import (\n    every,\n    repeat,\n    ScheduleError,\n    ScheduleValueError,\n    IntervalError,\n)\n\n# POSIX TZ string format\nTZ_BERLIN = \"CET-1CEST,M3.5.0,M10.5.0/3\"\nTZ_AUCKLAND = \"NZST-12NZDT,M9.5.0,M4.1.0/3\"\nTZ_CHATHAM = \"<+1245>-12:45<+1345>,M9.5.0/2:45,M4.1.0/3:45\"\nTZ_UTC = \"UTC0\"\n\n# Set timezone to Europe/Berlin (CEST) to ensure global reproducibility\nos.environ[\"TZ\"] = TZ_BERLIN\ntime.tzset()\n\n\ndef make_mock_job(name=None):\n    job = mock.Mock()\n    job.__name__ = name or \"job\"\n    return job\n\n\nclass mock_datetime:\n    \"\"\"\n    Monkey-patch datetime for predictable results\n    \"\"\"\n\n    def __init__(self, year, month, day, hour, minute, second=0, zone=None, fold=0):\n        self.year = year\n        self.month = month\n        self.day = day\n        self.hour = hour\n        self.minute = minute\n        self.second = second\n        self.zone = zone\n        self.fold = fold\n        self.original_datetime = None\n        self.original_zone = None\n\n    def __enter__(self):\n        class MockDate(datetime.datetime):\n            @classmethod\n            def today(cls):\n                return cls(self.year, self.month, self.day)\n\n            @classmethod\n            def now(cls, tz=None):\n                mock_date = cls(\n                    self.year,\n                    self.month,\n                    self.day,\n                    self.hour,\n                    self.minute,\n                    self.second,\n                    fold=self.fold,\n                )\n                if tz:\n                    return mock_date.astimezone(tz)\n                return mock_date\n\n        self.original_datetime = datetime.datetime\n        datetime.datetime = MockDate\n\n        self.original_zone = os.environ.get(\"TZ\")\n        if self.zone:\n            os.environ[\"TZ\"] = self.zone\n            time.tzset()\n\n        return MockDate(\n            self.year, self.month, self.day, self.hour, self.minute, self.second\n        )\n\n    def __exit__(self, *args, **kwargs):\n        datetime.datetime = self.original_datetime\n        if self.original_zone:\n            os.environ[\"TZ\"] = self.original_zone\n            time.tzset()\n\n\nclass SchedulerTests(TestCase):\n    def setUp(self):\n        schedule.clear()\n\n    def make_tz_mock_job(self, name=None):\n        try:\n            import pytz\n        except ModuleNotFoundError:\n            self.skipTest(\"pytz unavailable\")\n            return\n        return make_mock_job(name)\n\n    def test_time_units(self):\n        assert every().seconds.unit == \"seconds\"\n        assert every().minutes.unit == \"minutes\"\n        assert every().hours.unit == \"hours\"\n        assert every().days.unit == \"days\"\n        assert every().weeks.unit == \"weeks\"\n\n        job_instance = schedule.Job(interval=2)\n        # without a context manager, it incorrectly raises an error because\n        # it is not callable\n        with self.assertRaises(IntervalError):\n            job_instance.minute\n        with self.assertRaises(IntervalError):\n            job_instance.hour\n        with self.assertRaises(IntervalError):\n            job_instance.day\n        with self.assertRaises(IntervalError):\n            job_instance.week\n        with self.assertRaisesRegex(\n            IntervalError,\n            (\n                r\"Scheduling \\.monday\\(\\) jobs is only allowed for weekly jobs\\. \"\n                r\"Using \\.monday\\(\\) on a job scheduled to run every 2 or more \"\n                r\"weeks is not supported\\.\"\n            ),\n        ):\n            job_instance.monday\n        with self.assertRaisesRegex(\n            IntervalError,\n            (\n                r\"Scheduling \\.tuesday\\(\\) jobs is only allowed for weekly jobs\\. \"\n                r\"Using \\.tuesday\\(\\) on a job scheduled to run every 2 or more \"\n                r\"weeks is not supported\\.\"\n            ),\n        ):\n            job_instance.tuesday\n        with self.assertRaisesRegex(\n            IntervalError,\n            (\n                r\"Scheduling \\.wednesday\\(\\) jobs is only allowed for weekly jobs\\. \"\n                r\"Using \\.wednesday\\(\\) on a job scheduled to run every 2 or more \"\n                r\"weeks is not supported\\.\"\n            ),\n        ):\n            job_instance.wednesday\n        with self.assertRaisesRegex(\n            IntervalError,\n            (\n                r\"Scheduling \\.thursday\\(\\) jobs is only allowed for weekly jobs\\. \"\n                r\"Using \\.thursday\\(\\) on a job scheduled to run every 2 or more \"\n                r\"weeks is not supported\\.\"\n            ),\n        ):\n            job_instance.thursday\n        with self.assertRaisesRegex(\n            IntervalError,\n            (\n                r\"Scheduling \\.friday\\(\\) jobs is only allowed for weekly jobs\\. \"\n                r\"Using \\.friday\\(\\) on a job scheduled to run every 2 or more \"\n                r\"weeks is not supported\\.\"\n            ),\n        ):\n            job_instance.friday\n        with self.assertRaisesRegex(\n            IntervalError,\n            (\n                r\"Scheduling \\.saturday\\(\\) jobs is only allowed for weekly jobs\\. \"\n                r\"Using \\.saturday\\(\\) on a job scheduled to run every 2 or more \"\n                r\"weeks is not supported\\.\"\n            ),\n        ):\n            job_instance.saturday\n        with self.assertRaisesRegex(\n            IntervalError,\n            (\n                r\"Scheduling \\.sunday\\(\\) jobs is only allowed for weekly jobs\\. \"\n                r\"Using \\.sunday\\(\\) on a job scheduled to run every 2 or more \"\n                r\"weeks is not supported\\.\"\n            ),\n        ):\n            job_instance.sunday\n\n        # test an invalid unit\n        job_instance.unit = \"foo\"\n        self.assertRaises(ScheduleValueError, job_instance.at, \"1:0:0\")\n        self.assertRaises(ScheduleValueError, job_instance._schedule_next_run)\n\n        # test start day exists but unit is not 'weeks'\n        job_instance.unit = \"days\"\n        job_instance.start_day = 1\n        self.assertRaises(ScheduleValueError, job_instance._schedule_next_run)\n\n        # test weeks with an invalid start day\n        job_instance.unit = \"weeks\"\n        job_instance.start_day = \"bar\"\n        self.assertRaises(ScheduleValueError, job_instance._schedule_next_run)\n\n        # test a valid unit with invalid hours/minutes/seconds\n        job_instance.unit = \"days\"\n        self.assertRaises(ScheduleValueError, job_instance.at, \"25:00:00\")\n        self.assertRaises(ScheduleValueError, job_instance.at, \"00:61:00\")\n        self.assertRaises(ScheduleValueError, job_instance.at, \"00:00:61\")\n\n        # test invalid time format\n        self.assertRaises(ScheduleValueError, job_instance.at, \"25:0:0\")\n        self.assertRaises(ScheduleValueError, job_instance.at, \"0:61:0\")\n        self.assertRaises(ScheduleValueError, job_instance.at, \"0:0:61\")\n\n        # test self.latest >= self.interval\n        job_instance.latest = 1\n        self.assertRaises(ScheduleError, job_instance._schedule_next_run)\n        job_instance.latest = 3\n        self.assertRaises(ScheduleError, job_instance._schedule_next_run)\n\n    def test_next_run_with_tag(self):\n        with mock_datetime(2014, 6, 28, 12, 0):\n            job1 = every(5).seconds.do(make_mock_job(name=\"job1\")).tag(\"tag1\")\n            job2 = every(2).hours.do(make_mock_job(name=\"job2\")).tag(\"tag1\", \"tag2\")\n            job3 = (\n                every(1)\n                .minutes.do(make_mock_job(name=\"job3\"))\n                .tag(\"tag1\", \"tag3\", \"tag2\")\n            )\n            assert schedule.next_run(\"tag1\") == job1.next_run\n            assert schedule.default_scheduler.get_next_run(\"tag2\") == job3.next_run\n            assert schedule.next_run(\"tag3\") == job3.next_run\n            assert schedule.next_run(\"tag4\") is None\n\n    def test_singular_time_units_match_plural_units(self):\n        assert every().second.unit == every().seconds.unit\n        assert every().minute.unit == every().minutes.unit\n        assert every().hour.unit == every().hours.unit\n        assert every().day.unit == every().days.unit\n        assert every().week.unit == every().weeks.unit\n\n    def test_time_range(self):\n        with mock_datetime(2014, 6, 28, 12, 0):\n            mock_job = make_mock_job()\n\n            # Choose a sample size large enough that it's unlikely the\n            # same value will be chosen each time.\n            minutes = set(\n                [\n                    every(5).to(30).minutes.do(mock_job).next_run.minute\n                    for i in range(100)\n                ]\n            )\n\n            assert len(minutes) > 1\n            assert min(minutes) >= 5\n            assert max(minutes) <= 30\n\n    def test_time_range_repr(self):\n        mock_job = make_mock_job()\n\n        with mock_datetime(2014, 6, 28, 12, 0):\n            job_repr = repr(every(5).to(30).minutes.do(mock_job))\n\n        assert job_repr.startswith(\"Every 5 to 30 minutes do job()\")\n\n    def test_at_time(self):\n        mock_job = make_mock_job()\n        assert every().day.at(\"10:30\").do(mock_job).next_run.hour == 10\n        assert every().day.at(\"10:30\").do(mock_job).next_run.minute == 30\n        assert every().day.at(\"20:59\").do(mock_job).next_run.minute == 59\n        assert every().day.at(\"10:30:50\").do(mock_job).next_run.second == 50\n\n        self.assertRaises(ScheduleValueError, every().day.at, \"2:30:000001\")\n        self.assertRaises(ScheduleValueError, every().day.at, \"::2\")\n        self.assertRaises(ScheduleValueError, every().day.at, \".2\")\n        self.assertRaises(ScheduleValueError, every().day.at, \"2\")\n        self.assertRaises(ScheduleValueError, every().day.at, \":2\")\n        self.assertRaises(ScheduleValueError, every().day.at, \" 2:30:00\")\n        self.assertRaises(ScheduleValueError, every().day.at, \"59:59\")\n        self.assertRaises(ScheduleValueError, every().do, lambda: 0)\n        self.assertRaises(TypeError, every().day.at, 2)\n\n        # without a context manager, it incorrectly raises an error because\n        # it is not callable\n        with self.assertRaises(IntervalError):\n            every(interval=2).second\n        with self.assertRaises(IntervalError):\n            every(interval=2).minute\n        with self.assertRaises(IntervalError):\n            every(interval=2).hour\n        with self.assertRaises(IntervalError):\n            every(interval=2).day\n        with self.assertRaises(IntervalError):\n            every(interval=2).week\n        with self.assertRaises(IntervalError):\n            every(interval=2).monday\n        with self.assertRaises(IntervalError):\n            every(interval=2).tuesday\n        with self.assertRaises(IntervalError):\n            every(interval=2).wednesday\n        with self.assertRaises(IntervalError):\n            every(interval=2).thursday\n        with self.assertRaises(IntervalError):\n            every(interval=2).friday\n        with self.assertRaises(IntervalError):\n            every(interval=2).saturday\n        with self.assertRaises(IntervalError):\n            every(interval=2).sunday\n\n    def test_until_time(self):\n        mock_job = make_mock_job()\n        # Check argument parsing\n        with mock_datetime(2020, 1, 1, 10, 0, 0) as m:\n            assert every().day.until(datetime.datetime(3000, 1, 1, 20, 30)).do(\n                mock_job\n            ).cancel_after == datetime.datetime(3000, 1, 1, 20, 30, 0)\n            assert every().day.until(datetime.datetime(3000, 1, 1, 20, 30, 50)).do(\n                mock_job\n            ).cancel_after == datetime.datetime(3000, 1, 1, 20, 30, 50)\n            assert every().day.until(datetime.time(12, 30)).do(\n                mock_job\n            ).cancel_after == m.replace(hour=12, minute=30, second=0, microsecond=0)\n            assert every().day.until(datetime.time(12, 30, 50)).do(\n                mock_job\n            ).cancel_after == m.replace(hour=12, minute=30, second=50, microsecond=0)\n\n            assert every().day.until(\n                datetime.timedelta(days=40, hours=5, minutes=12, seconds=42)\n            ).do(mock_job).cancel_after == datetime.datetime(2020, 2, 10, 15, 12, 42)\n\n            assert every().day.until(\"10:30\").do(mock_job).cancel_after == m.replace(\n                hour=10, minute=30, second=0, microsecond=0\n            )\n            assert every().day.until(\"10:30:50\").do(mock_job).cancel_after == m.replace(\n                hour=10, minute=30, second=50, microsecond=0\n            )\n            assert every().day.until(\"3000-01-01 10:30\").do(\n                mock_job\n            ).cancel_after == datetime.datetime(3000, 1, 1, 10, 30, 0)\n            assert every().day.until(\"3000-01-01 10:30:50\").do(\n                mock_job\n            ).cancel_after == datetime.datetime(3000, 1, 1, 10, 30, 50)\n            assert every().day.until(datetime.datetime(3000, 1, 1, 10, 30, 50)).do(\n                mock_job\n            ).cancel_after == datetime.datetime(3000, 1, 1, 10, 30, 50)\n\n        # Invalid argument types\n        self.assertRaises(TypeError, every().day.until, 123)\n        self.assertRaises(ScheduleValueError, every().day.until, \"123\")\n        self.assertRaises(ScheduleValueError, every().day.until, \"01-01-3000\")\n\n        # Using .until() with moments in the passed\n        self.assertRaises(\n            ScheduleValueError,\n            every().day.until,\n            datetime.datetime(2019, 12, 31, 23, 59),\n        )\n        self.assertRaises(\n            ScheduleValueError, every().day.until, datetime.timedelta(minutes=-1)\n        )\n        one_hour_ago = datetime.datetime.now() - datetime.timedelta(hours=1)\n        self.assertRaises(ScheduleValueError, every().day.until, one_hour_ago)\n\n        # Unschedule job after next_run passes the deadline\n        schedule.clear()\n        with mock_datetime(2020, 1, 1, 11, 35, 10):\n            mock_job.reset_mock()\n            every(5).seconds.until(datetime.time(11, 35, 20)).do(mock_job)\n            with mock_datetime(2020, 1, 1, 11, 35, 15):\n                schedule.run_pending()\n                assert mock_job.call_count == 1\n                assert len(schedule.jobs) == 1\n            with mock_datetime(2020, 1, 1, 11, 35, 20):\n                schedule.run_all()\n                assert mock_job.call_count == 2\n                assert len(schedule.jobs) == 0\n\n        # Unschedule job because current execution time has passed deadline\n        schedule.clear()\n        with mock_datetime(2020, 1, 1, 11, 35, 10):\n            mock_job.reset_mock()\n            every(5).seconds.until(datetime.time(11, 35, 20)).do(mock_job)\n            with mock_datetime(2020, 1, 1, 11, 35, 50):\n                schedule.run_pending()\n                assert mock_job.call_count == 0\n                assert len(schedule.jobs) == 0\n\n    def test_weekday_at_todady(self):\n        mock_job = make_mock_job()\n\n        # This date is a wednesday\n        with mock_datetime(2020, 11, 25, 22, 38, 5):\n            job = every().wednesday.at(\"22:38:10\").do(mock_job)\n            assert job.next_run.hour == 22\n            assert job.next_run.minute == 38\n            assert job.next_run.second == 10\n            assert job.next_run.year == 2020\n            assert job.next_run.month == 11\n            assert job.next_run.day == 25\n\n            job = every().wednesday.at(\"22:39\").do(mock_job)\n            assert job.next_run.hour == 22\n            assert job.next_run.minute == 39\n            assert job.next_run.second == 00\n            assert job.next_run.year == 2020\n            assert job.next_run.month == 11\n            assert job.next_run.day == 25\n\n    def test_at_time_hour(self):\n        with mock_datetime(2010, 1, 6, 12, 20):\n            mock_job = make_mock_job()\n            assert every().hour.at(\":30\").do(mock_job).next_run.hour == 12\n            assert every().hour.at(\":30\").do(mock_job).next_run.minute == 30\n            assert every().hour.at(\":30\").do(mock_job).next_run.second == 0\n            assert every().hour.at(\":10\").do(mock_job).next_run.hour == 13\n            assert every().hour.at(\":10\").do(mock_job).next_run.minute == 10\n            assert every().hour.at(\":10\").do(mock_job).next_run.second == 0\n            assert every().hour.at(\":00\").do(mock_job).next_run.hour == 13\n            assert every().hour.at(\":00\").do(mock_job).next_run.minute == 0\n            assert every().hour.at(\":00\").do(mock_job).next_run.second == 0\n\n            self.assertRaises(ScheduleValueError, every().hour.at, \"2:30:00\")\n            self.assertRaises(ScheduleValueError, every().hour.at, \"::2\")\n            self.assertRaises(ScheduleValueError, every().hour.at, \".2\")\n            self.assertRaises(ScheduleValueError, every().hour.at, \"2\")\n            self.assertRaises(ScheduleValueError, every().hour.at, \" 2:30\")\n            self.assertRaises(ScheduleValueError, every().hour.at, \"61:00\")\n            self.assertRaises(ScheduleValueError, every().hour.at, \"00:61\")\n            self.assertRaises(ScheduleValueError, every().hour.at, \"01:61\")\n            self.assertRaises(TypeError, every().hour.at, 2)\n\n            # test the 'MM:SS' format\n            assert every().hour.at(\"30:05\").do(mock_job).next_run.hour == 12\n            assert every().hour.at(\"30:05\").do(mock_job).next_run.minute == 30\n            assert every().hour.at(\"30:05\").do(mock_job).next_run.second == 5\n            assert every().hour.at(\"10:25\").do(mock_job).next_run.hour == 13\n            assert every().hour.at(\"10:25\").do(mock_job).next_run.minute == 10\n            assert every().hour.at(\"10:25\").do(mock_job).next_run.second == 25\n            assert every().hour.at(\"00:40\").do(mock_job).next_run.hour == 13\n            assert every().hour.at(\"00:40\").do(mock_job).next_run.minute == 0\n            assert every().hour.at(\"00:40\").do(mock_job).next_run.second == 40\n\n    def test_at_time_minute(self):\n        with mock_datetime(2010, 1, 6, 12, 20, 30):\n            mock_job = make_mock_job()\n            assert every().minute.at(\":40\").do(mock_job).next_run.hour == 12\n            assert every().minute.at(\":40\").do(mock_job).next_run.minute == 20\n            assert every().minute.at(\":40\").do(mock_job).next_run.second == 40\n            assert every().minute.at(\":10\").do(mock_job).next_run.hour == 12\n            assert every().minute.at(\":10\").do(mock_job).next_run.minute == 21\n            assert every().minute.at(\":10\").do(mock_job).next_run.second == 10\n\n            self.assertRaises(ScheduleValueError, every().minute.at, \"::2\")\n            self.assertRaises(ScheduleValueError, every().minute.at, \".2\")\n            self.assertRaises(ScheduleValueError, every().minute.at, \"2\")\n            self.assertRaises(ScheduleValueError, every().minute.at, \"2:30:00\")\n            self.assertRaises(ScheduleValueError, every().minute.at, \"2:30\")\n            self.assertRaises(ScheduleValueError, every().minute.at, \" :30\")\n            self.assertRaises(TypeError, every().minute.at, 2)\n\n    def test_next_run_time(self):\n        with mock_datetime(2010, 1, 6, 12, 15):\n            mock_job = make_mock_job()\n            assert schedule.next_run() is None\n            assert every().minute.do(mock_job).next_run.minute == 16\n            assert every(5).minutes.do(mock_job).next_run.minute == 20\n            assert every().hour.do(mock_job).next_run.hour == 13\n            assert every().day.do(mock_job).next_run.day == 7\n            assert every().day.at(\"09:00\").do(mock_job).next_run.day == 7\n            assert every().day.at(\"12:30\").do(mock_job).next_run.day == 6\n            assert every().week.do(mock_job).next_run.day == 13\n            assert every().monday.do(mock_job).next_run.day == 11\n            assert every().tuesday.do(mock_job).next_run.day == 12\n            assert every().wednesday.do(mock_job).next_run.day == 13\n            assert every().thursday.do(mock_job).next_run.day == 7\n            assert every().friday.do(mock_job).next_run.day == 8\n            assert every().saturday.do(mock_job).next_run.day == 9\n            assert every().sunday.do(mock_job).next_run.day == 10\n            assert (\n                every().minute.until(datetime.time(12, 17)).do(mock_job).next_run.minute\n                == 16\n            )\n\n    def test_next_run_time_day_end(self):\n        mock_job = make_mock_job()\n        # At day 1, schedule job to run at daily 23:30\n        with mock_datetime(2010, 12, 1, 23, 0, 0):\n            job = every().day.at(\"23:30\").do(mock_job)\n            # first occurrence same day\n            assert job.next_run.day == 1\n            assert job.next_run.hour == 23\n\n        # Running the job 01:00 on day 2, afterwards the job should be\n        # scheduled at 23:30 the same day. This simulates a job that started\n        # on day 1 at 23:30 and took 1,5 hours to finish\n        with mock_datetime(2010, 12, 2, 1, 0, 0):\n            job.run()\n            assert job.next_run.day == 2\n            assert job.next_run.hour == 23\n\n        # Run the job at 23:30 on day 2, afterwards the job should be\n        # scheduled at 23:30 the next day\n        with mock_datetime(2010, 12, 2, 23, 30, 0):\n            job.run()\n            assert job.next_run.day == 3\n            assert job.next_run.hour == 23\n\n    def test_next_run_time_hour_end(self):\n        try:\n            import pytz\n        except ModuleNotFoundError:\n            self.skipTest(\"pytz unavailable\")\n\n        self.tst_next_run_time_hour_end(None, 0)\n\n    def test_next_run_time_hour_end_london(self):\n        try:\n            import pytz\n        except ModuleNotFoundError:\n            self.skipTest(\"pytz unavailable\")\n\n        self.tst_next_run_time_hour_end(\"Europe/London\", 0)\n\n    def test_next_run_time_hour_end_katmandu(self):\n        try:\n            import pytz\n        except ModuleNotFoundError:\n            self.skipTest(\"pytz unavailable\")\n\n        # 12:00 in Berlin is 15:45 in Kathmandu\n        # this test schedules runs at :10 minutes, so job runs at\n        # 16:10 in Kathmandu, which is 13:25 in Berlin\n        # in local time we don't run at :10, but at :25, offset of 15 minutes\n        self.tst_next_run_time_hour_end(\"Asia/Kathmandu\", 15)\n\n    def tst_next_run_time_hour_end(self, tz, offsetMinutes):\n        mock_job = make_mock_job()\n\n        # So a job scheduled to run at :10 in Kathmandu, runs always 25 minutes\n        with mock_datetime(2010, 10, 10, 12, 0, 0):\n            job = every().hour.at(\":10\", tz).do(mock_job)\n            assert job.next_run.hour == 12\n            assert job.next_run.minute == 10 + offsetMinutes\n\n        with mock_datetime(2010, 10, 10, 13, 0, 0):\n            job.run()\n            assert job.next_run.hour == 13\n            assert job.next_run.minute == 10 + offsetMinutes\n\n        with mock_datetime(2010, 10, 10, 13, 30, 0):\n            job.run()\n            assert job.next_run.hour == 14\n            assert job.next_run.minute == 10 + offsetMinutes\n\n    def test_next_run_time_minute_end(self):\n        self.tst_next_run_time_minute_end(None)\n\n    def test_next_run_time_minute_end_london(self):\n        try:\n            import pytz\n        except ModuleNotFoundError:\n            self.skipTest(\"pytz unavailable\")\n\n        self.tst_next_run_time_minute_end(\"Europe/London\")\n\n    def test_next_run_time_minute_end_katmhandu(self):\n        try:\n            import pytz\n        except ModuleNotFoundError:\n            self.skipTest(\"pytz unavailable\")\n\n        self.tst_next_run_time_minute_end(\"Asia/Kathmandu\")\n\n    def tst_next_run_time_minute_end(self, tz):\n        mock_job = make_mock_job()\n        with mock_datetime(2010, 10, 10, 10, 10, 0):\n            job = every().minute.at(\":15\", tz).do(mock_job)\n            assert job.next_run.minute == 10\n            assert job.next_run.second == 15\n\n        with mock_datetime(2010, 10, 10, 10, 10, 59):\n            job.run()\n            assert job.next_run.minute == 11\n            assert job.next_run.second == 15\n\n        with mock_datetime(2010, 10, 10, 10, 12, 14):\n            job.run()\n            assert job.next_run.minute == 12\n            assert job.next_run.second == 15\n\n        with mock_datetime(2010, 10, 10, 10, 12, 16):\n            job.run()\n            assert job.next_run.minute == 13\n            assert job.next_run.second == 15\n\n    def test_tz(self):\n        mock_job = self.make_tz_mock_job()\n        with mock_datetime(2022, 2, 1, 23, 15):\n            # Current Berlin time: feb-1 23:15 (local)\n            # Current India time: feb-2 03:45\n            # Expected to run India time: feb-2 06:30\n            # Next run Berlin time: feb-2 02:00\n            next = every().day.at(\"06:30\", \"Asia/Kolkata\").do(mock_job).next_run\n            assert next.day == 2\n            assert next.hour == 2\n            assert next.minute == 0\n\n    def test_tz_daily_midnight(self):\n        mock_job = self.make_tz_mock_job()\n        with mock_datetime(2023, 4, 14, 4, 50):\n            # Current Berlin time: april-14 04:50 (local) (during daylight saving)\n            # Current US/Central time: april-13 21:50\n            # Expected to run US/Central time: april-14 00:00\n            # Next run Berlin time: april-14 07:00\n            next = every().day.at(\"00:00\", \"US/Central\").do(mock_job).next_run\n            assert next.day == 14\n            assert next.hour == 7\n            assert next.minute == 0\n\n    def test_tz_daily_half_hour_offset(self):\n        mock_job = self.make_tz_mock_job()\n        with mock_datetime(2022, 4, 8, 10, 0):\n            # Current Berlin time: 10:00 (local) (during daylight saving)\n            # Current NY time: 04:00\n            # Expected to run NY time: 10:30\n            # Next run Berlin time: 16:30\n            next = every().day.at(\"10:30\", \"America/New_York\").do(mock_job).next_run\n            assert next.hour == 16\n            assert next.minute == 30\n\n    def test_tz_daily_dst(self):\n        mock_job = self.make_tz_mock_job()\n        import pytz\n\n        with mock_datetime(2022, 3, 20, 10, 0):\n            # Current Berlin time: 10:00 (local) (NOT during daylight saving)\n            # Current NY time: 04:00 (during daylight saving)\n            # Expected to run NY time: 10:30\n            # Next run Berlin time: 15:30\n            tz = pytz.timezone(\"America/New_York\")\n            next = every().day.at(\"10:30\", tz).do(mock_job).next_run\n            assert next.hour == 15\n            assert next.minute == 30\n\n    def test_tz_daily_dst_skip_hour(self):\n        mock_job = self.make_tz_mock_job()\n        # Test the DST-case that is described in the documentation\n        with mock_datetime(2023, 3, 26, 1, 30):\n            # Current Berlin time: 01:30 (NOT during daylight saving)\n            # Expected to run: 02:30 - this time doesn't exist\n            #  because clock moves from 02:00 to 03:00\n            # Next run: 03:30\n            job = every().day.at(\"02:30\", \"Europe/Berlin\").do(mock_job)\n            assert job.next_run.day == 26\n            assert job.next_run.hour == 3\n            assert job.next_run.minute == 30\n        with mock_datetime(2023, 3, 27, 1, 30):\n            # the next day the job shall again run at 02:30\n            job.run()\n            assert job.next_run.day == 27\n            assert job.next_run.hour == 2\n            assert job.next_run.minute == 30\n\n    def test_tz_daily_dst_overlap_hour(self):\n        mock_job = self.make_tz_mock_job()\n        # Test the DST-case that is described in the documentation\n        with mock_datetime(2023, 10, 29, 1, 30):\n            # Current Berlin time: 01:30 (during daylight saving)\n            # Expected to run: 02:30 - this time exists twice\n            #  because clock moves from 03:00 to 02:00\n            # Next run should be at the first occurrence of 02:30\n            job = every().day.at(\"02:30\", \"Europe/Berlin\").do(mock_job)\n            assert job.next_run.day == 29\n            assert job.next_run.hour == 2\n            assert job.next_run.minute == 30\n        with mock_datetime(2023, 10, 29, 2, 35):\n            # After the job runs, the next run should be scheduled on the next day at 02:30\n            job.run()\n            assert job.next_run.day == 30\n            assert job.next_run.hour == 2\n            assert job.next_run.minute == 30\n\n    def test_tz_daily_exact_future_scheduling(self):\n        mock_job = self.make_tz_mock_job()\n        with mock_datetime(2022, 3, 20, 10, 0):\n            # Current Berlin time: 10:00 (local) (NOT during daylight saving)\n            # Current Krasnoyarsk time: 16:00\n            # Expected to run Krasnoyarsk time: mar-21 11:00\n            # Next run Berlin time: mar-21 05:00\n            # Expected idle seconds: 68400\n            schedule.clear()\n            every().day.at(\"11:00\", \"Asia/Krasnoyarsk\").do(mock_job)\n            expected_delta = (\n                datetime.datetime(2022, 3, 21, 5, 0) - datetime.datetime.now()\n            )\n            assert schedule.idle_seconds() == expected_delta.total_seconds()\n\n    def test_tz_daily_utc(self):\n        mock_job = self.make_tz_mock_job()\n        with mock_datetime(2023, 9, 18, 10, 59, 0, TZ_AUCKLAND):\n            # Testing issue #598\n            # Current Auckland time: 10:59 (local) (NOT during daylight saving)\n            # Current UTC time: 21:59 (17 september)\n            # Expected to run UTC time: sept-18 00:00\n            # Next run Auckland time: sept-18 12:00\n            schedule.clear()\n            next = every().day.at(\"00:00\", \"UTC\").do(mock_job).next_run\n            assert next.day == 18\n            assert next.hour == 12\n            assert next.minute == 0\n\n            # Test that .day.at() and .monday.at() are equivalent in this case\n            schedule.clear()\n            next = every().monday.at(\"00:00\", \"UTC\").do(mock_job).next_run\n            assert next.day == 18\n            assert next.hour == 12\n            assert next.minute == 0\n\n    def test_tz_daily_issue_592(self):\n        mock_job = self.make_tz_mock_job()\n        with mock_datetime(2023, 7, 15, 13, 0, 0, TZ_UTC):\n            # Testing issue #592\n            # Current UTC time: 13:00\n            # Expected to run US East time: 9:45 (daylight saving active)\n            # Next run UTC time: july-15 13:45\n            schedule.clear()\n            next = every().day.at(\"09:45\", \"US/Eastern\").do(mock_job).next_run\n            assert next.day == 15\n            assert next.hour == 13\n            assert next.minute == 45\n\n    def test_tz_daily_exact_seconds_precision(self):\n        mock_job = self.make_tz_mock_job()\n        with mock_datetime(2023, 10, 19, 15, 0, 0, TZ_UTC):\n            # Testing issue #603\n            # Current UTC: oktober-19 15:00\n            # Current Amsterdam: oktober-19 17:00 (daylight saving active)\n            # Expected run Amsterdam: oktober-20 00:00:20 (daylight saving active)\n            # Next run UTC time: oktober-19 22:00:20\n            schedule.clear()\n            next = every().day.at(\"00:00:20\", \"Europe/Amsterdam\").do(mock_job).next_run\n            assert next.day == 19\n            assert next.hour == 22\n            assert next.minute == 00\n            assert next.second == 20\n\n    def test_tz_weekly_sunday_conversion(self):\n        mock_job = self.make_tz_mock_job()\n        with mock_datetime(2023, 10, 22, 23, 0, 0, TZ_UTC):\n            # Current UTC: sunday 22-okt 23:00\n            # Current Amsterdam: monday 23-okt 01:00 (daylight saving active)\n            # Expected run Amsterdam: sunday 29 oktober 23:00 (daylight saving NOT active)\n            # Next run UTC time: oktober-29 22:00\n            schedule.clear()\n            next = every().sunday.at(\"23:00\", \"Europe/Amsterdam\").do(mock_job).next_run\n            assert next.day == 29\n            assert next.hour == 22\n            assert next.minute == 00\n\n    def test_tz_daily_new_year_offset(self):\n        mock_job = self.make_tz_mock_job()\n        with mock_datetime(2023, 12, 31, 23, 0, 0):\n            # Current Berlin time: dec-31 23:00 (local)\n            # Current Sydney time: jan-1 09:00 (next day)\n            # Expected to run Sydney time: jan-1 12:00\n            # Next run Berlin time: jan-1 02:00\n            next = every().day.at(\"12:00\", \"Australia/Sydney\").do(mock_job).next_run\n            assert next.day == 1\n            assert next.hour == 2\n            assert next.minute == 0\n\n    def test_tz_daily_end_year_cross_continent(self):\n        mock_job = self.make_tz_mock_job()\n        with mock_datetime(2023, 12, 31, 23, 50):\n            # End of the year in Berlin\n            # Current Berlin time: dec-31 23:50\n            # Current Tokyo time: jan-1 07:50 (next day)\n            # Expected to run Tokyo time: jan-1 09:00\n            # Next run Berlin time: jan-1 01:00\n            next = every().day.at(\"09:00\", \"Asia/Tokyo\").do(mock_job).next_run\n            assert next.day == 1\n            assert next.hour == 1\n            assert next.minute == 0\n\n    def test_tz_daily_end_month_offset(self):\n        mock_job = self.make_tz_mock_job()\n        with mock_datetime(2023, 2, 28, 23, 50):\n            # End of the month (non-leap year) in Berlin\n            # Current Berlin time: feb-28 23:50\n            # Current Sydney time: mar-1 09:50 (next day)\n            # Expected to run Sydney time: mar-1 10:00\n            # Next run Berlin time: mar-1 00:00\n            next = every().day.at(\"10:00\", \"Australia/Sydney\").do(mock_job).next_run\n            assert next.day == 1\n            assert next.hour == 0\n            assert next.minute == 0\n\n    def test_tz_daily_leap_year(self):\n        mock_job = self.make_tz_mock_job()\n        with mock_datetime(2024, 2, 28, 23, 50):\n            # End of the month (leap year) in Berlin\n            # Current Berlin time: feb-28 23:50\n            # Current Dubai time: feb-29 02:50\n            # Expected to run Dubai time: feb-29 04:00\n            # Next run Berlin time: feb-29 01:00\n            next = every().day.at(\"04:00\", \"Asia/Dubai\").do(mock_job).next_run\n            assert next.month == 2\n            assert next.day == 29\n            assert next.hour == 1\n            assert next.minute == 0\n\n    def test_tz_daily_issue_605(self):\n        mock_job = self.make_tz_mock_job()\n        with mock_datetime(2023, 9, 18, 10, 00, 0, TZ_AUCKLAND):\n            schedule.clear()\n            # Testing issue #605\n            # Current time: Monday 18 September 10:00 NZST\n            # Current time UTC: Sunday 17 September 22:00\n            # We expect the job to run at 23:00 on Sunday 17 September NZST\n            # That is an expected idle time of 1 hour\n            # Expected next run in NZST: 2023-09-18 11:00:00\n            next = schedule.every().day.at(\"23:00\", \"UTC\").do(mock_job).next_run\n            assert round(schedule.idle_seconds() / 3600) == 1\n            assert next.day == 18\n            assert next.hour == 11\n            assert next.minute == 0\n\n    def test_tz_daily_dst_starting_point(self):\n        mock_job = self.make_tz_mock_job()\n        with mock_datetime(2023, 3, 26, 1, 30):\n            # Daylight Saving Time starts in Berlin\n            # In Berlin, 26 March 2023, 02:00:00 clocks were turned forward 1 hour\n            # In London, 26 March 2023, 01:00:00 clocks were turned forward 1 hour\n            # Current Berlin time:  26 March 01:30 (UTC+1)\n            # Current London time:  26 March 00:30 (UTC+0)\n            # Expected London time: 26 March 02:00 (UTC+1)\n            # Expected Berlin time: 26 March 03:00 (UTC+2)\n            next = every().day.at(\"01:00\", \"Europe/London\").do(mock_job).next_run\n            assert next.day == 26\n            assert next.hour == 3\n            assert next.minute == 0\n\n    def test_tz_daily_dst_ending_point(self):\n        mock_job = self.make_tz_mock_job()\n        with mock_datetime(2023, 10, 29, 2, 30, fold=1):\n            # Daylight Saving Time ends in Berlin\n            # Current Berlin time: oct-29 02:30 (after moving back to 02:00 due to DST end)\n            # Current Istanbul time: oct-29 04:30\n            # Expected to run Istanbul time: oct-29 06:00\n            # Next run Berlin time: oct-29 04:00\n            next = every().day.at(\"06:00\", \"Europe/Istanbul\").do(mock_job).next_run\n            assert next.hour == 4\n            assert next.minute == 0\n\n    def test_tz_daily_issue_608_pre_dst(self):\n        mock_job = self.make_tz_mock_job()\n        with mock_datetime(2023, 9, 18, 10, 00, 0, TZ_AUCKLAND):\n            # See ticket #608\n            # Testing timezone conversion the week before daylight saving comes into effect\n            # Current time: Monday 18 September 10:00 NZST\n            # Current time UTC: Sunday 17 September 22:00\n            # Expected next run in NZST: 2023-09-18 11:00:00\n            schedule.clear()\n            next = schedule.every().day.at(\"23:00\", \"UTC\").do(mock_job).next_run\n            assert next.day == 18\n            assert next.hour == 11\n            assert next.minute == 0\n\n    def test_tz_daily_issue_608_post_dst(self):\n        mock_job = self.make_tz_mock_job()\n        with mock_datetime(2024, 4, 8, 10, 00, 0, TZ_AUCKLAND):\n            # See ticket #608\n            # Testing timezone conversion the week after daylight saving ends\n            # Current time: Monday 8 April 10:00 NZST\n            # Current time UTC: Sunday 7 April 22:00\n            # Expected next run in NZDT: 2023-04-08 11:00:00\n            schedule.clear()\n            next = schedule.every().day.at(\"23:00\", \"UTC\").do(mock_job).next_run\n            assert next.day == 8\n            assert next.hour == 11\n            assert next.minute == 0\n\n    def test_tz_daily_issue_608_mid_dst(self):\n        mock_job = self.make_tz_mock_job()\n        with mock_datetime(2023, 9, 25, 10, 00, 0, TZ_AUCKLAND):\n            # See ticket #608\n            # Testing timezone conversion during the week after daylight saving comes into effect\n            # Current time: Monday 25 September 10:00 NZDT\n            # Current time UTC: Sunday 24 September 21:00\n            # Expected next run in UTC:  2023-09-24 23:00\n            # Expected next run in NZDT: 2023-09-25 12:00\n            schedule.clear()\n            next = schedule.every().day.at(\"23:00\", \"UTC\").do(mock_job).next_run\n            assert next.month == 9\n            assert next.day == 25\n            assert next.hour == 12\n            assert next.minute == 0\n\n    def test_tz_daily_issue_608_before_dst_end(self):\n        mock_job = self.make_tz_mock_job()\n        with mock_datetime(2024, 4, 1, 10, 00, 0, TZ_AUCKLAND):\n            # See ticket #608\n            # Testing timezone conversion during the week before daylight saving ends\n            # Current time: Monday 1 April 10:00 NZDT\n            # Current time UTC: Friday 31 March 21:00\n            # Expected next run in UTC:  2023-03-31 23:00\n            # Expected next run in NZDT: 2024-04-01 12:00\n            schedule.clear()\n            next = schedule.every().day.at(\"23:00\", \"UTC\").do(mock_job).next_run\n            assert next.month == 4\n            assert next.day == 1\n            assert next.hour == 12\n            assert next.minute == 0\n\n    def test_tz_hourly_intermediate_conversion(self):\n        mock_job = self.make_tz_mock_job()\n        with mock_datetime(2024, 5, 4, 14, 37, 22, TZ_CHATHAM):\n            # Crurent time: 14:37:22  New Zealand, Chatham Islands (UTC +12:45)\n            # Current time: 3 may, 23:22:22 Canada, Newfoundland (UTC -2:30)\n            # Exected next run in Newfoundland: 4 may, 09:14:45\n            # Expected next run in Chatham: 5 may, 00:29:45\n            schedule.clear()\n            next = (\n                schedule.every(10)\n                .hours.at(\"14:45\", \"Canada/Newfoundland\")\n                .do(mock_job)\n                .next_run\n            )\n            assert next.day == 5\n            assert next.hour == 0\n            assert next.minute == 29\n            assert next.second == 45\n\n    def test_tz_minutes_year_round(self):\n        mock_job = self.make_tz_mock_job()\n        # Test a full year of scheduling across timezones, where one timezone\n        # is in the northern hemisphere and the other in the southern hemisphere\n        # These two timezones are also a bit exotic (not the usual UTC+1, UTC-1)\n        # Local timezone: Newfoundland, Canada: UTC-2:30 / DST UTC-3:30\n        # Remote timezone: Chatham Islands, New Zealand: UTC+12:45 / DST UTC+13:45\n        schedule.clear()\n        job = schedule.every(20).minutes.at(\":13\", \"Canada/Newfoundland\").do(mock_job)\n        with mock_datetime(2024, 9, 29, 2, 20, 0, TZ_CHATHAM):\n            # First run, nothing special, no utc-offset change\n            # Current time: 29 sept, 02:20:00  Chatham\n            # Current time: 28 sept, 11:05:00  Newfoundland\n            # Expected time: 28 sept, 11:20:13 Newfoundland\n            # Expected time: 29 sept, 02:40:13 Chatham\n            job.run()\n            assert job.next_run.day == 29\n            assert job.next_run.hour == 2\n            assert job.next_run.minute == 40\n            assert job.next_run.second == 13\n        with mock_datetime(2024, 9, 29, 2, 40, 14, TZ_CHATHAM):\n            # next-schedule happens 1 second behind schedule\n            job.run()\n            # On 29 Sep, 02:45 2024, in Chatham, the clock is moved +1 hour\n            # Thus, the next run happens AFTER the local timezone exits DST\n            # Current time:  29 sept, 02:40:14 Chatham      (UTC +12:45)\n            # Current time:  28 sept, 11:25:14 Newfoundland (UTC -2:30)\n            # Expected time: 28 sept, 11:45:13 Newfoundland (UTC -2:30)\n            # Expected time: 29 sept, 04:00:13 Chatham      (UTC +13:45)\n            assert job.next_run.day == 29\n            assert job.next_run.hour == 4\n            assert job.next_run.minute == 00\n            assert job.next_run.second == 13\n        with mock_datetime(2024, 11, 3, 2, 23, 55, TZ_CHATHAM, fold=0):\n            # Time is right before Newfoundland exits DST\n            # Local time will move 1 hour back at 03:00\n\n            job.run()\n            # There are no timezone switches yet, nothing special going on:\n            # Current time:  3 Nov, 02:23:55 Chatham\n            # Expected time: 3 Nov, 02:43:13 Chatham\n            assert job.next_run.day == 3\n            assert job.next_run.hour == 2\n            assert job.next_run.minute == 43  # Within the fold, first occurrence\n            assert job.next_run.second == 13\n        with mock_datetime(2024, 11, 3, 2, 23, 55, TZ_CHATHAM, fold=1):\n            # Time is during the fold. Local time has moved back 1 hour, this is\n            # the second occurrence of the 02:23 time.\n\n            job.run()\n            # Current time:  3 Nov, 02:23:55 Chatham\n            # Expected time: 3 Nov, 02:43:13 Chatham\n            assert job.next_run.day == 3\n            assert job.next_run.hour == 2\n            assert job.next_run.minute == 43\n            assert job.next_run.second == 13\n        with mock_datetime(2025, 3, 9, 19, 00, 00, TZ_CHATHAM):\n            # Time is right before Newfoundland enters DST\n            # At 02:00, the remote clock will move forward 1 hour\n\n            job.run()\n            # Current time:  9 March, 19:00:00 Chatham      (UTC +13:45)\n            # Current time:  9 March, 01:45:00 Newfoundland (UTC -3:30)\n            # Expected time: 9 March, 03:05:13 Newfoundland (UTC -2:30)\n            # Expected time  9 March, 19:20:13 Chatham      (UTC +13:45)\n\n            assert job.next_run.day == 9\n            assert job.next_run.hour == 19\n            assert job.next_run.minute == 20\n            assert job.next_run.second == 13\n        with mock_datetime(2025, 4, 7, 17, 55, 00, TZ_CHATHAM):\n            # Time is within the few hours before Catham exits DST\n            # At 03:45, the local clock moves back 1 hour\n\n            job.run()\n            # Current time:  7 April, 17:55:00 Chatham\n            # Current time:  7 April, 02:40:00 Newfoundland\n            # Expected time: 7 April, 03:00:13 Newfoundland\n            # Expected time  7 April, 18:15:13 Chatham\n            assert job.next_run.day == 7\n            assert job.next_run.hour == 18\n            assert job.next_run.minute == 15\n            assert job.next_run.second == 13\n        with mock_datetime(2025, 4, 7, 18, 55, 00, TZ_CHATHAM):\n            # Schedule the next run exactly when the clock moved backwards\n            # Curren time is before the clock-move, next run is after the clock change\n\n            job.run()\n            # Current time:  7 April, 18:55:00 Chatham\n            # Current time:  7 April, 03:40:00 Newfoundland\n            # Expected time: 7 April, 03:00:13 Newfoundland (clock moved back)\n            # Expected time  7 April, 19:15:13 Chatham\n            assert job.next_run.day == 7\n            assert job.next_run.hour == 19\n            assert job.next_run.minute == 15\n            assert job.next_run.second == 13\n        with mock_datetime(2025, 4, 7, 19, 15, 13, TZ_CHATHAM):\n            # Schedule during the fold in the remote timezone\n\n            job.run()\n            # Current time:  7 April, 19:15:13 Chatham\n            # Current time:  7 April, 03:00:13 Newfoundland (fold)\n            # Expected time: 7 April, 03:20:13 Newfoundland (fold)\n            # Expected time: 7 April, 19:35:13 Chatham\n            assert job.next_run.day == 7\n            assert job.next_run.hour == 19\n            assert job.next_run.minute == 35\n            assert job.next_run.second == 13\n\n    def test_tz_weekly_large_interval_forward(self):\n        mock_job = self.make_tz_mock_job()\n        # Testing scheduling large intervals that skip over clock move forward\n        with mock_datetime(2024, 3, 28, 11, 0, 0, TZ_BERLIN):\n            # At March 31st 2024, 02:00:00 clocks were turned forward 1 hour\n            schedule.clear()\n            next = (\n                schedule.every(7)\n                .days.at(\"11:00\", \"Europe/Berlin\")\n                .do(mock_job)\n                .next_run\n            )\n            assert next.month == 4\n            assert next.day == 4\n            assert next.hour == 11\n            assert next.minute == 0\n            assert next.second == 0\n\n    def test_tz_weekly_large_interval_backward(self):\n        mock_job = self.make_tz_mock_job()\n        import pytz\n\n        # Testing scheduling large intervals that skip over clock move back\n        with mock_datetime(2024, 10, 25, 11, 0, 0, TZ_BERLIN):\n            # At March 31st 2024, 02:00:00 clocks were turned forward 1 hour\n            schedule.clear()\n            next = (\n                schedule.every(7)\n                .days.at(\"11:00\", \"Europe/Berlin\")\n                .do(mock_job)\n                .next_run\n            )\n            assert next.month == 11\n            assert next.day == 1\n            assert next.hour == 11\n            assert next.minute == 0\n            assert next.second == 0\n\n    def test_tz_daily_skip_dst_change(self):\n        mock_job = self.make_tz_mock_job()\n        with mock_datetime(2024, 11, 3, 10, 0):\n            # At 3 November 2024, 02:00:00 clocks are turned backward 1 hour\n            # The job skips the whole DST change becaus it runs at 14:00\n            # Current time Berlin:     3 Nov, 10:00\n            # Current time Anchorage:  3 Nov, 00:00 (UTC-08:00)\n            # Expected time Anchorage: 3 Nov, 14:00 (UTC-09:00)\n            # Expected time Berlin:    4 Nov, 00:00\n            schedule.clear()\n            next = (\n                schedule.every()\n                .day.at(\"14:00\", \"America/Anchorage\")\n                .do(mock_job)\n                .next_run\n            )\n            assert next.day == 4\n            assert next.hour == 0\n            assert next.minute == 00\n\n    def test_tz_daily_different_simultaneous_dst_change(self):\n        mock_job = self.make_tz_mock_job()\n\n        # TZ_BERLIN_EXTRA is the same as Berlin, but during summer time\n        # moves the clock 2 hours forward instead of 1\n        # This is a fictional timezone\n        TZ_BERLIN_EXTRA = \"CET-01CEST-03,M3.5.0,M10.5.0/3\"\n        with mock_datetime(2024, 3, 31, 0, 0, 0, TZ_BERLIN_EXTRA):\n            # In Berlin at March 31 2024, 02:00:00 clocks were turned forward 1 hour\n            # In Berlin Extra, the clocks move forward 2 hour at the same time\n            # Current time Berlin Extra:  31 Mar, 00:00 (UTC+01:00)\n            # Current time Berlin:        31 Mar, 00:00 (UTC+01:00)\n            # Expected time Berlin:       31 Mar, 10:00 (UTC+02:00)\n            # Expected time Berlin Extra: 31 Mar, 11:00 (UTC+03:00)\n            schedule.clear()\n            next = (\n                schedule.every().day.at(\"10:00\", \"Europe/Berlin\").do(mock_job).next_run\n            )\n            assert next.day == 31\n            assert next.hour == 11\n            assert next.minute == 00\n\n    def test_tz_daily_opposite_dst_change(self):\n        mock_job = self.make_tz_mock_job()\n\n        # TZ_BERLIN_INVERTED changes in the opposite direction of Berlin\n        # This is a fictional timezone\n        TZ_BERLIN_INVERTED = \"CET-1CEST,M10.5.0/3,M3.5.0\"\n        with mock_datetime(2024, 3, 31, 0, 0, 0, TZ_BERLIN_INVERTED):\n            # In Berlin at March 31 2024, 02:00:00 clocks were turned forward 1 hour\n            # In Berlin Inverted, the clocks move back 1 hour at the same time\n            # Current time Berlin Inverted:  31 Mar, 00:00 (UTC+02:00)\n            # Current time Berlin:           31 Mar, 00:00 (UTC+01:00)\n            # Expected time Berlin:          31 Mar, 10:00 (UTC+02:00) +9 hour\n            # Expected time Berlin Inverted: 31 Mar, 09:00 (UTC+01:00)\n            schedule.clear()\n            next = (\n                schedule.every().day.at(\"10:00\", \"Europe/Berlin\").do(mock_job).next_run\n            )\n            assert next.day == 31\n            assert next.hour == 9\n            assert next.minute == 00\n\n    def test_tz_invalid_timezone_exceptions(self):\n        mock_job = self.make_tz_mock_job()\n        import pytz\n\n        with self.assertRaises(pytz.exceptions.UnknownTimeZoneError):\n            every().day.at(\"10:30\", \"FakeZone\").do(mock_job)\n\n        with self.assertRaises(ScheduleValueError):\n            every().day.at(\"10:30\", 43).do(mock_job)\n\n    def test_align_utc_offset_no_timezone(self):\n        job = schedule.every().day.at(\"10:00\").do(make_mock_job())\n        now = datetime.datetime(2024, 5, 11, 10, 30, 55, 0)\n        aligned_time = job._correct_utc_offset(now, fixate_time=True)\n        self.assertEqual(now, aligned_time)\n\n    def setup_utc_offset_test(self):\n        try:\n            import pytz\n        except ModuleNotFoundError:\n            self.skipTest(\"pytz unavailable\")\n        job = (\n            schedule.every()\n            .day.at(\"10:00\", \"Europe/Berlin\")\n            .do(make_mock_job(\"tz-test\"))\n        )\n        tz = pytz.timezone(\"Europe/Berlin\")\n        return (job, tz)\n\n    def test_align_utc_offset_no_change(self):\n        (job, tz) = self.setup_utc_offset_test()\n        now = tz.localize(datetime.datetime(2023, 3, 26, 1, 30))\n        aligned_time = job._correct_utc_offset(now, fixate_time=False)\n        self.assertEqual(now, aligned_time)\n\n    def test_align_utc_offset_with_dst_gap(self):\n        (job, tz) = self.setup_utc_offset_test()\n        # Non-existent time in Berlin timezone\n        gap_time = tz.localize(datetime.datetime(2024, 3, 31, 2, 30, 0))\n        aligned_time = job._correct_utc_offset(gap_time, fixate_time=True)\n\n        assert aligned_time.utcoffset() == datetime.timedelta(hours=2)\n        assert aligned_time.day == 31\n        assert aligned_time.hour == 3\n        assert aligned_time.minute == 30\n\n    def test_align_utc_offset_with_dst_fold(self):\n        (job, tz) = self.setup_utc_offset_test()\n        # This time exists twice, this is the first occurance\n        overlap_time = tz.localize(datetime.datetime(2024, 10, 27, 2, 30))\n        aligned_time = job._correct_utc_offset(overlap_time, fixate_time=False)\n        # Since the time exists twice, no fixate_time flag should yield the first occurrence\n        first_occurrence = tz.localize(datetime.datetime(2024, 10, 27, 2, 30, fold=0))\n        self.assertEqual(first_occurrence, aligned_time)\n\n    def test_align_utc_offset_with_dst_fold_fixate_1(self):\n        (job, tz) = self.setup_utc_offset_test()\n        # This time exists twice, this is the 1st occurance\n        overlap_time = tz.localize(datetime.datetime(2024, 10, 27, 1, 30), is_dst=True)\n        overlap_time += datetime.timedelta(\n            hours=1\n        )  # puts it at 02:30+02:00 (Which exists once)\n\n        aligned_time = job._correct_utc_offset(overlap_time, fixate_time=True)\n        # The time should not have moved, because the original time is valid\n        assert aligned_time.utcoffset() == datetime.timedelta(hours=2)\n        assert aligned_time.hour == 2\n        assert aligned_time.minute == 30\n        assert aligned_time.day == 27\n\n    def test_align_utc_offset_with_dst_fold_fixate_2(self):\n        (job, tz) = self.setup_utc_offset_test()\n        # 02:30 exists twice, this is the 2nd occurance\n        overlap_time = tz.localize(datetime.datetime(2024, 10, 27, 2, 30), is_dst=False)\n        # The time 2024-10-27 02:30:00+01:00 exists once\n\n        aligned_time = job._correct_utc_offset(overlap_time, fixate_time=True)\n        # The time was valid, should not have been moved\n        assert aligned_time.utcoffset() == datetime.timedelta(hours=1)\n        assert aligned_time.hour == 2\n        assert aligned_time.minute == 30\n        assert aligned_time.day == 27\n\n    def test_align_utc_offset_after_fold_fixate(self):\n        (job, tz) = self.setup_utc_offset_test()\n        # This time is 30 minutes after a folded hour.\n        duplicate_time = tz.localize(datetime.datetime(2024, 10, 27, 2, 30))\n        duplicate_time += datetime.timedelta(hours=1)\n\n        aligned_time = job._correct_utc_offset(duplicate_time, fixate_time=False)\n\n        assert aligned_time.utcoffset() == datetime.timedelta(hours=1)\n        assert aligned_time.hour == 3\n        assert aligned_time.minute == 30\n        assert aligned_time.day == 27\n\n    def test_daylight_saving_time(self):\n        mock_job = make_mock_job()\n        # 27 March 2022, 02:00:00 clocks were turned forward 1 hour\n        with mock_datetime(2022, 3, 27, 0, 0):\n            assert every(4).hours.do(mock_job).next_run.hour == 4\n\n        # Sunday, 30 October 2022, 03:00:00 clocks were turned backward 1 hour\n        with mock_datetime(2022, 10, 30, 0, 0):\n            assert every(4).hours.do(mock_job).next_run.hour == 4\n\n    def test_move_to_next_weekday_today(self):\n        monday = datetime.datetime(2024, 5, 13, 10, 27, 54)\n        tuesday = schedule._move_to_next_weekday(monday, \"monday\")\n        assert tuesday.day == 13  # today! Time didn't change.\n        assert tuesday.hour == 10\n        assert tuesday.minute == 27\n\n    def test_move_to_next_weekday_tommorrow(self):\n        monday = datetime.datetime(2024, 5, 13, 10, 27, 54)\n        tuesday = schedule._move_to_next_weekday(monday, \"tuesday\")\n        assert tuesday.day == 14  # 1 day ahead\n        assert tuesday.hour == 10\n        assert tuesday.minute == 27\n\n    def test_move_to_next_weekday_nextweek(self):\n        wednesday = datetime.datetime(2024, 5, 15, 10, 27, 54)\n        tuesday = schedule._move_to_next_weekday(wednesday, \"tuesday\")\n        assert tuesday.day == 21  # next week monday\n        assert tuesday.hour == 10\n        assert tuesday.minute == 27\n\n    def test_run_all(self):\n        mock_job = make_mock_job()\n        every().minute.do(mock_job)\n        every().hour.do(mock_job)\n        every().day.at(\"11:00\").do(mock_job)\n        schedule.run_all()\n        assert mock_job.call_count == 3\n\n    def test_run_all_with_decorator(self):\n        mock_job = make_mock_job()\n\n        @repeat(every().minute)\n        def job1():\n            mock_job()\n\n        @repeat(every().hour)\n        def job2():\n            mock_job()\n\n        @repeat(every().day.at(\"11:00\"))\n        def job3():\n            mock_job()\n\n        schedule.run_all()\n        assert mock_job.call_count == 3\n\n    def test_run_all_with_decorator_args(self):\n        mock_job = make_mock_job()\n\n        @repeat(every().minute, 1, 2, \"three\", foo=23, bar={})\n        def job(*args, **kwargs):\n            mock_job(*args, **kwargs)\n\n        schedule.run_all()\n        mock_job.assert_called_once_with(1, 2, \"three\", foo=23, bar={})\n\n    def test_run_all_with_decorator_defaultargs(self):\n        mock_job = make_mock_job()\n\n        @repeat(every().minute)\n        def job(nothing=None):\n            mock_job(nothing)\n\n        schedule.run_all()\n        mock_job.assert_called_once_with(None)\n\n    def test_job_func_args_are_passed_on(self):\n        mock_job = make_mock_job()\n        every().second.do(mock_job, 1, 2, \"three\", foo=23, bar={})\n        schedule.run_all()\n        mock_job.assert_called_once_with(1, 2, \"three\", foo=23, bar={})\n\n    def test_to_string(self):\n        def job_fun():\n            pass\n\n        s = str(every().minute.do(job_fun, \"foo\", bar=23))\n        assert s == (\n            \"Job(interval=1, unit=minutes, do=job_fun, \"\n            \"args=('foo',), kwargs={'bar': 23})\"\n        )\n        assert \"job_fun\" in s\n        assert \"foo\" in s\n        assert \"{'bar': 23}\" in s\n\n    def test_to_repr(self):\n        def job_fun():\n            pass\n\n        s = repr(every().minute.do(job_fun, \"foo\", bar=23))\n        assert s.startswith(\n            \"Every 1 minute do job_fun('foo', bar=23) (last run: [never], next run: \"\n        )\n        assert \"job_fun\" in s\n        assert \"foo\" in s\n        assert \"bar=23\" in s\n\n        # test repr when at_time is not None\n        s2 = repr(every().day.at(\"00:00\").do(job_fun, \"foo\", bar=23))\n        assert s2.startswith(\n            (\n                \"Every 1 day at 00:00:00 do job_fun('foo', \"\n                \"bar=23) (last run: [never], next run: \"\n            )\n        )\n\n        # Ensure Job.__repr__ does not throw exception on a partially-composed Job\n        s3 = repr(schedule.every(10))\n        assert s3 == \"Every 10 None do [None] (last run: [never], next run: [never])\"\n\n    def test_to_string_lambda_job_func(self):\n        assert len(str(every().minute.do(lambda: 1))) > 1\n        assert len(str(every().day.at(\"10:30\").do(lambda: 1))) > 1\n\n    def test_repr_functools_partial_job_func(self):\n        def job_fun(arg):\n            pass\n\n        job_fun = functools.partial(job_fun, \"foo\")\n        job_repr = repr(every().minute.do(job_fun, bar=True, somekey=23))\n        assert \"functools.partial\" in job_repr\n        assert \"bar=True\" in job_repr\n        assert \"somekey=23\" in job_repr\n\n    def test_to_string_functools_partial_job_func(self):\n        def job_fun(arg):\n            pass\n\n        job_fun = functools.partial(job_fun, \"foo\")\n        job_str = str(every().minute.do(job_fun, bar=True, somekey=23))\n        assert \"functools.partial\" in job_str\n        assert \"bar=True\" in job_str\n        assert \"somekey=23\" in job_str\n\n    def test_run_pending(self):\n        \"\"\"Check that run_pending() runs pending jobs.\n        We do this by overriding datetime.datetime with mock objects\n        that represent increasing system times.\n\n        Please note that it is *intended behavior that run_pending() does not\n        run missed jobs*. For example, if you've registered a job that\n        should run every minute and you only call run_pending() in one hour\n        increments then your job won't be run 60 times in between but\n        only once.\n        \"\"\"\n        mock_job = make_mock_job()\n\n        with mock_datetime(2010, 1, 6, 12, 15):\n            every().minute.do(mock_job)\n            every().hour.do(mock_job)\n            every().day.do(mock_job)\n            every().sunday.do(mock_job)\n            schedule.run_pending()\n            assert mock_job.call_count == 0\n\n        with mock_datetime(2010, 1, 6, 12, 16):\n            schedule.run_pending()\n            assert mock_job.call_count == 1\n\n        with mock_datetime(2010, 1, 6, 13, 16):\n            mock_job.reset_mock()\n            schedule.run_pending()\n            assert mock_job.call_count == 2\n\n        with mock_datetime(2010, 1, 7, 13, 16):\n            mock_job.reset_mock()\n            schedule.run_pending()\n            assert mock_job.call_count == 3\n\n        with mock_datetime(2010, 1, 10, 13, 16):\n            mock_job.reset_mock()\n            schedule.run_pending()\n            assert mock_job.call_count == 4\n\n    def test_run_every_weekday_at_specific_time_today(self):\n        mock_job = make_mock_job()\n        with mock_datetime(2010, 1, 6, 13, 16):  # january 6 2010 == Wednesday\n            every().wednesday.at(\"14:12\").do(mock_job)\n            schedule.run_pending()\n            assert mock_job.call_count == 0\n\n        with mock_datetime(2010, 1, 6, 14, 16):\n            schedule.run_pending()\n            assert mock_job.call_count == 1\n\n    def test_run_every_weekday_at_specific_time_past_today(self):\n        mock_job = make_mock_job()\n        with mock_datetime(2010, 1, 6, 13, 16):\n            every().wednesday.at(\"13:15\").do(mock_job)\n            schedule.run_pending()\n            assert mock_job.call_count == 0\n\n        with mock_datetime(2010, 1, 13, 13, 14):\n            schedule.run_pending()\n            assert mock_job.call_count == 0\n\n        with mock_datetime(2010, 1, 13, 13, 16):\n            schedule.run_pending()\n            assert mock_job.call_count == 1\n\n    def test_run_every_n_days_at_specific_time(self):\n        mock_job = make_mock_job()\n        with mock_datetime(2010, 1, 6, 11, 29):\n            every(2).days.at(\"11:30\").do(mock_job)\n            schedule.run_pending()\n            assert mock_job.call_count == 0\n\n        with mock_datetime(2010, 1, 6, 11, 31):\n            schedule.run_pending()\n            assert mock_job.call_count == 0\n\n        with mock_datetime(2010, 1, 7, 11, 31):\n            schedule.run_pending()\n            assert mock_job.call_count == 0\n\n        with mock_datetime(2010, 1, 8, 11, 29):\n            schedule.run_pending()\n            assert mock_job.call_count == 0\n\n        with mock_datetime(2010, 1, 8, 11, 31):\n            schedule.run_pending()\n            assert mock_job.call_count == 1\n\n        with mock_datetime(2010, 1, 10, 11, 31):\n            schedule.run_pending()\n            assert mock_job.call_count == 2\n\n    def test_next_run_property(self):\n        original_datetime = datetime.datetime\n        with mock_datetime(2010, 1, 6, 13, 16):\n            hourly_job = make_mock_job(\"hourly\")\n            daily_job = make_mock_job(\"daily\")\n            every().day.do(daily_job)\n            every().hour.do(hourly_job)\n            assert len(schedule.jobs) == 2\n            # Make sure the hourly job is first\n            assert schedule.next_run() == original_datetime(2010, 1, 6, 14, 16)\n\n    def test_idle_seconds(self):\n        assert schedule.default_scheduler.next_run is None\n        assert schedule.idle_seconds() is None\n\n        mock_job = make_mock_job()\n        with mock_datetime(2020, 12, 9, 21, 46):\n            job = every().hour.do(mock_job)\n            assert schedule.idle_seconds() == 60 * 60\n            schedule.cancel_job(job)\n            assert schedule.next_run() is None\n            assert schedule.idle_seconds() is None\n\n    def test_cancel_job(self):\n        def stop_job():\n            return schedule.CancelJob\n\n        mock_job = make_mock_job()\n\n        every().second.do(stop_job)\n        mj = every().second.do(mock_job)\n        assert len(schedule.jobs) == 2\n\n        schedule.run_all()\n        assert len(schedule.jobs) == 1\n        assert schedule.jobs[0] == mj\n\n        schedule.cancel_job(\"Not a job\")\n        assert len(schedule.jobs) == 1\n        schedule.default_scheduler.cancel_job(\"Not a job\")\n        assert len(schedule.jobs) == 1\n\n        schedule.cancel_job(mj)\n        assert len(schedule.jobs) == 0\n\n    def test_cancel_jobs(self):\n        def stop_job():\n            return schedule.CancelJob\n\n        every().second.do(stop_job)\n        every().second.do(stop_job)\n        every().second.do(stop_job)\n        assert len(schedule.jobs) == 3\n\n        schedule.run_all()\n        assert len(schedule.jobs) == 0\n\n    def test_tag_type_enforcement(self):\n        job1 = every().second.do(make_mock_job(name=\"job1\"))\n        self.assertRaises(TypeError, job1.tag, {})\n        self.assertRaises(TypeError, job1.tag, 1, \"a\", [])\n        job1.tag(0, \"a\", True)\n        assert len(job1.tags) == 3\n\n    def test_get_by_tag(self):\n        every().second.do(make_mock_job()).tag(\"job1\", \"tag1\")\n        every().second.do(make_mock_job()).tag(\"job2\", \"tag2\", \"tag4\")\n        every().second.do(make_mock_job()).tag(\"job3\", \"tag3\", \"tag4\")\n\n        # Test None input yields all 3\n        jobs = schedule.get_jobs()\n        assert len(jobs) == 3\n        assert {\"job1\", \"job2\", \"job3\"}.issubset(\n            {*jobs[0].tags, *jobs[1].tags, *jobs[2].tags}\n        )\n\n        # Test each 1:1 tag:job\n        jobs = schedule.get_jobs(\"tag1\")\n        assert len(jobs) == 1\n        assert \"job1\" in jobs[0].tags\n\n        # Test multiple jobs found.\n        jobs = schedule.get_jobs(\"tag4\")\n        assert len(jobs) == 2\n        assert \"job1\" not in {*jobs[0].tags, *jobs[1].tags}\n\n        # Test no tag.\n        jobs = schedule.get_jobs(\"tag5\")\n        assert len(jobs) == 0\n        schedule.clear()\n        assert len(schedule.jobs) == 0\n\n    def test_clear_by_tag(self):\n        every().second.do(make_mock_job(name=\"job1\")).tag(\"tag1\")\n        every().second.do(make_mock_job(name=\"job2\")).tag(\"tag1\", \"tag2\")\n        every().second.do(make_mock_job(name=\"job3\")).tag(\n            \"tag3\", \"tag3\", \"tag3\", \"tag2\"\n        )\n        assert len(schedule.jobs) == 3\n        schedule.run_all()\n        assert len(schedule.jobs) == 3\n        schedule.clear(\"tag3\")\n        assert len(schedule.jobs) == 2\n        schedule.clear(\"tag1\")\n        assert len(schedule.jobs) == 0\n        every().second.do(make_mock_job(name=\"job1\"))\n        every().second.do(make_mock_job(name=\"job2\"))\n        every().second.do(make_mock_job(name=\"job3\"))\n        schedule.clear()\n        assert len(schedule.jobs) == 0\n\n    def test_misconfigured_job_wont_break_scheduler(self):\n        \"\"\"\n        Ensure an interrupted job definition chain won't break\n        the scheduler instance permanently.\n        \"\"\"\n        scheduler = schedule.Scheduler()\n        scheduler.every()\n        scheduler.every(10).seconds\n        scheduler.run_pending()\n"
  },
  {
    "path": "tox.ini",
    "content": "[tox]\nenvlist = py3{7,8,9,10,11,12}{,-pytz}\nskip_missing_interpreters = true\n\n\n[gh-actions]\npython =\n    3.7: py37, py37-pytz\n    3.8: py38, py38-pytz\n    3.9: py39, py39-pytz\n    3.10: py310, py310-pytz\n    3.11: py311, py311-pytz\n    3.12: py312, py312-pytz\n\n[testenv]\ndeps =\n    pytest\n    pytest-cov\n    mypy\n    types-pytz\n    pytz: pytz\ncommands =\n    py.test test_schedule.py schedule -v --cov schedule --cov-report term-missing\n    python -m mypy -p schedule --install-types --non-interactive\n\n[testenv:docs]\nchangedir = docs\ndeps = -rrequirements-dev.txt\ncommands = sphinx-build -W -b html -d {envtmpdir}/doctrees . {envtmpdir}/html\n\n[testenv:format]\ndeps = -rrequirements-dev.txt\ncommands = black --check .\n\n[testenv:setuppy]\ndeps = -rrequirements-dev.txt\ncommands =\n    python setup.py check --strict --metadata --restructuredtext\n"
  }
]