pax_global_header00006660000000000000000000000064143775344530014531gustar00rootroot0000000000000052 comment=fa040fb54adbbd7f606bc34545058363ebef324f pyluach-2.2.0/000077500000000000000000000000001437753445300131775ustar00rootroot00000000000000pyluach-2.2.0/.github/000077500000000000000000000000001437753445300145375ustar00rootroot00000000000000pyluach-2.2.0/.github/workflows/000077500000000000000000000000001437753445300165745ustar00rootroot00000000000000pyluach-2.2.0/.github/workflows/testing-and-coverage.yml000066400000000000000000000021231437753445300233230ustar00rootroot00000000000000name: pytest on: push: branches: - master - dev jobs: test: runs-on: ubuntu-latest strategy: matrix: python-version: ['3.7', '3.8', '3.9', '3.10', '3.11'] steps: - uses: actions/checkout@v2 - name: setup python uses: actions/setup-python@v2 with: python-version: ${{matrix.python-version}} - name: install run: | python -m pip install --upgrade pip pip install pytest-cov coveralls beautifulsoup4 pip install ./ - name: run-tests run: | pytest --cov-report= --cov=pyluach tests/ - name: Coveralls Report run: coveralls --service=github env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} COVERALLS_FLAG_NAME: ${{ matrix.python-version }} COVERALLS_PARALLEL: true finish: needs: test runs-on: ubuntu-latest container: python:3-slim steps: - name: Coveralls Finished run: | pip3 install --upgrade coveralls coveralls --service=github --finish env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} pyluach-2.2.0/.gitignore000066400000000000000000000001141437753445300151630ustar00rootroot00000000000000.* !.gitignore !.github/ *.pyc *~ ENV* *egg* *build* *.whl dist/* *htmlcov* pyluach-2.2.0/.readthedocs.yaml000066400000000000000000000012101437753445300164200ustar00rootroot00000000000000# .readthedocs.yaml # Read the Docs configuration file # See https://docs.readthedocs.io/en/stable/config-file/v2.html for details # Required version: 2 # Set the version of Python and other tools you might need build: os: ubuntu-22.04 tools: python: "3.11" # Build documentation in the docs/ directory with Sphinx sphinx: configuration: docs/conf.py # If using Sphinx, optionally build your docs in additional formats such as PDF formats: - htmlzip - pdf - epub # Optionally declare the Python requirements required to build your docs python: install: - method: pip path: . extra_requirements: - doc pyluach-2.2.0/CHANGELOG.rst000066400000000000000000000102721437753445300152220ustar00rootroot00000000000000========== Changelog ========== This document records all notable changes to `pyluach `_. This project adheres to `Semantic Versioning `_. `2.2.0`_ (2023-02-28) ===================== * Added `prefix_day` param to ``festival`` and ``holiday`` methods and functions. `2.1.0`_ (2023-02-12) ===================== * Added ``add`` and ``subtract`` methods to ``dates.HebrewDate``. * Added ``replace`` method to ``CalendarDateMixin``. * Added missing documentation for `%y` and `%Y` in formatting ``HebrewDate``. `2.0.2`_ (2022-10-24) ===================== * Fix subtracting one date from another returning ``float`` instead of ``int``. `2.0.1`_ (2022-08-24) ===================== * Fix issue (`#24`_) where Shavuos is returned in most months on day 7. `2.0.0`_ (2022-05-29) ===================== * Changed equality comparers to compare object identity on unmatched types. * Equal dates of different types will no longer be considered identical keys for dicts. * Added ``strftime`` and ``__format__`` methods to ``dates.GregorianDate``. * Added ``__format__`` method to ``dates.HebrewDate``. * Added `withgershayim` parameter to ``dates.HebrewDate.hebrew_day`` and ``dates.HebrewDate.hebrew_year`` methods * Added ``monthcount`` method to ``hebrewcal.Year``. * Removed deprecated ``hebrewcal.Month.name`` attribute. * Implemented HebrewCalendar classes for generating calendars similar to Calendar classes in the standard library calendar module. `1.4.2`_ (2022-05-20) ===================== * Fixed bug in ``hebrewcal.Month`` comparisons when one month is before Nissan and one is not. `1.4.1`_ (2022-03-25) ===================== * Fixed mistakes in docstring and error message. `1.4.0`_ (2022-02-21) ===================== * Added parameter `include_working_days` to ``festival`` method and function. * Removed support for python < 3.6 `1.3.0`_ (2021-06-09) ===================== * Added option to get parsha in Hebrew. * Added ``dates.HebrewDate`` methods to get hebrew day, month, year, and date string in Hebrew. * Added method to get ``hebrewcal.Month`` names in Hebrew. * Added methods to get year and month strings in Hebrew. * Added classmethods to ``hebrewcal.Year`` and ``hebrewcal.Month`` to get objects from dates and pydates. * Added methods to dates classes to get holidays, fast days and festivals. * Implemented more consistent Hebrew to English transliterations for parshios. `1.2.1`_ (2020-11-08) ===================== * Fixed molad having weekday of ``0`` when it should be ``7``. `1.2.0`_ (2020-10-28) ===================== * Created ``isoweekday`` method for all date types in the ``dates`` module. * Created fast_day and festival functions (`#11`_) * Added Pesach Sheni to festival. `1.1.1`_ (2020-08-14) ===================== * Fixed error getting parsha of Shabbos on Rosh Hashana. `1.1.0`_ (2020-06-03) ===================== * Redesigned documentation. * Added ``molad`` and ``molad_announcement`` methods to ``hebrewcal.Month``. * Stopped supporting python < 3.4 and modernized code. `1.0.1`_ (2019-03-02) ===================== * Initial public release .. _`2.2.0`: https://github.com/simlist/pyluach/compare/v2.1.0...v2.2.0 .. _`2.1.0`: https://github.com/simlist/pyluach/compare/v2.0.2...v2.1.0 .. _`2.0.2`: https://github.com/simlist/pyluach/compare/v2.0.1...v2.0.2 .. _`2.0.1`: https://github.com/simlist/pyluach/compare/v2.0.0...v2.0.1 .. _`2.0.0`: https://github.com/simlist/pyluach/compare/v1.4.2...v2.0.0 .. _`1.4.2`: https://github.com/simlist/pyluach/compare/v1.4.1...v1.4.2 .. _`1.4.1`: https://github.com/simlist/pyluach/compare/v1.4.0...v1.4.1 .. _`1.4.0`: https://github.com/simlist/pyluach/compare/v1.3.0...v1.4.0 .. _`1.3.0`: https://github.com/simlist/pyluach/compare/v1.2.1...v1.3.0 .. _`1.2.1`: https://github.com/simlist/pyluach/compare/v1.2.0...v1.2.1 .. _`1.2.0`: https://github.com/simlist/pyluach/compare/v1.1.1...v1.2.0 .. _`1.1.1`: https://github.com/simlist/pyluach/compare/v1.1.0...v1.1.1 .. _`1.1.0`: https://github.com/simlist/pyluach/compare/v1.0.1...v1.1.0 .. _`1.0.1`: https://github.com/simlist/pyluach/releases/tag/v1.0.1 .. _`#11`: https://github.com/simlist/pyluach/issues/11 .. _`#24`: https://github.com/simlist/pyluach/issues/24 pyluach-2.2.0/README.rst000066400000000000000000000055661437753445300147020ustar00rootroot00000000000000pyluach ======= .. image:: https://readthedocs.org/projects/pyluach/badge/?version=stable :target: http://pyluach.readthedocs.io/en/latest/?badge=stable :alt: Documentation Status .. image:: https://github.com/simlist/pyluach/actions/workflows/testing-and-coverage.yml/badge.svg?branch=master :target: https://github.com/simlist/pyluach/actions/workflows/testing-and-coverage.yml .. image:: https://coveralls.io/repos/github/simlist/pyluach/badge.svg?branch=master :target: https://coveralls.io/github/simlist/pyluach?branch=master Pyluach is a Python package for dealing with Hebrew (Jewish) calendar dates. Features --------- * Conversion between Hebrew and Gregorian dates * Finding the difference between two dates * Finding a date at a given duration from the given date * Rich comparisons between dates * Finding the weekday of a given date * Finding the weekly Parsha reading of a given date * Getting the holiday occuring on a given date * Generating html and text Hebrew calendars Installation ------------- Use ``pip install pyluach``. Documentation ------------- Documentation for pyluach can be found at https://readthedocs.org/projects/pyluach/. Examples ------------ :: >>> from pyluach import dates, hebrewcal, parshios >>> today = dates.HebrewDate.today() >>> lastweek_gregorian = (today - 7).to_greg() >>> lastweek_gregorian < today True >>> today - lastweek_gregorian 7 >>> greg = dates.GregorianDate(1986, 3, 21) >>> heb = dates.HebrewDate(5746, 13, 10) >>> greg == heb True >>> purim = dates.HebrewDate(5781, 12, 14) >>> purim.hebrew_day() 'י״ד' >>> purim.hebrew_date_string() 'י״ד אדר תשפ״א' >>> purim.hebrew_date_string(True) 'י״ד אדר ה׳תשפ״א' >>> rosh_hashana = dates.HebrewDate(5782, 7, 1) >>> rosh_hashana.holiday() 'Rosh Hashana' >>> rosh_hashana.holiday(hebrew=True) 'ראש השנה' >>> (rosh_hashana + 3).holiday() None >>> month = hebrewcal.Month(5781, 10) >>> month.month_name() 'Teves' >>> month.month_name(True) 'טבת' >>> month + 3 Month(5781, 1) >>> for month in hebrewcal.Year(5774).itermonths(): ... print(month.month_name()) Tishrei Cheshvan ... >>> date = dates.GregorianDate(2010, 10, 6) >>> parshios.getparsha(date) [0] >>> parshios.getparsha_string(date, israel=True) 'Beraishis' >>> parshios.getparsha_string(date, hebrew=True) 'בראשית' >>> new_date = dates.GregorianDate(2021, 3, 10) >>> parshios.getparsha_string(new_date) 'Vayakhel, Pekudei' >>> parshios.getparsha_string(new_date, hebrew=True) 'ויקהל, פקודי' Contact -------- For questions and comments please `raise an issue in github `_ or contact me at simlist@gmail.com. License -------- Pyluach is licensed under the MIT license.pyluach-2.2.0/docs/000077500000000000000000000000001437753445300141275ustar00rootroot00000000000000pyluach-2.2.0/docs/Makefile000066400000000000000000000011721437753445300155700ustar00rootroot00000000000000# Minimal makefile for Sphinx documentation # # You can set these variables from the command line, and also # from the environment for the first two. SPHINXOPTS ?= SPHINXBUILD ?= sphinx-build SOURCEDIR = . BUILDDIR = _build # Put it first so that "make" without argument is like "make help". help: @$(SPHINXBUILD) -M help "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) .PHONY: help Makefile # Catch-all target: route all unknown targets to Sphinx using the new # "make mode" option. $(O) is meant as a shortcut for $(SPHINXOPTS). %: Makefile @$(SPHINXBUILD) -M $@ "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) pyluach-2.2.0/docs/changelog.rst000066400000000000000000000000351437753445300166060ustar00rootroot00000000000000.. include:: ../CHANGELOG.rstpyluach-2.2.0/docs/conf.py000066400000000000000000000046171437753445300154360ustar00rootroot00000000000000# Configuration file for the Sphinx documentation builder. # # This file only contains a selection of the most common options. For a full # list see the documentation: # https://www.sphinx-doc.org/en/master/usage/configuration.html # -- Path setup -------------------------------------------------------------- # If extensions (or modules to document with autodoc) are in another directory, # add these directories to sys.path here. If the directory is relative to the # documentation root, use os.path.abspath to make it absolute, like shown here. import sphinx_rtd_theme # -- Project information ----------------------------------------------------- project = 'pyluach' copyright = '2016, MS List' author = 'MS List' # The full version, including alpha/beta/rc tags release = '2.2.0' # -- General configuration --------------------------------------------------- # Add any Sphinx extension module names here, as strings. They can be # extensions coming with Sphinx (named 'sphinx.ext.*') or your custom # ones. extensions = [ 'sphinx.ext.autodoc', 'sphinx.ext.viewcode', 'sphinx.ext.autosummary', 'sphinx.ext.intersphinx', 'sphinx.ext.napoleon', 'sphinx_rtd_theme', ] # Napolean settings napoleon_include_special_with_doc = False # Autodoc settings autodoc_default_options = { 'members': True, 'inherited-members': True, 'show-inheritance': True, 'member-order': 'bysource' } autodoc_member_order = 'bysource' # Autosummary settings autosummary_generate = True # Intersphinx settings intersphinx_mapping = {'python': ('https://docs.python.org/3', None)} # Add any paths that contain templates here, relative to this directory. templates_path = ['_templates'] # List of patterns, relative to source directory, that match files and # directories to ignore when looking for source files. # This pattern also affects html_static_path and html_extra_path. exclude_patterns = ['_build', 'Thumbs.db', '.DS_Store'] # -- Options for HTML output ------------------------------------------------- # The theme to use for HTML and HTML Help pages. See the documentation for # a list of builtin themes. # html_theme = 'sphinx_rtd_theme' # Add any paths that contain custom static files (such as style sheets) here, # relative to this directory. They are copied after the builtin static files, # so a file named "default.css" will overwrite the builtin "default.css". html_static_path = ['_static'] pyluach-2.2.0/docs/dates.rst000066400000000000000000000000711437753445300157570ustar00rootroot00000000000000dates module ============ .. automodule:: pyluach.dates pyluach-2.2.0/docs/hebrewcal.rst000066400000000000000000000001051437753445300166110ustar00rootroot00000000000000hebrewcal module ================ .. automodule:: pyluach.hebrewcal pyluach-2.2.0/docs/index.rst000066400000000000000000000005101437753445300157640ustar00rootroot00000000000000Welcome to pyluach's documentation! =================================== .. include:: ../README.rst .. toctree:: :hidden: self .. toctree:: :maxdepth: 1 :caption: Contents: dates hebrewcal parshios changelog Indices and tables ================== * :ref:`genindex` * :ref:`modindex` * :ref:`search` pyluach-2.2.0/docs/make.bat000066400000000000000000000013701437753445300155350ustar00rootroot00000000000000@ECHO OFF pushd %~dp0 REM Command file for Sphinx documentation if "%SPHINXBUILD%" == "" ( set SPHINXBUILD=sphinx-build ) set SOURCEDIR=. set BUILDDIR=_build if "%1" == "" goto help %SPHINXBUILD% >NUL 2>NUL if errorlevel 9009 ( echo. echo.The 'sphinx-build' command was not found. Make sure you have Sphinx echo.installed, then set the SPHINXBUILD environment variable to point echo.to the full path of the 'sphinx-build' executable. Alternatively you echo.may add the Sphinx directory to PATH. echo. echo.If you don't have Sphinx installed, grab it from echo.http://sphinx-doc.org/ exit /b 1 ) %SPHINXBUILD% -M %1 %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% %O% goto end :help %SPHINXBUILD% -M help %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% %O% :end popd pyluach-2.2.0/docs/parshios.rst000066400000000000000000000001021437753445300165020ustar00rootroot00000000000000parshios module =============== .. automodule:: pyluach.parshios pyluach-2.2.0/docs/requirements.txt000066400000000000000000000000361437753445300174120ustar00rootroot00000000000000Sphinx~=6.1.3 sphinx_rtd_themepyluach-2.2.0/license.txt000066400000000000000000000021431437753445300153620ustar00rootroot00000000000000The MIT License (MIT) [OSI Approved License] The MIT License (MIT) Copyright (c) 2014 Meir S. List Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.pyluach-2.2.0/pyproject.toml000066400000000000000000000025131437753445300161140ustar00rootroot00000000000000[build-system] requires = ["flit_core >=3.2,<4"] build-backend = "flit_core.buildapi" [project] name = "pyluach" authors = [ {name = "MS List", email = "simlist@gmail.com"} ] license = {file = "license.txt"} dynamic = ["description", "version"] readme = "README.rst" requires-python = ">=3.7" classifiers=[ "Development Status :: 5 - Production/Stable", "Intended Audience :: Developers", "Topic :: Software Development :: Libraries :: Python Modules", "License :: OSI Approved :: MIT License", "Programming Language :: Python :: 3", "Programming Language :: Python :: 3 :: Only", "Programming Language :: Python :: 3.7", "Programming Language :: Python :: 3.8", "Programming Language :: Python :: 3.9", "Programming Language :: Python :: 3.10", "Programming Language :: Python :: 3.11", ] keywords = [ 'hebrew', 'calendar', 'jewish', 'luach', 'gregorian', 'julian', 'days', 'dates', 'date', 'conversion', 'parsha', 'holiday' ] [project.optional-dependencies] test = [ "pytest", "pytest-cov", "flake8", "beautifulsoup4" ] doc = ["sphinx ~= 6.1.3", "sphinx_rtd_theme ~= 1.2.0"] [project.urls] Documentation = "https://readthedocs.org/projects/pyluach/" Source = "https://github.com/simlist/pyluach" [tool.flit.sdist] exclude = ["tests", "docs", ".github", "requirements.txt", ".gitignore"] pyluach-2.2.0/requirements.txt000066400000000000000000000015111437753445300164610ustar00rootroot00000000000000alabaster==0.7.13 attrs==22.2.0 Babel==2.11.0 beautifulsoup4==4.11.2 certifi==2022.12.7 charset-normalizer==3.0.1 coverage==7.1.0 docutils==0.19 exceptiongroup==1.1.0 flake8==6.0.0 flit==3.8.0 flit-core==3.8.0 idna==3.4 imagesize==1.4.1 importlib-metadata==6.0.0 iniconfig==2.0.0 Jinja2==3.1.2 MarkupSafe==2.1.2 mccabe==0.7.0 packaging==23.0 pluggy==1.0.0 pycodestyle==2.10.0 pyflakes==3.0.1 Pygments==2.14.0 pyluach==2.1.0 pytest==7.2.1 pytest-cov==4.0.0 pytz==2022.7.1 requests==2.28.2 snowballstemmer==2.2.0 soupsieve==2.3.2.post1 sphinx==6.1.3 sphinx-rtd-theme==1.2.0 sphinxcontrib-applehelp==1.0.4 sphinxcontrib-devhelp==1.0.2 sphinxcontrib-htmlhelp==2.0.1 sphinxcontrib-jquery==2.0.0 sphinxcontrib-jsmath==1.0.1 sphinxcontrib-qthelp==1.0.3 sphinxcontrib-serializinghtml==1.1.5 tomli==2.0.1 tomli-w==1.0.0 urllib3==1.26.14 zipp==3.13.0 pyluach-2.2.0/setup.cfg000066400000000000000000000001021437753445300150110ustar00rootroot00000000000000[flake8] exclude = ENV,dist,docs,tests,build extend-ignore = E228 pyluach-2.2.0/src/000077500000000000000000000000001437753445300137665ustar00rootroot00000000000000pyluach-2.2.0/src/pyluach/000077500000000000000000000000001437753445300154335ustar00rootroot00000000000000pyluach-2.2.0/src/pyluach/__init__.py000066400000000000000000000001411437753445300175400ustar00rootroot00000000000000"""A Python package for dealing with Hebrew (Jewish) calendar dates. """ __version__ = '2.2.0' pyluach-2.2.0/src/pyluach/dates.py000066400000000000000000001070671437753445300171200ustar00rootroot00000000000000"""The dates module implements classes for representing and manipulating several date types. Contents -------- * :class:`Rounding` * :class:`BaseDate` * :class:`CalendarDateMixin` * :class:`JulianDay` * :class:`GregorianDate` * :class:`HebrewDate` Note ---- All instances of the classes in this module should be treated as read only. No attributes should be changed once they're created. """ import abc from datetime import date from numbers import Number from enum import Enum, auto from pyluach import utils from pyluach import gematria class Rounding(Enum): """Enumerator to provide options for rounding Hebrew dates. This provides constants to use as arguments for functions. It should not be instantiated. Attributes ---------- PREVIOUS_DAY If the day is the 30th and the month only has 29 days, round to the 29th of the month. NEXT_DAY If the day is the 30th and the month only has 29 days, round to the 1st of the next month. EXCEPTION If the day is the 30th and the month only has 29 days, raise a ValueError. """ PREVIOUS_DAY = auto() NEXT_DAY = auto() EXCEPTION = auto() class BaseDate(abc.ABC): """BaseDate is a base class for all date types. It provides the following arithmetic and comparison operators common to all child date types. =================== ================================================= Operation Result =================== ================================================= d2 = date1 + int New date ``int`` days after date1 d2 = date1 - int New date ``int`` days before date1 int = date1 - date2 Positive integer equal to the duration from date1 to date2 date1 > date2 True if date1 occurs later than date2 date1 < date2 True if date1 occurs earlier than date2 date1 == date2 True if date1 occurs on the same day as date2 date1 != date2 True if ``date1 == date2`` is False =================== ================================================= Any subclass of ``BaseDate`` can be compared to and diffed with any other subclass date. """ @property @abc.abstractmethod def jd(self): """Return julian day number. Returns ------- float The Julian day number at midnight (as ``n.5``). """ @abc.abstractmethod def to_heb(self): """Return Hebrew Date. Returns ------- HebrewDate """ def __hash__(self): return hash(repr(self)) def __add__(self, other): try: return JulianDay(self.jd + other)._to_x(self) except TypeError: return NotImplemented def __sub__(self, other): try: if isinstance(other, Number): return JulianDay(self.jd - other)._to_x(self) return int(abs(self.jd - other.jd)) except (AttributeError, TypeError): return NotImplemented def __eq__(self, other): try: return self.jd == other.jd except AttributeError: return NotImplemented def __ne__(self, other): try: return self.jd != other.jd except AttributeError: return NotImplemented def __lt__(self, other): try: return self.jd < other.jd except AttributeError: return NotImplemented def __gt__(self, other): try: return self.jd > other.jd except AttributeError: return NotImplemented def __le__(self, other): try: return self.jd <= other.jd except AttributeError: return NotImplemented def __ge__(self, other): try: return self.jd >= other.jd except AttributeError: return NotImplemented def weekday(self): """Return day of week as an integer. Returns ------- int An integer representing the day of the week with Sunday as 1 through Saturday as 7. """ return int(self.jd+.5+1) % 7 + 1 def isoweekday(self): """Return the day of the week corresponding to the iso standard. Returns ------- int An integer representing the day of the week where Monday is 1 and and Sunday is 7. """ weekday = self.weekday() if weekday == 1: return 7 return weekday - 1 def shabbos(self): """Return the Shabbos on or following the date. Returns ------- JulianDay, GregorianDate, or HebrewDate `self` if the date is Shabbos or else the following Shabbos as the same date type as called from. Examples -------- >>> heb_date = HebrewDate(5781, 3, 29) >>> greg_date = heb_date.to_greg() >>> heb_date.shabbos() HebrewDate(5781, 4, 2) >>> greg_date.shabbos() GregorianDate(2021, 6, 12) """ return self + (7 - self.weekday()) def _day_of_holiday(self, israel, hebrew=False): """Return the day of the holiday. Parameters ---------- israel : bool, optional hebrew : bool, optional Returns ------- str """ name = utils._festival_string(self, israel) if name is not None: holiday = utils._Days(name) if holiday is utils._Days.SHAVUOS and israel: return '' first_day = utils._first_day_of_holiday(holiday) if first_day: year = self.to_heb().year day = HebrewDate(year, *first_day) - self + 1 if hebrew: day = gematria._num_to_str(day) return str(day) return '' def fast_day(self, hebrew=False): """Return name of fast day of date. Parameters ---------- hebrew : bool, optional ``True`` if you want the fast day name in Hebrew letters. Default is ``False``, which returns the name transliterated into English. Returns ------- str or None The name of the fast day or ``None`` if the date is not a fast day. """ return utils._fast_day_string(self, hebrew) def festival( self, israel=False, hebrew=False, include_working_days=True, prefix_day=False ): """Return name of Jewish festival of date. This method will return all major and minor religous Jewish holidays not including fast days. Parameters ---------- israel : bool, optional ``True`` if you want the holidays according to the Israel schedule. Defaults to ``False``. hebrew : bool, optional ``True`` if you want the festival name in Hebrew letters. Default is ``False``, which returns the name transliterated into English. include_working_days : bool, optional ``True`` to include festival days on which melacha (work) is allowed; ie. Pesach Sheni, Chol Hamoed, etc. Default is ``True``. prefix_day : bool, optional ``True`` to prefix multi day festivals with the day of the festival. Default is ``False``. Examples -------- >>> pesach = HebrewDate(2023, 1, 15) >>> pesach.festival(prefix_day=True) '1 Pesach' >>> pesach.festival(hebrew=True, prefix_day=True) 'א׳ פסח' >>> shavuos = HebrewDate(5783, 3, 6) >>> shavuos.festival(israel=True, prefix_day=True) 'Shavuos' Returns ------- str or None The name of the festival or ``None`` if the given date is not a Jewish festival. """ name = utils._festival_string( self, israel, hebrew, include_working_days ) if prefix_day and name is not None: day = self._day_of_holiday(israel=israel, hebrew=hebrew) if day: return f'{day} {name}' return name def holiday(self, israel=False, hebrew=False, prefix_day=False): """Return name of Jewish holiday of the date. The holidays include the major and minor religious Jewish holidays including fast days. Parameters ---------- israel : bool, optional ``True`` if you want the holidays according to the Israel schedule. Defaults to ``False``. hebrew : bool, optional ``True`` if you want the holiday name in Hebrew letters. Default is ``False``, which returns the name transliterated into English. prefix_day : bool, optional ``True`` to prefix multi day holidays with the day of the holiday. Default is ``False``. Examples -------- >>> pesach = HebrewDate(2023, 1, 15) >>> pesach.holiday(prefix_day=True) '1 Pesach' >>> pesach.holiday(hebrew=True, prefix_day=True) 'א׳ פסח' >>> taanis_esther = HebrewDate(5783, 12, 13) >>> taanis_esther.holiday(prefix_day=True) 'Taanis Esther' Returns ------- str or None The name of the holiday or ``None`` if the given date is not a Jewish holiday. """ return ( self.fast_day(hebrew=hebrew) or self.festival(israel, hebrew, prefix_day=prefix_day) ) class CalendarDateMixin: """CalendarDateMixin is a mixin for Hebrew and Gregorian dates. Parameters ---------- year : int month : int day : int Attributes ---------- year : int month : int day : int jd : float The equivalent Julian day at midnight. """ def __init__(self, year, month, day, jd=None): self.year = year self.month = month self.day = day self._jd = jd def __repr__(self): class_name = self.__class__.__name__ return f'{class_name}({self.year}, {self.month}, {self.day})' def __str__(self): return f'{self.year:04d}-{self.month:02d}-{self.day:02d}' def __iter__(self): yield self.year yield self.month yield self.day def tuple(self): """Return date as tuple. Returns ------- tuple of ints A tuple of ints in the form ``(year, month, day)``. """ return (self.year, self.month, self.day) def dict(self): """Return the date as a dictionary. Returns ------- dict A dictionary in the form ``{'year': int, 'month': int, 'day': int}``. """ return {'year': self.year, 'month': self.month, 'day': self.day} def replace(self, year=None, month=None, day=None): """Return new date with new values for the specified field. Parameters ---------- year : int, optional month: int, optional day : int, optional Returns ------- CalendarDateMixin Any date that inherits from CalendarDateMixin (``GregorianDate``, ````HebrewDate``). Raises ValueError Raises a ``ValueError`` if the new date does not exist. """ if year is None: year = self.year if month is None: month = self.month if day is None: day = self.day return type(self)(year, month, day) class JulianDay(BaseDate): """A JulianDay object represents a Julian Day at midnight. Parameters ---------- day : float or int The julian day. Note that Julian days start at noon so day number 10 is represented as 9.5 which is day 10 at midnight. Attributes ---------- day : float The Julian Day Number at midnight (as *n*.5) """ def __init__(self, day): if day-int(day) < .5: self.day = int(day) - .5 else: self.day = int(day) + .5 def __repr__(self): return f'JulianDay({self.day})' def __str__(self): return str(self.day) @property def jd(self): """Return julian day. Returns ------- float """ return self.day @staticmethod def from_pydate(pydate): """Return a `JulianDay` from a python date object. Parameters ---------- pydate : datetime.date A python standard library ``datetime.date`` instance Returns ------- JulianDay """ return GregorianDate.from_pydate(pydate).to_jd() @staticmethod def today(): """Return instance of current Julian day from timestamp. Extends the built-in ``datetime.date.today()``. Returns ------- JulianDay A JulianDay instance representing the current Julian day from the timestamp. Warning ------- Julian Days change at noon, but pyluach treats them as if they change at midnight, so at midnight this method will return ``JulianDay(n.5)`` until the following midnight when it will return ``JulianDay(n.5 + 1)``. """ return GregorianDate.today().to_jd() def to_greg(self): """Convert JulianDay to a Gregorian Date. Returns ------- GregorianDate The equivalent Gregorian date instance. Notes ----- This method uses the Fliegel-Van Flandern algorithm. """ jd = int(self.day + .5) L = jd + 68569 n = 4*L // 146097 L = L - (146097*n + 3) // 4 i = (4000 * (L+1)) // 1461001 L = L - ((1461*i) // 4) + 31 j = (80*L) // 2447 day = L - 2447*j // 80 L = j // 11 month = j + 2 - 12*L year = 100 * (n-49) + i + L if year < 1: year -= 1 return GregorianDate(year, month, day, self.day) def to_heb(self): """ Convert to a Hebrew date. Returns ------- HebrewDate The equivalent Hebrew date instance. """ if self.day <= 347997: raise ValueError('Date is before creation') jd = int(self.day + .5) # Try to account for half day jd -= 347997 year = int(jd//365) + 2 # try that to debug early years first_day = utils._elapsed_days(year) while first_day > jd: year -= 1 first_day = utils._elapsed_days(year) months = utils._monthslist(year) days_remaining = jd - first_day for month in months: if days_remaining >= utils._month_length(year, month): days_remaining -= utils._month_length(year, month) else: return HebrewDate(year, month, days_remaining + 1, self.day) def _to_x(self, type_): """Return a date object of the given type.""" if isinstance(type_, GregorianDate): return self.to_greg() if isinstance(type_, HebrewDate): return self.to_heb() if isinstance(type_, JulianDay): return self raise TypeError( 'This method has not been implemented with that type.' ) def to_pydate(self): """Convert to a datetime.date object. Returns ------- datetime.date A standard library ``datetime.date`` instance. """ return self.to_greg().to_pydate() class GregorianDate(BaseDate, CalendarDateMixin): """A GregorianDate object represents a Gregorian date (year, month, day). This is an idealized date with the current Gregorian calendar infinitely extended in both directions. Parameters ---------- year : int month : int day : int jd : float, optional This parameter should not be assigned manually. Attributes ---------- year : int month : int day : int Warnings -------- Although B.C.E. dates are allowed, they should be treated as approximations as they may return inconsistent results when converting between date types and using arithmetic and comparison operators! """ def __init__(self, year, month, day, jd=None): """Initialize a GregorianDate. This initializer extends the CalendarDateMixin initializer adding in date validation specific to Gregorian dates. """ if month < 1 or month > 12: raise ValueError(f'{str(month)} is an invalid month.') monthlength = self._monthlength(year, month) if day < 1 or day > monthlength: raise ValueError(f'Given month has {monthlength} days.') super().__init__(year, month, day, jd) def __format__(self, fmt): return self.strftime(fmt) def strftime(self, fmt): """Return formatted date. Wraps :py:meth:`datetime.date.strftime` method and uses the same format options. Parameters ---------- fmt : str The format string. Returns ------- str """ return self.to_pydate().strftime(fmt) @property def jd(self): """Return the corresponding Julian day number. Returns ------- float The Julian day number at midnight. """ if self._jd is None: year = self.year month = self.month day = self.day if year < 0: year += 1 if month < 3: year -= 1 month += 12 month += 1 a = year // 100 b = 2 - a + a//4 self._jd = ( int(365.25*year) + int(30.6001*month) + b + day + 1720994.5 ) return self._jd @classmethod def from_pydate(cls, pydate): """Return a `GregorianDate` instance from a python date object. Parameters ---------- pydate : datetime.date A python standard library ``datetime.date`` instance. Returns ------- GregorianDate """ return cls(*pydate.timetuple()[:3]) @staticmethod def today(): """Return a GregorianDate instance for the current day. This static method wraps the Python standard library's date.today() method to get the date from the timestamp. Returns ------- GregorianDate The current Gregorian date from the computer's timestamp. """ return GregorianDate.from_pydate(date.today()) @staticmethod def _is_leap(year): """Return True if year of date is a leap year, otherwise False.""" if year < 0: year += 1 if (year % 4 == 0) and not (year % 100 == 0 and year % 400 != 0): return True return False def is_leap(self): """Return if the date is in a leap year Returns ------- bool True if the date is in a leap year, False otherwise. """ return self._is_leap(self.year) @classmethod def _monthlength(cls, year, month): if month in [1, 3, 5, 7, 8, 10, 12]: return 31 if month == 2: if cls._is_leap(year): return 29 return 28 return 30 def to_jd(self): """Convert to a Julian day. Returns ------- JulianDay The equivalent JulianDay instance. """ return JulianDay(self.jd) def to_heb(self): """Convert to Hebrew date. Returns ------- HebrewDate The equivalent HebrewDate instance. """ return self.to_jd().to_heb() def to_pydate(self): """Convert to a standard library date. Returns ------- datetime.date The equivalent datetime.date instance. """ return date(*self.tuple()) class HebrewDate(BaseDate, CalendarDateMixin): """A class for manipulating Hebrew dates. The following format options are available similar to strftime: ====== ======= =========================================================== Format Example Meaning ====== ======= =========================================================== %a Sun Weekday as locale's abbreviated name %A Sunday Weekday as locale's full name %w 1 Weekday as decimal number 1-7 Sunday-Shabbos %d 07 Day of the month as a 0-padded 2 digit decimal number %-d 7 Day of the month as a decimal number %B Iyar Month name transliterated into English %m 02 Month as a 0-padded 2 digit decimal number %-m 2 Month as a decimal number %y 82, 01 Year without century as a zero-padded decimal number %Y 5782 Year as a decimal number %*a א׳ Weekday as a Hebrew numeral %*A ראשון Weekday name in Hebrew %*d ז׳, ט״ז Day of month as Hebrew numeral %*-d א, טו Day of month without gershayim %*B אייר Name of month in Hebrew %*y תשפ״ב Year in Hebrew numerals without the thousands place %*Y ה'תשפ״ב Year in Hebrew numerals with the thousands place %% % A literal '%' character ====== ======= =========================================================== Example ------- >>> date = HebrewDate(5783, 1, 15) >>> f'Today is {date:%a - %*-d %*B, %*y}' 'Today is Thu - טו אייר, תשפ"ג' Parameters ---------- year : int The Hebrew year. month : int The Hebrew month starting with Nissan as 1 (and Tishrei as 7). If there is a second Adar in the year it is has a value of 13. day : int The Hebrew day of the month. jd : float, optional This parameter should not be assigned manually. Attributes ---------- year : int month : int The Hebrew month starting with Nissan as 1 (and Tishrei as 7). If there is a second Adar it has a value of 13. day : int The day of the month. Raises ------ ValueError If the year is less than 1, if the month is less than 1 or greater than the last month, or if the day does not exist in the month a ``ValueError`` will be raised. """ def __init__(self, year, month, day, jd=None): """Initialize a HebrewDate instance. This initializer extends the CalendarDateMixin adding validation specific to hebrew dates. """ if year < 1: raise ValueError('Year must be >= 1.') if month < 1 or month > 13: raise ValueError(f'{month} is an invalid month.') if (not utils._is_leap(year)) and month == 13: raise ValueError(f'{year} is not a leap year') monthlength = utils._month_length(year, month) if day < 1 or day > monthlength: raise ValueError(f'Given month has {monthlength} days.') super().__init__(year, month, day, jd) def __format__(self, fmt): new = [] i = 0 while i < len(fmt): if fmt[i] != '%': new.append(fmt[i]) else: i += 1 try: curr = fmt[i] except IndexError as e: raise ValueError( 'Format string cannot end with single "%".' ) from e if curr == '%': new.append('%') elif curr == '*': i += 1 try: curr = fmt[i] except IndexError as e: raise ValueError( 'Format string cannot end with "%*".' ) from e if curr == '-': i += 1 try: curr = fmt[i] except IndexError as e: raise ValueError( 'Format string cannot end with "%*-"' ) from e if curr == 'd': new.append(self.hebrew_day(False)) else: raise ValueError('Invalid format string.') elif curr == 'a': new.append(gematria._num_to_str(self.weekday())) elif curr == 'A': new.append(utils.WEEKDAYS[self.weekday()]) elif curr == 'd': new.append(self.hebrew_day()) elif curr == 'B': new.append(self.month_name(True)) elif curr.casefold() == 'y': new.append(self.hebrew_year(curr == 'Y')) else: raise ValueError('Invalid format string.') elif curr == '-': i += 1 try: curr = fmt[i] except IndexError as e: raise ValueError( 'Format string cannot end with "%-"' ) from e if curr == 'd': new.append(str(self.day)) elif curr == 'm': new.append(str(self.month)) else: raise ValueError('Invalid format string.') else: if curr.casefold() == 'a': new.append(self.to_pydate().strftime(f'%{curr}')) elif curr == 'w': new.append(str(self.weekday())) elif curr == 'd': new.append(format(self.day, '02d')) elif curr == 'B': new.append(self.month_name(False)) elif curr == 'm': new.append(format(self.month, '02d')) elif curr.casefold() == 'y': new.append(date(self.year, 1, 1).strftime(f'%{curr}')) else: raise ValueError('Invalid format string.') i += 1 return ''.join(new) @property def jd(self): """Return the corresponding Julian day number. Returns ------- float The Julian day number at midnight. """ if self._jd is None: months = utils._monthslist(self.year) jd = utils._elapsed_days(self.year) for m in months: if m != self.month: jd += utils._month_length(self.year, m) else: self._jd = jd + (self.day-1) + 347996.5 return self._jd @staticmethod def from_pydate(pydate): """Return a `HebrewDate` from a python date object. Parameters ---------- pydate : datetime.date A python standard library ``datetime.date`` instance Returns ------- HebrewDate """ return GregorianDate.from_pydate(pydate).to_heb() @staticmethod def today(): """Return HebrewDate instance for the current day. This static method wraps the Python standard library's ``date.today()`` method to get the date from the timestamp. Returns ------- HebrewDate The current Hebrew date from the computer's timestamp. Warning ------- Pyluach treats Hebrew dates as if they change at midnight. If it's after nightfall but before midnight, to get the true Hebrew date do ``HebrewDate.today() + 1``. """ return GregorianDate.today().to_heb() def to_jd(self): """Convert to a Julian day. Returns ------- JulianDay The equivalent JulianDay instance. """ return JulianDay(self.jd) def to_greg(self): """Convert to a Gregorian date. Returns ------- GregorianDate The equivalent GregorianDate instance. """ return self.to_jd().to_greg() def to_pydate(self): """Convert to a standard library date. Returns ------- datetime.date The equivalent datetime.date instance. """ return self.to_greg().to_pydate() def to_heb(self): return self def month_name(self, hebrew=False): """Return the name of the month. Parameters ---------- hebrew : bool, optional ``True`` if the month name should be in Hebrew characters. Default is ``False`` which returns the month name transliterated into English. Returns ------- str """ return utils._month_name(self.year, self.month, hebrew) def hebrew_day(self, withgershayim=True): """Return the day of the month in Hebrew letters. Parameters ---------- withgershayim : bool, optional Default is ``True`` which includes a geresh with a single character and gershayim between two characters. Returns ------- str The day of the month in Hebrew letters. Examples -------- >>> date = HebrewDate(5782, 3, 6) >>> date.hebrew_day() 'ו׳' >>> date.hebrew_day(False) 'ו' >>> HebrewDate(5783, 12, 14).hebrew_day() 'י״ד' """ return gematria._num_to_str(self.day, withgershayim=withgershayim) def hebrew_year(self, thousands=False, withgershayim=True): """Return the year in Hebrew letters. Parameters ---------- thousands : bool ``True`` to prefix the year with a letter for the thousands place, ie. 'ה׳תשפ״א'. Default is ``False``. withgershayim : bool, optional Default is ``True`` which includes a geresh after the thousands place if applicable and a gershayim before the last character of the year. Returns ------- str """ return gematria._num_to_str(self.year, thousands, withgershayim) def hebrew_date_string(self, thousands=False): """Return a Hebrew string representation of the date. The date is in the form ``f'{day} {month} {year}'``. Parameters ---------- thousands : bool ``True`` to have the thousands include in the year. Default is ``False``. Returns ------- str Examples -------- >>> date = HebrewDate(5781, 9, 25) >>> date.hebrew_date_string() 'כ״ה כסלו תשפ״א' >>> date.hebrew_date_string(True) 'כ״ה כסלו ה׳תשפ״א' """ day = self.hebrew_day() month = self.month_name(True) year = self.hebrew_year(thousands) return f'{day} {month} {year}' def add( self, years=0, months=0, days=0, adar1=False, rounding=Rounding.NEXT_DAY ): """Add years, months, and days to date. Parameters ---------- years : int, optional The number of years to add. Default is 0. months : int, optional The number of months to add. Default is 0. days : int, optional The number of days to add. Default is 0. adar1 : bool, optional True to return a date in Adar Aleph if `self` is in a regular Adar and after adding the years it's leap year. Default is ``False`` which will return the date in Adar Beis. rounding : Rounding, optional Choose what to do if self is the 30th day of the month, and there are only 29 days in the destination month. :obj:`Rounding.NEXT_DAY` to return the first day of the next month. :obj:`Rounding.PREVIOUS_DAY` to return the last day of the month. :obj:`Rounding.EXCEPTION` to raise a ValueError. Default is :obj:`Rounding.NEXT_DAY`. Returns ------- HebrewDate Note ---- This method first adds the `years`. If the starting month is Adar and the destination year has two Adars, it chooses which one based on the `adar1` argument, then it adds the `months`. If the starting day doesn't exist in that month it adjusts it based on the `rounding` argument, then it adds the `days`. Examples -------- >>> date = HebrewDate(5783, 11, 30) >>> date.add(months=1) HebrewDate(5783, 1, 1) >>> date.add(months=1, rounding=Rounding.PREVIOUS_DAY) HebrewDate(5783, 12, 29) """ year = self.year + years month = self.month if self.month == 13 and not utils._is_leap(year): month = 12 elif ( self.month == 12 and not utils._is_leap(self.year) and utils._is_leap(year) and not adar1 ): month = 13 if months > 0: year, month = utils._add_months(year, month, months) elif months < 0: year, month = utils._subtract_months(year, month, -months) if utils._month_length(year, month) < self.day: date = HebrewDate(year, month, 29) if rounding is Rounding.EXCEPTION: raise ValueError(f'{date:%B %Y} has only 29 days.') if rounding is Rounding.NEXT_DAY: date += 1 elif not isinstance(rounding, Rounding): raise TypeError( 'The rounding argument can only be a member of the' ' dates.Rounding enum.' ) else: date = HebrewDate(year, month, self.day) return date + days def subtract( self, years=0, months=0, days=0, adar1=False, rounding=Rounding.NEXT_DAY ): """Subtract years, months, and days from date. Parameters ---------- years : int, optional The number of years to subtract. Default is 0. months : int, optional The number of months to subtract. Default is 0. days : int, optional The number of days to subtract. Default is 0. adar1 : bool, optional True to return a date in Adar Aleph if `self` is in a regular Adar and the destination year is leap year. Default is ``False`` which will return the date in Adar Beis. rounding : Rounding, optional Choose what to do if self is the 30th day of the month, and there are only 29 days in the destination month. :obj:`Rounding.NEXT_DAY` to return the first day of the next month. :obj:`Rounding.PREVIOUS_DAY` to return the last day of the month. :obj:`Rounding.EXCEPTION` to raise a ValueError. Default is :obj:`Rounding.NEXT_DAY`. Returns ------- HebrewDate Note ---- This method first subtracts the `years`. If the starting month is Adar and the destination year has two Adars, it chooses which one based on the `adar1` argument, then it subtracts the `months`. If the starting day doesn't exist in that month it adjusts it based on the `rounding` argument, then it subtracts the `days`. """ return self.add(-years, -months, -days, adar1, rounding) pyluach-2.2.0/src/pyluach/gematria.py000066400000000000000000000034201437753445300175750ustar00rootroot00000000000000_GEMATRIOS = { 1: 'א', 2: 'ב', 3: 'ג', 4: 'ד', 5: 'ה', 6: 'ו', 7: 'ז', 8: 'ח', 9: 'ט', 10: 'י', 20: 'כ', 30: 'ל', 40: 'מ', 50: 'נ', 60: 'ס', 70: 'ע', 80: 'פ', 90: 'צ', 100: 'ק', 200: 'ר', 300: 'ש', 400: 'ת' } def _stringify_gematria(letters): """Insert geresh or gershayim symbols into gematria.""" length = len(letters) if length > 1: return f'{letters[:-1]}״{letters[-1]}' if length == 1: return f'{letters}׳' return '' def _get_letters(num): """Convert numbers under 1,000 into raw letters.""" ones = num % 10 tens = num % 100 - ones hundreds = num % 1000 - tens - ones four_hundreds = ''.join(['ת' for i in range(hundreds // 400)]) ones = _GEMATRIOS.get(ones, '') tens = _GEMATRIOS.get(tens, '') hundreds = _GEMATRIOS.get(hundreds % 400, '') letters = f'{four_hundreds}{hundreds}{tens}{ones}' return letters.replace('יה', 'טו').replace('יו', 'טז') def _num_to_str(num, thousands=False, withgershayim=True): """Return gematria string for number. Parameters ---------- num : int The number to get the Hebrew letter representation thousands : bool, optional True if the hebrew returned should include a letter for the thousands place ie. 'ה׳' for five thousand. Returns ------- str The Hebrew representation of the number. """ letters = _get_letters(num) if withgershayim: letters = _stringify_gematria(letters) if thousands: thousand = _get_letters(num // 1000) if withgershayim: thousand = ''.join([thousand, '׳']) letters = ''.join([thousand, letters]) return letters pyluach-2.2.0/src/pyluach/hebrewcal.py000066400000000000000000001165751437753445300177600ustar00rootroot00000000000000"""The hebrewcal module contains Hebrew calendar related classes and functions. It contains classes for representing a Hebrew year and month, functions for getting the holiday or fast day for a given date, and classes adapting :py:mod:`calendar` classes to render Hebrew calendars. Contents -------- * :class:`Year` * :class:`Month` * :func:`to_hebrew_numeral` * :class:`HebrewCalendar` * :class:`HebrewHTMLCalendar` * :class:`HebrewTextCalendar` * :func:`fast_day` * :func:`festival` * :func:`holiday` """ from numbers import Number from itertools import repeat import calendar from pyluach.dates import HebrewDate from pyluach import utils from pyluach.gematria import _num_to_str class IllegalMonthError(ValueError): """An exception for an illegal month. Subclasses ``ValueError`` to show a message for an invalid month number for the Hebrew calendar. Mimics :py:class:`calendar.IllegalMonthError`. Parameters ---------- month : int The invalid month number """ def __init__(self, month): self.month = month def __str__(self): return ( f'bad month number {self.month}; must be 1-12 or 13 in a leap year' ) class IllegalWeekdayError(ValueError): """An exception for an illegal weekday. Subclasses ``ValueError`` to show a message for an invalid weekday number. Mimics :py:class:`calendar.IllegalWeekdayError`. Parameters ---------- month : int The invalid month number """ def __init__(self, weekday): self.weekday = weekday def __str__(self): return ( f'bad weekday number {self.weekday}; ' f'must be 1 (Sunday) to 7 (Saturday)' ) class Year: """A Year object represents a Hebrew calendar year. It provided the following operators: ===================== ================================================ Operation Result ===================== ================================================ year2 = year1 + int New ``Year`` ``int`` days after year1. year2 = year1 - int New ``Year`` ``int`` days before year1. int = year1 - year2 ``int`` equal to the absolute value of the difference between year2 and year1. bool = year1 == year2 True if year1 represents the same year as year2. bool = year1 > year2 True if year1 is later than year2. bool = year1 >= year2 True if year1 is later or equal to year2. bool = year1 < year2 True if year 1 earlier than year2. bool = year1 <= year2 True if year 1 earlier or equal to year 2. ===================== ================================================ Parameters ---------- year : int A Hebrew year. Attributes ---------- year : int The hebrew year. leap : bool True if the year is a leap year else false. """ def __init__(self, year): if year < 1: raise ValueError(f'Year {year} is before creation.') self.year = year self.leap = utils._is_leap(year) def __repr__(self): return f'Year({self.year})' def __len__(self): return utils._days_in_year(self.year) def __eq__(self, other): if isinstance(other, Year): return self.year == other.year return NotImplemented def __add__(self, other): """Add int to year.""" try: return Year(self.year + other) except TypeError: return NotImplemented def __sub__(self, other): """Subtract int or Year from Year. If other is an int return a new Year other before original year. If other is a Year object, return delta of the two years as an int. """ if isinstance(other, Year): return abs(self.year - other.year) try: return Year(self.year - other) except TypeError: return NotImplemented def __gt__(self, other): if isinstance(other, Year): return self.year > other.year return NotImplemented def __ge__(self, other): if isinstance(other, Year): return self > other or self == other return NotImplemented def __lt__(self, other): if isinstance(other, Year): return self.year < other.year return NotImplemented def __le__(self, other): if isinstance(other, Year): return self < other or self == other return NotImplemented def __iter__(self): """Yield integer for each month in year.""" yield from utils._monthslist(self.year) def monthscount(self): """Return number of months in the year. Returns ------- int """ if self.leap: return 13 return 12 def itermonths(self): """Yield Month instance for each month of the year. Yields ------ :obj:`Month` The next month in the Hebrew calendar year as a ``Month`` instance beginning with Tishrei through Elul. """ for month in self: yield Month(self.year, month) def iterdays(self): """Yield integer for each day of the year. Yields ------ :obj:`int` An integer beginning with 1 for the the next day of the year. """ for day in range(1, len(self) + 1): yield day def iterdates(self): """Iterate through each Hebrew date of the year. Yields ------ :obj:`pyluach.dates.HebrewDate` The next date of the Hebrew calendar year starting with the first of Tishrei. """ for month in self.itermonths(): for day in month: yield HebrewDate(self.year, month.month, day) @classmethod def from_date(cls, date): """Return Year object that given date occurs in. Parameters ---------- date : ~pyluach.dates.BaseDate Any subclass of ``BaseDate``. Returns ------- Year """ return cls(date.to_heb().year) @classmethod def from_pydate(cls, pydate): """Return Year object from python date object. Parameters ---------- pydate : datetime.date A python standard library date object Returns ------- Year The Hebrew year the given date occurs in """ return cls.from_date(HebrewDate.from_pydate(pydate)) def year_string(self, thousands=False): """Return year as a Hebrew string. Parameters ---------- thousands: bool, optional ``True`` to prefix the year with the thousands place. Default is ``False``. Examples -------- >>> year = Year(5781) >>> year.year_string() תשפ״א >>> year.year_string(True) ה׳תשפ״א """ return _num_to_str(self.year, thousands) class Month: """A Month object represents a month of the Hebrew calendar. It provides the same operators as a `Year` object. Parameters ---------- year : int month : int The month as an integer starting with 7 for Tishrei through 13 if necessary for Adar Sheni and then 1-6 for Nissan - Elul. Attributes ---------- year : int The Hebrew year. month : int The month as an integer starting with 7 for Tishrei through 13 if necessary for Adar Sheni and then 1-6 for Nissan - Elul. """ def __init__(self, year, month): if year < 1: raise ValueError('Year must be >= 1.') self.year = year if month < 1 or month > 12 + utils._is_leap(self.year): raise IllegalMonthError(month) self.month = month def __repr__(self): return f'Month({self.year}, {self.month})' def __len__(self): return utils._month_length(self.year, self.month) def __iter__(self): for day in range(1, len(self) + 1): yield day def __eq__(self, other): if isinstance(other, Month): return (self.year == other.year and self.month == other.month) return NotImplemented def __add__(self, other): try: year, month = utils._add_months(self.year, self.month, other) return Month(year, month) except (AttributeError, TypeError): return NotImplemented def __sub__(self, other): if isinstance(other, Number): year, month = utils._subtract_months(self.year, self.month, other) return Month(year, month) try: return abs(self._elapsed_months() - other._elapsed_months()) except AttributeError: return NotImplemented def __gt__(self, other): if isinstance(other, Month): return ( self.year > other.year or ( self.year == other.year and self._month_number() > other._month_number() ) ) return NotImplemented def __ge__(self, other): if isinstance(other, Month): return self > other or self == other return NotImplemented def __lt__(self, other): if isinstance(other, Month): return ( self.year < other.year or ( self.year == other.year and self._month_number() < other._month_number() ) ) return NotImplemented def __le__(self, other): if isinstance(other, Month): return self < other or self == other return NotImplemented @classmethod def from_date(cls, date): """Return Month object that given date occurs in. Parameters ---------- date : ~pyluach.dates.BaseDate Any subclass of ``BaseDate``. Returns ------- Month The Hebrew month the given date occurs in """ heb = date.to_heb() return Month(heb.year, heb.month) @classmethod def from_pydate(cls, pydate): """Return Month object from python date object. Parameters ---------- pydate : datetime.date A python standard library date object Returns ------- Month The Hebrew month the given date occurs in """ return cls.from_date(HebrewDate.from_pydate(pydate)) def _month_number(self): """Return month number 1-12 or 13, Tishrei - Elul.""" return list(Year(self.year)).index(self.month) + 1 def month_name(self, hebrew=False): """Return the name of the month. Replaces `name` attribute. Parameters ---------- hebrew : bool, optional `True` if the month name should be written with Hebrew letters and False to be transliterated into English using the Ashkenazic pronunciation. Default is `False`. Returns ------- str """ return utils._month_name(self.year, self.month, hebrew) def month_string(self, thousands=False): """Return month and year in Hebrew. Parameters ---------- thousands : bool, optional ``True`` to prefix year with thousands place. Default is ``False``. Returns ------- str The month and year in Hebrew in the form ``f'{month} {year}'``. """ return f'{self.month_name(True)} {_num_to_str(self.year, thousands)}' def starting_weekday(self): """Return first weekday of the month. Returns ------- int The weekday of the first day of the month starting with Sunday as 1 through Saturday as 7. """ return HebrewDate(self.year, self.month, 1).weekday() def _elapsed_months(self): """Return number of months elapsed from beginning of calendar""" yearmonths = tuple(Year(self.year)) months_elapsed = ( utils._elapsed_months(self.year) + yearmonths.index(self.month) ) return months_elapsed def iterdates(self): """Iterate through the Hebrew dates of the month. Yields ------ :obj:`pyluach.dates.HebrewDate` The next Hebrew date of the month. """ for day in self: yield HebrewDate(self.year, self.month, day) def molad(self): """Return the month's molad. Returns ------- dict A dictionary in the form ``{weekday: int, hours: int, parts: int}`` Note ----- This method does not return the molad in the form that is traditionally announced in the shul. This is the molad in the form used to calculate the length of the year. See Also -------- molad_announcement: The molad as it is traditionally announced. """ months = self._elapsed_months() parts = 204 + months*793 hours = 5 + months*12 + parts//1080 days = 2 + months*29 + hours//24 weekday = days % 7 or 7 return {'weekday': weekday, 'hours': hours % 24, 'parts': parts % 1080} def molad_announcement(self): """Return the month's molad in the announcement form. Returns a dictionary in the form that the molad is traditionally announced. The weekday is adjusted to change at midnight and the hour of the day and minutes are given as traditionally announced. Note that the hour is given as in a twenty four hour clock ie. 0 for 12:00 AM through 23 for 11:00 PM. Returns ------- dict A dictionary in the form:: { weekday: int, hour: int, minutes: int, parts: int } """ molad = self.molad() weekday = molad['weekday'] hour = 18 + molad['hours'] if hour < 24: if weekday != 1: weekday -= 1 else: weekday = 7 else: hour -= 24 minutes = molad['parts'] // 18 parts = molad['parts'] % 18 return { 'weekday': weekday, 'hour': hour, 'minutes': minutes, 'parts': parts } def _to_pyweekday(weekday): if weekday == 1: return 6 return weekday - 2 def _year_and_month(month): return month.year, month.month def to_hebrew_numeral(num, thousands=False, withgershayim=True): """Convert int to Hebrew numeral. Function useful in formatting Hebrew calendars. Parameters ---------- num : int The number to convert thousands : bool, optional True if the hebrew returned should include a letter for the thousands place ie. 'ה׳' for five thousand. Default is ``False``. withgershayim : bool, optional ``True`` to include a geresh after a single letter and double geresh before the last letter if there is more than one letter. Default is ``True``. Returns ------- str The Hebrew numeral representation of the number. """ return _num_to_str(num, thousands, withgershayim) class HebrewCalendar(calendar.Calendar): """Calendar base class. This class extends the python library :py:class:`Calendar ` class for the Hebrew calendar. The weekdays are 1 for Sunday through 7 for Shabbos. Parameters ---------- firstweekday : int, optional The weekday to start each week with. Default is ``1`` for Sunday. hebrewnumerals : bool, optional Default is ``True``, which shows the days of the month with Hebrew numerals. ``False`` shows the days of the month as a decimal number. hebrewweekdays : bool, optional ``True`` to show the weekday in Hebrew. Default is ``False``, which shows the weekday in English. hebrewmonths : bool, optional ``True`` to show the month name in Hebrew. Default is ``False``, which shows the month name transliterated into English. hebrewyear : bool, optional ``True`` to show the year in Hebrew numerals. Default is ``False``, which shows the year as a decimal number. Attributes ---------- hebrewnumerals : bool hebrewweekdays : bool hebrewmonths : bool hebrewyear : bool Note ---- All of the parameters other than `firstweekday` are not used in the ``HebrewCalendar`` base class. They're there for use in child classes. """ def __init__( self, firstweekday=1, hebrewnumerals=True, hebrewweekdays=False, hebrewmonths=False, hebrewyear=False ): if not 1 <= firstweekday <= 7: raise IllegalWeekdayError(firstweekday) self._firstweekday = firstweekday self._firstpyweekday = _to_pyweekday(firstweekday) self.hebrewnumerals = hebrewnumerals self.hebrewweekdays = hebrewweekdays self.hebrewmonths = hebrewmonths self.hebrewyear = hebrewyear @property def firstweekday(self): """Get and set the weekday the weeks should start with. Returns ------- int """ return self._firstweekday @firstweekday.setter def firstweekday(self, thefirstweekday): self._firstweekday = thefirstweekday self._firstpyweekday = _to_pyweekday(thefirstweekday) def iterweekdays(self): """Return one week of weekday numbers. The numbers start with the configured first one. Yields ------ :obj:`int` The next weekday with 1-7 for Sunday - Shabbos. The iterator starts with the ``HebrewCalendar`` object's configured first weekday ie. if configured to start with Monday it will first yield `2` and end with `1`. """ for i in range(self.firstweekday, self.firstweekday + 7): yield i % 7 or 7 def itermonthdates(self, year, month): """Yield dates for one month. The iterator will always iterate through complete weeks, so it will yield dates outside the specified month. Parameters ---------- year : int month : int The Hebrew month starting with 1 for Nissan through 13 for Adar Sheni if necessary. Yields ------ :obj:`pyluach.dates.HebrewDate` The next Hebrew Date of the month starting with the first date of the week the first of the month falls in, and ending with the last date of the week that the last day of the month falls in. """ for y, m, d in self.itermonthdays3(year, month): yield HebrewDate(y, m, d) def itermonthdays(self, year, month): """Like ``itermonthdates()`` but will yield day numbers. For days outside the specified month the day number is 0. Parameters ---------- year : int month : int Yields ------ :obj:`int` The day of the month or 0 if the date is before or after the month. """ currmonth = Month(year, month) day1 = _to_pyweekday(currmonth.starting_weekday()) ndays = len(currmonth) days_before = (day1 - self._firstpyweekday) % 7 yield from repeat(0, days_before) yield from range(1, ndays + 1) days_after = (self._firstpyweekday - day1 - ndays) % 7 yield from repeat(0, days_after) def itermonthdays2(self, year, month): """Return iterator for the days and weekdays of the month. Parameters ---------- year : int month : int Yields ------ :obj:`tuple` of :obj:`int` A tuple of ints in the form ``(day of month, weekday)``. """ for i, d in enumerate( self.itermonthdays(year, month), self.firstweekday ): yield d, i % 7 or 7 def itermonthdays3(self, year, month): """Return iterator for the year, month, and day of the month. Parameters ---------- year : int month : int Yields ------ :obj:`tuple` of :obj:`int` A tuple of ints in the form ``(year, month, day)``. """ currmonth = Month(year, month) day1 = _to_pyweekday(currmonth.starting_weekday()) ndays = len(currmonth) days_before = (day1 - self._firstpyweekday) % 7 days_after = (self._firstpyweekday - day1 - ndays) % 7 try: prevmonth = currmonth - 1 except ValueError: prevmonth = currmonth y, m = _year_and_month(prevmonth) end = len(prevmonth) + 1 for d in range(end - days_before, end): yield y, m, d for d in range(1, ndays + 1): yield year, month, d y, m = _year_and_month(currmonth + 1) for d in range(1, days_after + 1): yield y, m, d def itermonthdays4(self, year, month): """Return iterator for the year, month, day, and weekday Parameters ---------- year : int month : int Yields ------ :obj:`tuple` of :obj:`int` A tuple of ints in the form ``(year, month, day, weekday)``. """ for i, (y, m, d) in enumerate(self.itermonthdays3(year, month)): yield y, m, d, (self.firstweekday + i) % 7 or 7 def yeardatescalendar(self, year, width=3): """Return data of specified year ready for formatting. Parameters ---------- year : int width : int, optional The number of months per row. Default is 3. Returns ------ :obj:`list` of list of list of list of :obj:`pyluach.dates.HebrewDate` Returns a list of month rows. Each month row contains a list of up to `width` months. Each month contains either 5 or 6 weeks, and each week contains 7 days. Days are ``HebrewDate`` objects. """ months = [ self.monthdatescalendar(year, m) for m in Year(year) ] return [months[i:i+width] for i in range(0, len(months), width)] def yeardays2calendar(self, year, width=3): """Return the data of the specified year ready for formatting. This method is similar to the ``yeardatescalendar`` except the entries in the week lists are ``(day number, weekday number)`` tuples. Parameters ---------- year : int width : int, optional The number of months per row. Default is 3. Returns ------- :obj:`list` of list of list of list of :obj:`tuple` Returns a list of month rows. Each month row contains a list of up to `width` months. Each month contains between 4 and 6 weeks, and each week contains 1-7 days. Days are tuples with the form ``(day number, weekday number)``. """ months = [ self.monthdays2calendar(year, m) for m in Year(year) ] return [months[i:i+width] for i in range(0, len(months), width)] def yeardayscalendar(self, year, width=3): """Return the data of the specified year ready for formatting. This method is similar to the ``yeardatescalendar`` except the entries in the week lists are day numbers. Parameters ---------- year : int width : int, optional The number of months per row. Default is 3. Returns ------- :obj:`list` of list of list of list of :obj:`int` Returns a list of month rows. Each month row contains a list of up to `width` months. Each month contains either 5 or 6 weeks, and each week contains 1-7 days. Each day is the day of the month as an int. """ months = [ self.monthdayscalendar(year, m) for m in Year(year) ] return [months[i:i+width] for i in range(0, len(months), width)] def monthdatescalendar(self, year, month): """Return matrix (list of lists) of dates for month's calendar. Each row represents a week; week entries are HebrewDate instances. Parameters ---------- year : int month : int Returns ------- :obj:`list` of list of :obj:`pyluach.dates.HebrewDate` List of weeks in the month containing 7 ``HebrewDate`` instances each. """ return super().monthdatescalendar(year, month) class HebrewHTMLCalendar(HebrewCalendar, calendar.HTMLCalendar): """Class to generate html calendars . Adapts :py:class:`calendar.HTMLCalendar` for the Hebrew calendar. Parameters ---------- firstweekday : int, optional The weekday to start each week with. Default is ``1`` for Sunday. hebrewnumerals : bool, optional Default is ``True``, which shows the days of the month with Hebrew numerals. ``False`` shows the days of the month as a decimal number. hebrewweekdays : bool, optional ``True`` to show the weekday in Hebrew. Default is ``False``, which shows the weekday in English. hebrewmonths : bool, optional ``True`` to show the month name in Hebrew. Default is ``False``, which shows the month name transliterated into English. hebrewyear : bool, optional ``True`` to show the year in Hebrew numerals. Default is ``False``, which shows the year as a decimal number. rtl : bool, optional ``True`` to arrange the months and the days of the month from right to left. Default is ``False``. Attributes ---------- hebrewnumerals : bool hebrewweekdays : bool hebrewmonths : bool hebrewyear : bool rtl : bool """ def __init__( self, firstweekday=1, hebrewnumerals=True, hebrewweekdays=False, hebrewmonths=False, hebrewyear=False, rtl=False ): self.rtl = rtl super().__init__( firstweekday, hebrewnumerals, hebrewweekdays, hebrewmonths, hebrewyear ) def _rtl_str(self): if self.rtl: return ' dir="rtl"' return '' def formatday(self, day, weekday): """Return a day as an html table cell. Parameters ---------- day : int The day of the month or zero for a day outside the month. weekday : int The weekday with 1 as Sunday through 7 as Shabbos. Returns ------- str """ pyweekday = _to_pyweekday(weekday) if day == 0: return f' ' if self.hebrewnumerals: day = to_hebrew_numeral(day, withgershayim=False) return f'{day}' def formatweekday(self, day): """Return a weekday name as an html table header. Parameters ---------- day : int The day of the week 1-7 with Sunday as 1 and Shabbos as 7. Returns ------- str """ pyday = _to_pyweekday(day) if self.hebrewweekdays: dayname = utils.WEEKDAYS[day][:3] else: dayname = calendar.day_abbr[pyday] return ( f'{dayname}' ) def formatyearnumber(self, theyear): """Return a formatted year. Parameters ---------- theyear : int Returns ------- int or str If ``self.hebrewyear`` is ``True`` return the year as a Hebrew numeral, else return `theyear` as is. """ if self.hebrewyear: return to_hebrew_numeral(theyear) return theyear def formatmonthname(self, theyear, themonth, withyear=True): """Return month name as an html table row. Parameters ---------- theyear : int themonth : int The month as an int 1-12 Nissan - Adar and 13 if leap year. withyear : bool, optional ``True`` to append the year to the month name. Default is ``True``. Return ------ str """ s = Month(theyear, themonth).month_name(self.hebrewmonths) if withyear: s = f'{s} {self.formatyearnumber(theyear)}' return ( f'' f'{s}' ) def formatmonth(self, theyear, themonth, withyear=True): """Return a formatted month as an html table. Parameters ---------- theyear : int themonth : int withyear : bool, optional ``True`` to have the year appended to the month name. Default is ``True``. Returns ------- str """ v = [] a = v.append a( '' ) a('\n') a(self.formatmonthname(theyear, themonth, withyear=withyear)) a('\n') a(self.formatweekheader()) a('\n') for week in self.monthdays2calendar(theyear, themonth): a(self.formatweek(week)) a('\n') a('
') a('\n') return ''.join(v) def formatyear(self, theyear, width=3): """Return a formatted year as an html table. Parameters ---------- theyear : int width : int, optional The number of months to display per row. Default is 3. Returns ------- str """ year = Year(theyear) monthscount = year.monthscount() yearmonths = list(year) v = [] a = v.append width = max(width, 1) a( '' ) a('\n') a( f'' ) for i in range(1, monthscount + 1, width): # months in this row months = range(i, min(i+width, monthscount + 1)) a('') for m in months: a('') a('') a('
' f'{self.formatyearnumber(theyear)}
') a(self.formatmonth( theyear, yearmonths[m-1], withyear=False )) a('
') return ''.join(v) class HebrewTextCalendar(HebrewCalendar, calendar.TextCalendar): """Subclass of HebrewCalendar that outputs a plaintext calendar. ``HebrewTextCalendar`` adapts :py:class:`calendar.TextCalendar` for the Hebrew calendar. Parameters ---------- firstweekday : int, optional The weekday to start each week with. Default is ``1`` for Sunday. hebrewnumerals : bool, optional Default is ``True``, which shows the days of the month with Hebrew numerals. ``False`` shows the days of the month as a decimal number. hebrewweekdays : bool, optional ``True`` to show the weekday in Hebrew. Default is ``False``, which shows the weekday in English. hebrewmonths : bool, optional ``True`` to show the month name in Hebrew. Default is ``False``, which shows the month name transliterated into English. hebrewyear : bool, optional ``True`` to show the year in Hebrew numerals. Default is ``False``, which shows the year as a decimal number. Attributes ---------- hebrewnumerals : bool hebrewweekdays : bool hebrewmonths : bool hebrewyear : bool Note ---- This class generates plain text calendars. Any program that adds any formatting may misrender the calendars especially when using any Hebrew characters. """ def formatday(self, day, weekday, width): """Return a formatted day. Extends calendar.TextCalendar formatday method. Parameters ---------- day : int The day of the month. weekday : int The weekday 1-7 Sunday-Shabbos. width : int The width of the day column. Returns ------- str """ if self.hebrewnumerals: if day == 0: s = '' else: s = f'{to_hebrew_numeral(day, withgershayim=False):>2}' return s.center(width) return super().formatday(day, weekday, width) def formatweekday(self, day, width): """Return formatted weekday. Extends calendar.TextCalendar formatweekday method. Parameters ---------- day : int The weekday 1-7 Sunday-Shabbos. width : int The width of the day column. Returns ------- str """ if self.hebrewweekdays: if width < 5: name = to_hebrew_numeral(day) else: name = utils.WEEKDAYS[day] return name[:width].center(width) return super().formatweekday(_to_pyweekday(day), width) def formatmonthname( self, theyear, themonth, width=0, withyear=True ): """Return formatted month name. Parameters ---------- theyear : int themonth : int 1-12 or 13 for Nissan-Adar Sheni width : int, optional The number of columns per day. Default is 0 withyear : bool, optional Default is ``True`` to include the year with the month name. Returns ------- str """ s = Month(theyear, themonth).month_name(self.hebrewmonths) if withyear: if self.hebrewyear: year = to_hebrew_numeral(theyear) else: year = theyear s = f'{s} {year}' return s.center(width) def formatyear(self, theyear, w=2, l=1, c=6, m=3): # noqa: E741 """Return a year's calendar as a multi-line string. Parameters ---------- theyear : int w : int, optional The date column width. Default is 2 l : int, optional The number of lines per week. Default is 1. c : int, optional The number of columns in between each month. Default is 6 m : int, optional The number of months per row. Default is 3. Returns ------- str """ w = max(2, w) l = max(1, l) # noqa: E741 c = max(2, c) colwidth = (w + 1) * 7 - 1 v = [] a = v.append a(repr(theyear).center(colwidth*m+c*(m-1)).rstrip()) a('\n'*l) header = self.formatweekheader(w) yearmonths = list(Year(theyear)) for (i, row) in enumerate(self.yeardays2calendar(theyear, m)): # months in this row months = range(m*i+1, min(m*(i+1)+1, len(yearmonths)+1)) a('\n'*l) names = ( self.formatmonthname(theyear, yearmonths[k-1], colwidth, False) for k in months ) a(calendar.formatstring(names, colwidth, c).rstrip()) a('\n'*l) headers = (header for k in months) a(calendar.formatstring(headers, colwidth, c).rstrip()) a('\n'*l) # max number of weeks for this row height = max(len(cal) for cal in row) for j in range(height): weeks = [] for cal in row: if j >= len(cal): weeks.append('') else: weeks.append(self.formatweek(cal[j], w)) a(calendar.formatstring(weeks, colwidth, c).rstrip()) a('\n' * l) return ''.join(v) def fast_day(date, hebrew=False): """Return name of fast day or None. Parameters ---------- date : ~pyluach.dates.BaseDate Any date instance from a subclass of ``BaseDate`` can be used. hebrew : bool, optional ``True`` if you want the fast_day name in Hebrew letters. Default is ``False``, which returns the name transliterated into English. Returns ------- str or None The name of the fast day or ``None`` if the given date is not a fast day. """ return date.fast_day(hebrew) def festival( date, israel=False, hebrew=False, include_working_days=True, prefix_day=False ): """Return Jewish festival of given day. This method will return all major and minor religous Jewish holidays not including fast days. Parameters ---------- date : ~pyluach.dates.BaseDate Any subclass of ``BaseDate`` can be used. israel : bool, optional ``True`` if you want the festivals according to the Israel schedule. Defaults to ``False``. hebrew : bool, optional ``True`` if you want the festival name in Hebrew letters. Default is ``False``, which returns the name transliterated into English. include_working_days : bool, optional ``True`` to include festival days on which melacha (work) is allowed; ie. Pesach Sheni, Chol Hamoed, etc. Default is ``True``. prefix_day : bool, optional ``True`` to prefix multi day festivals with the day of the festival. Default is ``False``. Examples -------- >>> from pyluach.dates import HebrewDate pesach = HebrewDate(2023, 1, 15) >>> festival(pesach, prefix_day=True) '1 Pesach' >>> festival(pesach, hebrew=True, prefix_day=True) 'א׳ פסח' >>> shavuos = HebrewDate(5783, 3, 6) >>> festival(shavuos, israel=True, prefix_day=True) 'Shavuos' Returns ------- str or None The name of the festival or ``None`` if the given date is not a Jewish festival. """ return date.festival(israel, hebrew, include_working_days, prefix_day) def holiday(date, israel=False, hebrew=False, prefix_day=False): """Return Jewish holiday of given date. The holidays include the major and minor religious Jewish holidays including fast days. Parameters ---------- date : pyluach.dates.BaseDate Any subclass of ``BaseDate`` can be used. israel : bool, optional ``True`` if you want the holidays according to the israel schedule. Default is ``False``. hebrew : bool, optional ``True`` if you want the holiday name in Hebrew letters. Default is ``False``, which returns the name transliterated into English. prefix_day : bool, optional ``True`` to prefix multi day holidays with the day of the holiday. Default is ``False``. Examples -------- >>> from pyluach.dates import HebrewDate >>> pesach = HebrewDate(2023, 1, 15) >>> holiday(pesach, prefix_day=True) '1 Pesach' >>> holiday(pesach, hebrew=True, prefix_day=True) 'א׳ פסח' >>> taanis_esther = HebrewDate(5783, 12, 13) >>> holiday(taanis_esther, prefix_day=True) 'Taanis Esther' Returns ------- str or None The name of the holiday or ``None`` if the given date is not a Jewish holiday. """ return date.holiday(israel, hebrew, prefix_day) pyluach-2.2.0/src/pyluach/parshios.py000066400000000000000000000163531437753445300176450ustar00rootroot00000000000000"""The parshios module has functions to find the weekly parasha. Examples -------- >>> from pyluach import dates, parshios >>> date = dates.HebrewDate(5781, 10, 5) >>> parshios.getparsha(date) 'Vayigash' >>> parshios.getparsha_string(date, hebrew=True) 'ויגש' >>> parshios.getparsha_string(dates.GregorianDate(2021, 3, 7), hebrew=True) 'ויקהל, פקודי' Note ---- The algorithm is based on Dr. Irv Bromberg's, University of Toronto at http://individual.utoronto.ca/kalendis/hebrew/parshah.htm All English parsha names are transliterated into the American Ashkenazik pronunciation. Attributes ---------- PARSHIOS : list of str A list of the parshios transliterated into English. PARSHIOS_HEBREW : list of str A list of the parshios in Hebrew. """ from collections import deque, OrderedDict from functools import lru_cache from pyluach.dates import HebrewDate from pyluach.utils import _is_leap PARSHIOS = [ 'Bereishis', 'Noach', 'Lech Lecha', 'Vayeira', 'Chayei Sarah', 'Toldos', 'Vayeitzei', 'Vayishlach', 'Vayeishev', 'Mikeitz', 'Vayigash', 'Vayechi', 'Shemos', "Va'eira", 'Bo', 'Beshalach', 'Yisro', 'Mishpatim', 'Terumah', 'Tetzaveh', 'Ki Sisa', 'Vayakhel', 'Pekudei', 'Vayikra', 'Tzav', 'Shemini', 'Tazria', 'Metzora', 'Acharei Mos', 'Kedoshim', 'Emor', 'Behar', 'Bechukosai', 'Bamidbar', 'Nasso', "Beha'aloscha", 'Shelach', 'Korach', 'Chukas', 'Balak', 'Pinchas', 'Mattos', 'Masei', 'Devarim', "Va'eschanan", 'Eikev', "Re'eh", 'Shoftim', 'Ki Seitzei', 'Ki Savo', 'Nitzavim', 'Vayeilech', 'Haazinu', 'Vezos Haberachah' ] PARSHIOS_HEBREW = [ 'בראשית', 'נח', 'לך לך', 'וירא', 'חיי שרה', 'תולדות', 'ויצא', 'וישלח', 'וישב', 'מקץ', 'ויגש', 'ויחי', 'שמות', 'וארא', 'בא', 'בשלח', 'יתרו', 'משפטים', 'תרומה', 'תצוה', 'כי תשא', 'ויקהל', 'פקודי', 'ויקרא', 'צו', 'שמיני', 'תזריע', 'מצורע', 'אחרי מות', 'קדושים', 'אמור', 'בהר', 'בחוקותי', 'במדבר', 'נשא', 'בהעלותך', 'שלח', 'קרח', 'חקת', 'בלק', 'פינחס', 'מטות', 'מסעי', 'דברים', 'ואתחנן', 'עקב', 'ראה', 'שופטים', 'כי תצא', 'כי תבא', 'נצבים', 'וילך', 'האזינו', 'וזאת הברכה' ] def _parshaless(date, israel=False): if israel and date.tuple()[1:] in [(7, 23), (1, 22), (3, 7)]: return False if date.month == 7 and date.day in ([1, 2, 10] + list(range(15, 24))): return True if date.month == 1 and date.day in range(15, 23): return True if date.month == 3 and date.day in [6, 7]: return True return False @lru_cache(maxsize=50) def _gentable(year, israel=False): """Return OrderedDict mapping date of Shabbos to list of parsha numbers. The numbers start with Beraishis as 0. Double parshios are represented as a list of the two numbers. If there is no Parsha the value is None. """ parshalist = deque([51, 52] + list(range(52))) table = OrderedDict() leap = _is_leap(year) pesachday = HebrewDate(year, 1, 15).weekday() rosh_hashana = HebrewDate(year, 7, 1) shabbos = rosh_hashana.shabbos() if rosh_hashana.weekday() > 4: parshalist.popleft() while shabbos.year == year: if _parshaless(shabbos, israel): table[shabbos] = None else: parsha = parshalist.popleft() table[shabbos] = [parsha] if ( (parsha == 21 and (HebrewDate(year, 1, 14) - shabbos) // 7 < 3) or (parsha in [26, 28] and not leap) or ( parsha == 31 and not leap and (not israel or pesachday != 7) ) or (parsha == 38 and not israel and pesachday == 5) or (parsha == 41 and (HebrewDate(year, 5, 9)-shabbos) // 7 < 2) or (parsha == 50 and HebrewDate(year+1, 7, 1).weekday() > 4) ): # If any of that then it's a double parsha. table[shabbos].append(parshalist.popleft()) shabbos += 7 return table def getparsha(date, israel=False): """Return the parsha for a given date. Returns the parsha for the Shabbos on or following the given date. Parameters ---------- date : ~pyluach.dates.BaseDate Any subclass of ``BaseDate``. This date does not have to be a Shabbos. israel : bool, optional ``True`` if you want the parsha according to the Israel schedule (with only one day of Yom Tov). Defaults to ``False``. Returns ------- list of int or None A list of the numbers of the parshios for the Shabbos of the given date, beginning with 0 for Beraishis, or ``None`` if the Shabbos doesn't have a parsha (i.e. it's on Yom Tov). """ shabbos = date.to_heb().shabbos() table = _gentable(shabbos.year, israel) return table[shabbos] def getparsha_string(date, israel=False, hebrew=False): """Return the parsha as a string for the given date. This function wraps ``getparsha`` returning the parsha name. Parameters ---------- date : ~pyluach.dates.BaseDate Any subclass of ``BaseDate``. The date does not have to be a Shabbos. israel : bool, optional ``True`` if you want the parsha according to the Israel schedule (with only one day of Yom Tov). Default is ``False``. hebrew : bool, optional ``True`` if you want the name of the parsha in Hebrew. Default is ``False``. Returns ------- str or None The name of the parsha separated by a comma and space if it is a double parsha or ``None`` if there is no parsha that Shabbos (ie. it's yom tov). """ parsha = getparsha(date, israel) if parsha is None: return None if not hebrew: name = [PARSHIOS[n] for n in parsha] else: name = [PARSHIOS_HEBREW[n] for n in parsha] return ', '.join(name) def iterparshios(year, israel=False): """Generate all the parshios in the year. Parameters ---------- year : int The Hebrew year to get the parshios for. israel : bool, optional ``True`` if you want the parsha according to the Israel schedule (with only one day of Yom Tov). Defaults to ``False`` Yields ------ :obj:`list` of :obj:`int` or :obj:`None` A list of the numbers of the parshios for the next Shabbos in the given year. Yields ``None`` for a Shabbos that doesn't have its own parsha (i.e. it occurs on a yom tov). """ table = _gentable(year, israel) for shabbos in table.values(): yield shabbos def parshatable(year, israel=False): """Return a table of all the Shabbosos in the year Parameters ---------- year : int The Hebrew year to get the parshios for. israel : bool, optional ``True`` if you want the parshios according to the Israel schedule (with only one day of Yom Tov). Defaults to ``False``. Returns ------- ~collections.OrderedDict An ordered dictionary with the ``HebrewDate`` of each Shabbos as the key mapped to the parsha as a list of ints, or ``None`` for a Shabbos with no parsha. """ return _gentable(year, israel) pyluach-2.2.0/src/pyluach/utils.py000066400000000000000000000244751437753445300171610ustar00rootroot00000000000000"""The utils module contains shared functions and constants. They are to be used internally. """ from functools import lru_cache from enum import Enum class _Days(Enum): ROSH_HASHANA = 'Rosh Hashana' YOM_KIPPUR = 'Yom Kippur' SUCCOS = 'Succos' SHMINI_ATZERES = 'Shmini Atzeres' SIMCHAS_TORAH = 'Simchas Torah' CHANUKA = 'Chanuka' TU_BSHVAT = "Tu B'shvat" PURIM_KATAN = 'Purim Katan' PURIM = 'Purim' SHUSHAN_PURIM = 'Shushan Purim' PESACH = 'Pesach' PESACH_SHENI = 'Pesach Sheni' LAG_BAOMER = "Lag Ba'omer" SHAVUOS = 'Shavuos' TU_BAV = "Tu B'av" TZOM_GEDALIA = 'Tzom Gedalia' TENTH_OF_TEVES = '10 of Teves' TAANIS_ESTHER = 'Taanis Esther' SEVENTEENTH_OF_TAMUZ = '17 of Tamuz' NINTH_OF_AV = '9 of Av' _days_hebrew = { _Days.ROSH_HASHANA: 'ראש השנה', _Days.YOM_KIPPUR: 'יום כיפור', _Days.SUCCOS: 'סוכות', _Days.SHMINI_ATZERES: 'שמיני עצרת', _Days.SIMCHAS_TORAH: 'שמחת תורה', _Days.CHANUKA: 'חנוכה', _Days.TU_BSHVAT: 'ט״ו בשבט', _Days.PURIM_KATAN: 'פורים קטן', _Days.PURIM: 'פורים', _Days.SHUSHAN_PURIM: 'שושן פורים', _Days.PESACH: 'פסח', _Days.PESACH_SHENI: 'פסח שני', _Days.LAG_BAOMER: 'ל״ג בעומר', _Days.SHAVUOS: 'שבועות', _Days.TU_BAV: 'ט״ו באב', _Days.TZOM_GEDALIA: 'צום גדליה', _Days.TENTH_OF_TEVES: 'י׳ בטבת', _Days.TAANIS_ESTHER: 'תענית אסתר', _Days.SEVENTEENTH_OF_TAMUZ: 'י״ז בתמוז', _Days.NINTH_OF_AV: 'ט׳ באב' } MONTH_NAMES = [ 'Nissan', 'Iyar', 'Sivan', 'Tammuz', 'Av', 'Elul', 'Tishrei', 'Cheshvan', 'Kislev', 'Teves', 'Shevat', 'Adar', 'Adar 1', 'Adar 2'] MONTH_NAMES_HEBREW = [ 'ניסן', 'אייר', 'סיון', 'תמוז', 'אב', 'אלול', 'תשרי', 'חשון', 'כסלו', 'טבת', 'שבט', 'אדר', 'אדר א׳', 'אדר ב׳'] FAST_DAYS = [ 'Tzom Gedalia', '10 of Teves', 'Taanis Esther', '17 of Tamuz', '9 of Av'] FAST_DAYS_HEBREW = [ 'צום גדליה', 'י׳ בטבת', 'תענית אסתר', 'י״ז בתמוז', 'ט׳ באב'] FESTIVALS = [ 'Rosh Hashana', 'Yom Kippur', 'Succos', 'Shmini Atzeres', 'Simchas Torah', 'Chanuka', "Tu B'shvat", 'Purim Katan', 'Purim', 'Shushan Purim', 'Pesach', 'Pesach Sheni', "Lag Ba'omer", 'Shavuos', "Tu B'av"] FESTIVALS_HEBREW = [ 'ראש השנה', 'יום כיפור', 'סוכות', 'שמיני עצרת', 'שמחת תורה', 'חנוכה', 'ט״ו בשבט', 'פורים קטן', 'פורים', 'שושן פורים', 'פסח', 'פסח שני', 'ל״ג בעומר', 'שבועות', 'ט״ו באב' ] WEEKDAYS = { 1: 'ראשון', 2: 'שני', 3: 'שלישי', 4: 'רביעי', 5: 'חמישי', 6: 'שישי', 7: 'שבת' } def _is_leap(year): if (((7*year) + 1) % 19) < 7: return True return False def _elapsed_months(year): return (235 * year - 234) // 19 @lru_cache(maxsize=10) def _elapsed_days(year): months_elapsed = _elapsed_months(year) parts_elapsed = 204 + 793*(months_elapsed%1080) hours_elapsed = ( 5 + 12*months_elapsed + 793*(months_elapsed//1080) + parts_elapsed//1080) conjunction_day = 1 + 29*months_elapsed + hours_elapsed//24 conjunction_parts = 1080 * (hours_elapsed%24) + parts_elapsed%1080 if ( (conjunction_parts >= 19440) or ( (conjunction_day % 7 == 2) and (conjunction_parts >= 9924) and not _is_leap(year) ) or ( (conjunction_day % 7 == 1) and conjunction_parts >= 16789 and _is_leap(year - 1) ) ): alt_day = conjunction_day + 1 else: alt_day = conjunction_day if alt_day % 7 in [0, 3, 5]: alt_day += 1 return alt_day def _days_in_year(year): return _elapsed_days(year + 1) - _elapsed_days(year) def _long_cheshvan(year): """Returns True if Cheshvan has 30 days""" return _days_in_year(year) % 10 == 5 def _short_kislev(year): """Returns True if Kislev has 29 days""" return _days_in_year(year) % 10 == 3 def _month_length(year, month): """Months start with Nissan (Nissan is 1 and Tishrei is 7)""" if month in [1, 3, 5, 7, 11]: return 30 if month in [2, 4, 6, 10, 13]: return 29 if month == 12: if _is_leap(year): return 30 return 29 if month == 8: # if long Cheshvan return 30, else return 29 if _long_cheshvan(year): return 30 return 29 if month == 9: # if short Kislev return 29, else return 30 if _short_kislev(year): return 29 return 30 raise ValueError('Invalid month') def _month_name(year, month, hebrew): index = month if month < 12 or not _is_leap(year): index -= 1 if hebrew: return MONTH_NAMES_HEBREW[index] return MONTH_NAMES[index] def _monthslist(year): months = [7, 8, 9, 10, 11, 12, 13, 1, 2, 3, 4, 5, 6] if not _is_leap(year): months.remove(13) return months def _add_months(year, month, num): monthslist = _monthslist(year) index = monthslist.index(month) months_remaining = len(monthslist[index+1:]) if num <= months_remaining: return (year, monthslist[index + num]) return _add_months(year + 1, 7, num - months_remaining - 1) def _subtract_months(year, month, num): monthslist = _monthslist(year) index = monthslist.index(month) if num <= index: return (year, monthslist[index - num]) return _subtract_months(year - 1, 6, num - (index+1)) def _fast_day(date): """Return name of fast day or None. Parameters ---------- date : ``HebrewDate``, ``GregorianDate``, or ``JulianDay`` Any date that implements a ``to_heb()`` method which returns a ``HebrewDate`` can be used. Returns ------- str or ``None`` The name of the fast day or ``None`` if the given date is not a fast day. """ date = date.to_heb() year = date.year month = date.month day = date.day weekday = date.weekday() adar = 13 if _is_leap(year) else 12 if month == 7: if (weekday == 1 and day == 4) or (weekday != 7 and day == 3): return _Days.TZOM_GEDALIA elif month == 10 and day == 10: return _Days.TENTH_OF_TEVES elif month == adar: if (weekday == 5 and day == 11) or weekday != 7 and day == 13: return _Days.TAANIS_ESTHER elif month == 4: if (weekday == 1 and day == 18) or (weekday != 7 and day == 17): return _Days.SEVENTEENTH_OF_TAMUZ elif month == 5: if (weekday == 1 and day == 10) or (weekday != 7 and day == 9): return _Days.NINTH_OF_AV return None def _fast_day_string(date, hebrew=False): fast = _fast_day(date) if fast is None: return None if hebrew: return _days_hebrew[fast] return fast.value def _first_day_of_holiday(holiday): if holiday is _Days.ROSH_HASHANA: return (7, 1) if holiday is _Days.SUCCOS: return (7, 15) if holiday is _Days.CHANUKA: return (9, 25) if holiday is _Days.PESACH: return (1, 15) if holiday is _Days.SHAVUOS: return (3, 6) return None def _festival(date, israel=False, include_working_days=True): """Return Jewish festival of given day. This method will return all major and minor religous Jewish holidays not including fast days. Parameters ---------- date : ``HebrewDate``, ``GregorianDate``, or ``JulianDay`` Any date that implements a ``to_heb()`` method which returns a ``HebrewDate`` can be used. israel : bool, optional ``True`` if you want the holidays according to the Israel schedule. Defaults to ``False``. include_working_days : bool, optional ``True`` to include festival days in which melacha (work) is allowed; ie. Pesach Sheni, Chol Hamoed, etc. Default is ``True``. Returns ------- str or ``None`` The festival or ``None`` if the given date is not a Jewish festival. """ date = date.to_heb() year = date.year month = date.month day = date.day if month == 7: if day in [1, 2]: return _Days.ROSH_HASHANA if day == 10: return _Days.YOM_KIPPUR if ( not include_working_days and (day in range(17, 22) or (israel and day == 16)) ): return None if day in range(15, 22): return _Days.SUCCOS if day == 22: return _Days.SHMINI_ATZERES if day == 23 and not israel: return _Days.SIMCHAS_TORAH elif month in [9, 10] and include_working_days: kislev_length = _month_length(year, 9) if ( month == 9 and day in range(25, kislev_length + 1) or month == 10 and day in range(1, 8 - (kislev_length - 25)) ): return _Days.CHANUKA elif month == 11 and day == 15 and include_working_days: return _Days.TU_BSHVAT elif month == 12 and include_working_days: leap = _is_leap(year) if day == 14: return _Days.PURIM_KATAN if leap else _Days.PURIM if day == 15 and not leap: return _Days.SHUSHAN_PURIM elif month == 13 and include_working_days: if day == 14: return _Days.PURIM if day == 15: return _Days.SHUSHAN_PURIM elif month == 1: if ( not include_working_days and (day in range(17, 21) or (israel and day == 16)) ): return None if day in range(15, 22 if israel else 23): return _Days.PESACH elif month == 2 and day == 14 and include_working_days: return _Days.PESACH_SHENI elif month == 2 and day == 18 and include_working_days: return _Days.LAG_BAOMER elif month == 3 and (day == 6 or (not israel and day == 7)): return _Days.SHAVUOS elif month == 5 and day == 15 and include_working_days: return _Days.TU_BAV return None def _festival_string( date, israel=False, hebrew=False, include_working_days=True, ): festival = _festival(date, israel, include_working_days) if festival is None: return None if hebrew: return _days_hebrew[festival] return festival.value pyluach-2.2.0/tests/000077500000000000000000000000001437753445300143415ustar00rootroot00000000000000pyluach-2.2.0/tests/__init__.py000066400000000000000000000000001437753445300164400ustar00rootroot00000000000000pyluach-2.2.0/tests/test_dates.py000066400000000000000000000331371437753445300170610ustar00rootroot00000000000000import datetime from operator import gt, lt, eq, ne, ge, le, add, sub import pytest from pyluach import dates, hebrewcal, utils from pyluach.dates import HebrewDate, GregorianDate, JulianDay, Rounding KNOWN_VALUES = {(2009, 8, 21): (5769, 6, 1), (2009, 9, 30): (5770, 7, 12), (2009, 11, 13): (5770, 8, 26), (2010, 1, 21): (5770, 11, 6), (2010, 5, 26): (5770, 3, 13), (2013, 11, 17): (5774, 9, 14), (2014, 3, 12): (5774, 13, 10), (2014, 6, 10): (5774, 3, 12), (2016, 2, 10): (5776, 12, 1) } @pytest.fixture(scope='module') def datetypeslist(): datetypes = [dates.HebrewDate, dates.GregorianDate] return datetypes class TestClassesSanity: def test_greg_sanity(self): for i in range(347998, 2460000, 117): jd = dates.JulianDay(i) conf = jd.to_greg().to_jd() if jd >= dates.GregorianDate(1, 1, 1): assert jd.day == conf.day else: assert abs(jd.day - conf.day) <= 1 bce = GregorianDate(-100, 1, 1) assert abs(bce.to_heb().to_greg() - bce) <= 1 def test_heb_sanity(self): for i in range(347998, 2460000, 117): jd = dates.JulianDay(i) conf = jd.to_heb().to_jd() assert jd.day == conf.day class TestClassesConversion: def test_from_greg(self): for date in KNOWN_VALUES: heb = dates.GregorianDate(*date).to_heb().tuple() assert KNOWN_VALUES[date] == heb def test_from_heb(self): for date in KNOWN_VALUES: greg = dates.HebrewDate(*KNOWN_VALUES[date]).to_greg().tuple() assert date == greg @pytest.fixture def setup(scope='module'): caltypes = [GregorianDate, HebrewDate, JulianDay] deltas = [0, 1, 29, 73, 1004] return {'caltypes': caltypes, 'deltas': deltas} class TestOperators: def test_add(self, setup): for cal in setup['caltypes']: for delta in setup['deltas']: date = cal.today() date2 = date + delta assert date.jd + delta == date2.jd def test_min_int(self, setup): """Test subtracting a number from a date""" for cal in setup['caltypes']: for delta in setup['deltas']: date = cal.today() date2 = date - delta assert date.jd - delta == date2.jd def test_min_date(self, setup): """Test subtracting one date from another This test loops through subtracting the current date of each calendar from a date of each calendar at intervals from the current date. """ for cal in setup['caltypes']: for cal2 in setup['caltypes']: for delta in setup['deltas']: today = cal.today() difference = (cal2.today() - delta) - today assert delta == difference assert isinstance(difference, int) class TestComparisons: """In ComparisonTests, comparisons are tested. Every function tests one test case comparing a date from each calendar type to another date from each calendar type. """ def test_gt(self, setup): """Test all comparers when one date is greater.""" for cal in setup['caltypes']: today = cal.today() for cal2 in setup['caltypes']: yesterday = cal2.today() - 1 for comp in [gt, ge, ne]: assert comp(today, yesterday) for comp in [eq, lt, le]: assert comp(today, yesterday) is False def test_lt(self, setup): """Test all comparers when one date is less than another.""" for cal in setup['caltypes']: today = cal.today() for cal2 in setup['caltypes']: tomorrow = cal2.today() + 1 for comp in [lt, le, ne]: assert comp(today, tomorrow) for comp in [gt, ge, eq]: assert comp(today, tomorrow) is False def test_eq(self, setup): """Test all comparers when the dates are equal.""" for cal in setup['caltypes']: today = cal.today() for cal2 in setup['caltypes']: today2 = cal2.today() for comp in [eq, ge, le]: assert comp(today, today2) for comp in [gt, lt, ne]: assert comp(today, today2) is False class TestErrors: def test_too_low_heb(self): with pytest.raises(ValueError): dates.HebrewDate(0, 7, 1) with pytest.raises(ValueError): dates.HebrewDate(-1, 7, 1) def test_comparer_errors(self): day1 = dates.HebrewDate(5777, 12, 10) for date in [day1, day1.to_greg(), day1.to_jd()]: for comparer in [gt, lt, ge, le]: for value in [1, 0, 'hello', None, '']: with pytest.raises(TypeError): comparer(date, value) assert (day1 == 5) is False assert (day1 != 'h') is True def test_operator_errors(self): day = dates.GregorianDate(2016, 11, 20) for operator in [add, sub]: for value in ['Hello', '', None]: with pytest.raises(TypeError): operator(day, value) with pytest.raises(TypeError): day + (day+1) def test_HebrewDate_errors(self): with pytest.raises(ValueError): HebrewDate(0, 6, 29) for datetuple in [ (5778, 0, 5), (5779, -1, 7), (5759, 14, 8), (5778, 13, 20), (5782, 12, 31) ]: with pytest.raises(ValueError): HebrewDate(*datetuple) for datetuple in [(5778, 6, 0), (5779, 8, 31), (5779, 10, 30)]: with pytest.raises(ValueError): HebrewDate(*datetuple) def test_GregorianDate_errors(self): for datetuple in [ (2018, 0, 3), (2018, -2, 8), (2018, 13, 9), (2018, 2, 0), (2018, 2, 29), (2012, 2, 30) ]: with pytest.raises(ValueError): GregorianDate(*datetuple) def test_JD_errors(self): with pytest.raises(ValueError): JulianDay(-1).to_heb() with pytest.raises(TypeError): JulianDay(689)._to_x(datetime.date) class TestReprandStr: def test_repr(self, datetypeslist): for datetype in datetypeslist: assert eval(repr(datetype.today())) == datetype.today() jd = JulianDay.today() assert eval(repr(jd)) == jd def test_jd_str(self): assert str(JulianDay(550.5)) == '550.5' assert str(JulianDay(1008)) == '1007.5' def test_greg_str(self): date = GregorianDate(2018, 8, 22) assert str(date) == '2018-08-22' assert str(GregorianDate(2008, 12, 2)) == '2008-12-02' assert str(GregorianDate(1, 1, 1)) == '0001-01-01' def test_weekday(): assert GregorianDate(2017, 8, 7).weekday() == 2 assert HebrewDate(5777, 6, 1).weekday() == 4 assert JulianDay(2458342.5).weekday() == 1 def test_isoweekday(): assert GregorianDate(2020, 9, 20).isoweekday() == 7 assert GregorianDate(2020, 10, 3).isoweekday() == 6 assert GregorianDate(2020, 10, 5).isoweekday() == 1 assert JulianDay(2458342.5).isoweekday() == 7 class TestMixinMethods: @pytest.fixture def date(self): return dates.GregorianDate(2017, 10, 31) def test_str(self, date): assert str(date) == '2017-10-31' def test_dict(self, date): assert date.dict() == {'year': 2017, 'month': 10, 'day': 31} def test_iter(self, date): assert list(date) == [date.year, date.month, date.day] class TestHolidayMethods: def test_fast_day(self): date = dates.HebrewDate(5781, 7, 3) assert date.holiday() == 'Tzom Gedalia' assert date.holiday(False, True) == 'צום גדליה' assert date.fast_day() == 'Tzom Gedalia' assert date.fast_day(True) == 'צום גדליה' def test_festival(self): date = dates.GregorianDate(2020, 12, 11) assert date.holiday() == 'Chanuka' assert date.holiday(hebrew=True) == 'חנוכה' assert date.festival() == 'Chanuka' assert date.festival(hebrew=True) == 'חנוכה' assert date.festival(include_working_days=False) is None def test_day_of_holiday(self): assert HebrewDate(5783, 12, 14)._day_of_holiday(israel=False) == '' shavuos = HebrewDate(5783, 3, 6) assert shavuos.holiday(prefix_day=True) == '1 Shavuos' assert shavuos.holiday(israel=True, prefix_day=True) == 'Shavuos' shavuos_greg = shavuos.to_greg() assert shavuos_greg.holiday(prefix_day=True) == '1 Shavuos' assert shavuos.to_jd().holiday(prefix_day=True) == '1 Shavuos' def test_to_pydate(): day = HebrewDate(5778, 6, 1) jd = day.to_jd() for day_type in [day, jd]: assert day_type.to_pydate() == datetime.date(2018, 8, 12) def test_from_pydate(): date = datetime.date(2018, 8, 27) assert date == GregorianDate.from_pydate(date).to_jd().to_pydate() assert date == HebrewDate.from_pydate(date).to_pydate() assert date == JulianDay.from_pydate(date).to_pydate() def test_is_leap(): assert GregorianDate(2020, 10, 26).is_leap() is True assert GregorianDate(2021, 10, 26).is_leap() is False def test_hebrew_year(): date = HebrewDate(5783, 12, 14) assert date.hebrew_year(True, False) == 'התשפג' def test_hebrew_date_string(): date = HebrewDate(5782, 7, 1) assert date.hebrew_date_string() == 'א׳ תשרי תשפ״ב' assert date.hebrew_date_string(True) == 'א׳ תשרי ה׳תשפ״ב' def test_month_name(): date = HebrewDate(5781, 12, 14) assert date.month_name() == 'Adar' assert date.month_name(True) == 'אדר' date2 = HebrewDate(5782, 12, 14) assert date2.month_name() == 'Adar 1' assert date2.month_name(True) == 'אדר א׳' def test_month_length(): with pytest.raises(ValueError): utils._month_length(5782, 14) @pytest.fixture def date(): return HebrewDate(5782, 2, 18) class TestFormat: def test_errors(self, date): with pytest.raises(ValueError): format(date, ' %') with pytest.raises(ValueError): format(date, '%*') with pytest.raises(ValueError): format(date, '%*-') with pytest.raises(ValueError): format(date, '%*-h') with pytest.raises(ValueError): format(date, '%z') with pytest.raises(ValueError): format(date, '%*z') with pytest.raises(ValueError): format(date, '%-') with pytest.raises(ValueError): format(date, '%-z') def test_format_weekday(self, date): pydate = date.to_pydate() A = pydate.strftime('%A') a = pydate.strftime('%a') ha = 'ה׳' hA = 'חמישי' assert format(date, 'w: %w %a %A %*a %*A') == f'w: 5 {a} {A} {ha} {hA}' def test_format_month(self, date): month = hebrewcal.Month(5782, 2) B = month.month_name(False) hB = month.month_name(True) assert format(date, 'm: %m %-m %B %*B') == f'm: 02 2 {B} {hB}' def test_format_day(self, date): assert format(date, 'd: %d %-d %%') == 'd: 18 18 %' assert format(date - 9, '%d %-d') == '09 9' assert format(date, '%*d %*-d') == 'י״ח יח' assert format(date + 2, '%*d - %*-d') == 'כ׳ - כ' def test_format_year(self, date): hy = 'תשפ״ב' hY = 'ה׳תשפ״ב' assert format(date, '%Y %y %*y %*Y') == f'5782 82 {hy} {hY}' def test_format_greg(self): date = GregorianDate(2022, 5, 8) assert format(date, '%y') == '22' assert date.strftime('%Y') == '2022' def test_add_years(): date = HebrewDate(5782, 12, 30) assert ( date.add(years=1, rounding=Rounding.PREVIOUS_DAY) == HebrewDate(5783, 12, 29) ) assert date.add(years=1) == HebrewDate(5783, 1, 1) date = HebrewDate(5782, 13, 1) assert date.add(years=1) == HebrewDate(5783, 12, 1) date = HebrewDate(5783, 12, 29) assert date.add(years=-1) == HebrewDate(5782, 13, 29) def test_add(): date = HebrewDate(5782, 12, 30) assert ( date.add(months=1, rounding=Rounding.PREVIOUS_DAY) == HebrewDate(5782, 13, 29) ) assert date.add(months=1) == HebrewDate(5782, 1, 1) assert date.add(months=27) == HebrewDate(5784, 1, 30) assert ( date.add(months=27, rounding=Rounding.PREVIOUS_DAY) == HebrewDate(5784, 1, 30) ) with pytest.raises(ValueError): date.add(months=1, rounding=Rounding.EXCEPTION) date = HebrewDate(5781, 7, 28) assert date.add(years=2, months=1, days=2) == HebrewDate(5783, 8, 30) assert date.add(years=3, months=2, days=2) == HebrewDate(5784, 10, 1) def test_add_error(): date = HebrewDate(5783, 11, 30) with pytest.raises(TypeError): date.add(months=1, rounding='Rounding.PREVIOUS_DAY') def test_subtract(): date = HebrewDate(5782, 12, 30) assert ( date.subtract(months=6, rounding=Rounding.PREVIOUS_DAY) == HebrewDate(5781, 6, 29) ) assert date.subtract(months=6) == HebrewDate(5782, 7, 1) def test_replace(): date = HebrewDate(5782, 4, 20) assert date.replace(year=5783) == HebrewDate(5783, 4, 20) assert date.replace(month=12) == HebrewDate(5782, 12, 20) assert date.replace(day=1) == HebrewDate(5782, 4, 1) with pytest.raises(ValueError): HebrewDate(5783, 12, 20).replace(month=13) pyluach-2.2.0/tests/test_gematria.py000066400000000000000000000015051437753445300175440ustar00rootroot00000000000000from pyluach.gematria import _num_to_str def test_one_letter(): assert _num_to_str(5) == 'ה׳' assert _num_to_str(10) == 'י׳' assert _num_to_str(200) == 'ר׳' def test_two_letters(): assert _num_to_str(18) == 'י״ח' assert _num_to_str(15) == 'ט״ו' assert _num_to_str(16) == 'ט״ז' assert _num_to_str(101) == 'ק״א' def test_three_letters(): assert _num_to_str(127) == 'קכ״ז' assert _num_to_str(489) == 'תפ״ט' assert _num_to_str(890) == 'תת״צ' def test_four_letters(): assert _num_to_str(532) == 'תקל״ב' def test_five_letters(): assert _num_to_str(916) == 'תתקט״ז' def test_thousands(): assert _num_to_str(5781, True) == 'ה׳תשפ״א' assert _num_to_str(10000, True) == 'י׳' assert _num_to_str(12045, True) == 'יב׳מ״ה' pyluach-2.2.0/tests/test_hebrewcal.py000066400000000000000000000563511437753445300177200ustar00rootroot00000000000000import datetime from copy import copy import calendar from pytest import fixture, raises from bs4 import BeautifulSoup from pyluach import dates, hebrewcal from pyluach.hebrewcal import Year, Month, holiday, festival, fast_day from pyluach.hebrewcal import HebrewTextCalendar, HebrewHTMLCalendar class TestYear: def test_repryear(self): year = Year(5777) assert eval(repr(year)) == year def test_iteryear(self): assert list(Year(5777)) == [7, 8, 9, 10, 11, 12, 1, 2, 3, 4, 5, 6] assert list(Year(5776)) == [7, 8, 9, 10, 11, 12, 13, 1, 2, 3, 4, 5, 6] def test_equalyear(self): year1 = hebrewcal.Year(5777) year2 = hebrewcal.Year(5777) assert year1 == year2 def test_addtoyear(self): year = Year(5777) assert year + 2 == Year(5779) assert year + 0 == year with raises(TypeError): year + year with raises(TypeError): year + 'str' def test_subtractintfromyear(self): year = Year(5777) assert year - 0 == year assert year - 3 == Year(5774) with raises(TypeError): year - 'str' def test_subtractyearfromyear(self): year = Year(5777) assert year - year == 0 assert year - (year - 1) == 1 assert year - (year + 2) == 2 def test_monthscount(self): year = Year(5782) assert year.monthscount() == 13 assert (year + 1).monthscount() == 12 def test_iterdays(self): year = Year(5778) yearlist = list(year.iterdays()) assert len(yearlist) == len(year) assert yearlist[0] == 1 assert yearlist[-1] == len(year) def test_iterdates(self): year = 5778 workingdate = dates.HebrewDate(year, 7, 1) for date in Year(year).iterdates(): assert workingdate == date workingdate += 1 def test_errors(self): with raises(ValueError): Year(0) def test_year_string(self): year = Year(5781) assert year.year_string() == 'תשפ״א' assert year.year_string(True) == 'ה׳תשפ״א' def test_from_date(self): date = dates.GregorianDate(2021, 6, 7) year = Year.from_date(date) assert year == Year(date.to_heb().year) def test_from_pydate(self): pydate = datetime.date(2021, 6, 7) date = dates.HebrewDate.from_pydate(pydate) assert Year.from_pydate(pydate) == Year(date.year) @fixture def years(): year1 = Year(5778) year2 = Year(5780) return {1: year1, 2: year2} class TestYearComparisons: def test_year_equals(self, years): assert years[1] == copy(years[1]) assert (years[1] == years[2]) is False assert years[2] != years[1] assert (copy(years[2]) != years[2]) is False assert years[1] != 5778 def test_year_gt(self, years): assert years[2] > years[1] assert (years[1] > years[1]) is False def test_years_ge(self, years): assert copy(years[1]) >= years[1] assert years[2] >= years[1] assert (years[1] >= years[2]) is False def test_years_lt(self, years): assert years[1] < years[2] assert (copy(years[2]) < years[2]) is False assert (years[2] < years[1]) is False def test_years_le(self, years): assert copy(years[1]) <= years[1] assert years[1] <= years[2] assert (years[2] <= years[1]) is False def test_errors(self, years): with raises(TypeError): years[1] > 5778 with raises(TypeError): years[1] >= 0 with raises(TypeError): years[1] < '5778' with raises(TypeError): years[1] <= 10000 class TestMonth: def test_reprmonth(self): month = Month(5777, 10) assert eval(repr(month)) == month def test_equalmonth(self): month1 = hebrewcal.Month(5777, 12) month2 = hebrewcal.Month(5777, 12) assert month1 == month2 assert not month1 == (month2 + 1) def test_addinttomonth(self): month = hebrewcal.Month(5777, 12) assert month + 0 == month assert month + 1 == hebrewcal.Month(5777, 1) assert month + 6 == hebrewcal.Month(5777, 6) assert month + 7 == hebrewcal.Month(5778, 7) assert month + 35 == hebrewcal.Month(5780, 10) with raises(TypeError): month + month with raises(TypeError): month + 'str' def test_subtract_month(self): month1 = hebrewcal.Month(5775, 10) month2 = hebrewcal.Month(5776, 10) month3 = hebrewcal.Month(5777, 10) assert month1 - month2 == 12 assert month3 - month1 == 25 def test_subtractintfrommonth(self): month = hebrewcal.Month(5778, 9) assert month - 2 == hebrewcal.Month(5778, 7) assert month - 3 == hebrewcal.Month(5777, 6) assert month - 30 == hebrewcal.Month(5775, 4) with raises(TypeError): month - 'str' def test_startingweekday(self): assert Month(5778, 8).starting_weekday() == 7 assert Month(5778, 9).starting_weekday() == 1 def test_iterdate(self): year = 5770 workingdate = dates.HebrewDate(year, 7, 1) for month in (list(range(7, 13)) + list(range(1, 7))): for date in Month(year, month).iterdates(): assert date == workingdate workingdate += 1 def test_molad(self): month = Month(5779, 7) assert month.molad() == {'weekday': 2, 'hours': 14, 'parts': 316} month = Month(5779, 5) assert month.molad() == {'weekday': 5, 'hours': 10, 'parts': 399} def test_molad_announcement(self): month = Month(5780, 3) assert month.molad_announcement() == { 'weekday': 6, 'hour': 11, 'minutes': 42, 'parts': 13 } month = Month(5780, 2) assert month.molad_announcement() == { 'weekday': 4, 'hour': 22, 'minutes': 58, 'parts': 12 } month = Month(5780, 8) assert month.molad_announcement() == { 'weekday': 2, 'hour': 18, 'minutes': 34, 'parts': 6 } month = Month(5780, 12) assert month.molad_announcement() == { 'weekday': 1, 'hour': 21, 'minutes': 30, 'parts': 10 } month = Month(5781, 1) assert month.molad_announcement() == { 'weekday': 7, 'hour': 19, 'minutes': 3, 'parts': 5 } month = Month(5781, 8) assert month.molad_announcement() == { 'weekday': 7, 'hour': 3, 'minutes': 23, 'parts': 0 } def test_month_name(self): month = Month(5781, 9) assert month.month_name() == 'Kislev' assert month.month_name(hebrew=True) == 'כסלו' adar = Month(5781, 12) assert adar.month_name() == 'Adar' adar_bais = Month(5782, 13) assert adar_bais.month_name() == 'Adar 2' def test_month_string(self): month = Month(5781, 3) assert month.month_string() == 'סיון תשפ״א' assert month.month_string(True) == 'סיון ה׳תשפ״א' def test_errors(self): with raises(ValueError): Month(-1, 1) with raises(ValueError): Month(5781, 13) with raises(ValueError): Month(5781, 14) def test_from_date(self): date = dates.HebrewDate(5781, 7, 10) assert Month.from_date(date) == Month(date.year, date.month) def test_from_pydate(self): pydate = datetime.date(2021, 6, 7) date = dates.HebrewDate.from_pydate(pydate) assert Month.from_pydate(pydate) == Month(date.year, date.month) @fixture def months(): month1 = Month(5780, 3) month2 = Month(5780, 4) month3 = Month(5781, 3) month12 = Month(5781, 12) return {1: month1, 2: month2, 3: month3, 12: month12} class TestCompareMonth: def test_month_gt(self, months): assert months[2] > months[1] assert not (months[1] > months[2]) assert months[3] > months[1] assert not (months[2] > months[3]) assert months[3] > months[12] assert not (months[12] > months[3]) def test_month_ge(self, months): assert copy(months[1]) >= months[1] assert months[2] >= months[1] assert (months[2] >= months[3]) is False assert months[3] >= months[12] def test_month_lt(self, months): assert (copy(months[2]) < months[2]) is False assert months[1] < months[2] assert months[2] < months[3] assert (months[3] < months[1]) is False assert Month(5780, 12) < months[1] def test_month_le(self, months): assert copy(months[2]) <= months[2] assert months[1] <= months[2] assert (months[3] <= months[2]) is False assert months[12] <= months[3] def test_month_ne(self, months): assert months[2] != months[1] assert months[3] != months[1] assert (copy(months[1]) != months[1]) is False assert months[3] != 3 def test_month_errors(self, months): with raises(TypeError): months[1] > 5 with raises(TypeError): assert months[2] <= '5' with raises(TypeError): assert months[1] >= 0 with raises(TypeError): assert months[3] < 100 class TestHoliday: def test_roshhashana(self): roshhashana = dates.HebrewDate(5779, 7, 1) assert all([ holiday(day, location) == 'Rosh Hashana' for day in [roshhashana, roshhashana + 1] for location in [True, False] ]) assert all(( festival( day, location, include_working_days=included_days ) == 'Rosh Hashana' for day in [roshhashana, roshhashana + 1] for location in [True, False] for included_days in [True, False] )) assert ( holiday(roshhashana, hebrew=True, prefix_day=True) == 'א׳ ראש השנה' ) assert festival(roshhashana + 1, prefix_day=True) == '2 Rosh Hashana' def test_yomkippur(self): yom_kippur = dates.HebrewDate(5775, 7, 10) assert holiday(yom_kippur) == 'Yom Kippur' assert holiday(yom_kippur, hebrew=True) == 'יום כיפור' assert festival(yom_kippur, include_working_days=False) == 'Yom Kippur' assert holiday(yom_kippur, prefix_day=True) == 'Yom Kippur' def test_succos(self): second_day = dates.HebrewDate(5782, 7, 16) day = dates.HebrewDate(5778, 7, 18) assert festival(day) == 'Succos' assert holiday(day, hebrew=True, prefix_day=True) == 'ד׳ סוכות' day2 = dates.HebrewDate(5778, 7, 23) assert festival(day2, israel=True, hebrew=True) is None assert festival(day, include_working_days=False) is None assert ( festival(second_day + 1, israel=True, include_working_days=False) is None ) assert ( festival(second_day, israel=False, include_working_days=False) == 'Succos' ) def test_shmini(self): shmini = dates.HebrewDate(5780, 7, 22) assert holiday(shmini, True) == 'Shmini Atzeres' assert holiday(shmini) == 'Shmini Atzeres' assert holiday(shmini + 1, prefix_day=True) == 'Simchas Torah' assert holiday(shmini + 1, israel=True, prefix_day=True) is None def test_chanuka(self): for year in [5778, 5787]: chanuka = dates.HebrewDate(year, 9, 25) assert ( festival(chanuka + 7, hebrew=True, prefix_day=True) == 'ח׳ חנוכה' ) for i in range(8): assert holiday(chanuka + i) == 'Chanuka' assert holiday(chanuka + 8) is None chanuka = dates.HebrewDate(5782, 9, 25) assert festival(chanuka, include_working_days=False) is None def test_tubshvat(self): tubeshvat = dates.HebrewDate(5779, 11, 15) assert holiday(tubeshvat) == "Tu B'shvat" assert festival(tubeshvat, include_working_days=False) is None def test_purim(self): purims = [dates.HebrewDate(5778, 12, 14), dates.HebrewDate(5779, 13, 14)] for purim in purims: assert holiday(purim, hebrew=True) == 'פורים' assert holiday(purim + 1) == 'Shushan Purim' assert holiday(dates.HebrewDate(5779, 12, 14)) == 'Purim Katan' for purim in purims: assert ( festival(purim, israel=True, include_working_days=False) is None ) def test_pesach(self): pesach = dates.HebrewDate(5778, 1, 15) for i in range(6): assert ( holiday(pesach + i, True) == 'Pesach' and holiday(pesach + i) == 'Pesach' ) eighth = pesach + 7 assert holiday(eighth) == 'Pesach' and holiday(eighth, True) is None assert holiday(eighth + 1) is None chol_hamoed = [pesach + i for i in range(2, 6)] for day in chol_hamoed: assert festival(day, include_working_days=False) is None assert ( festival(pesach + 1, israel=True, include_working_days=False) is None ) assert holiday(pesach, prefix_day=True) == '1 Pesach' assert ( festival(pesach + 2, include_working_days=False, prefix_day=True) is None ) def test_pesach_sheni(self): ps = dates.HebrewDate(5781, 2, 14) assert holiday(ps) == 'Pesach Sheni' assert holiday(ps + 1) is None assert festival(ps, include_working_days=False) is None def test_lagbaomer(self): lag_baomer = dates.GregorianDate(2018, 5, 3) assert festival(lag_baomer) == "Lag Ba'omer" assert festival(lag_baomer, hebrew=True) == 'ל״ג בעומר' assert ( festival(lag_baomer, hebrew=True, include_working_days=False) is None ) def test_shavuos(self): shavuos = dates.HebrewDate(5778, 3, 6) assert all( (holiday(day) == 'Shavuos' for day in [shavuos, shavuos + 1])) assert holiday(shavuos, True) == 'Shavuos' assert holiday(shavuos + 1, True) is None assert festival(shavuos + 1, include_working_days=False) == 'Shavuos' not_shavuos = dates.HebrewDate(5782, 4, 7) assert festival(not_shavuos) is None assert ( festival(shavuos + 1, hebrew=True, prefix_day=True) == 'ב׳ שבועות' ) def test_tubeav(self): tubeav = dates.HebrewDate(5779, 5, 15) assert holiday(tubeav) == "Tu B'av" assert festival(tubeav, include_working_days=False) is None class TestFasts: def test_gedalia(self): assert fast_day(dates.HebrewDate(5779, 7, 3)) == 'Tzom Gedalia' assert holiday(dates.HebrewDate(5778, 7, 3)) is None assert ( holiday(dates.HebrewDate(5778, 7, 4), hebrew=True) == 'צום גדליה' ) def test_asara(self): ten_of_teves = dates.GregorianDate(2018, 12, 18) assert holiday(ten_of_teves) == '10 of Teves' assert fast_day(ten_of_teves, hebrew=True) == 'י׳ בטבת' def test_esther(self): fasts = [ dates.HebrewDate(5778, 12, 13), dates.HebrewDate(5776, 13, 13), dates.HebrewDate(5777, 12, 11), # nidche dates.HebrewDate(5784, 13, 11) # ibbur and nidche ] for fast in fasts: assert holiday(fast) == 'Taanis Esther' non_fasts = [ dates.HebrewDate(5776, 12, 13), dates.HebrewDate(5777, 12, 13), dates.HebrewDate(5784, 12, 11), dates.HebrewDate(5784, 13, 13) ] for non in non_fasts: assert holiday(non) is None def test_tamuz(self): fasts = [dates.HebrewDate(5777, 4, 17), dates.HebrewDate(5778, 4, 18)] for fast in fasts: assert holiday(fast) == '17 of Tamuz' assert holiday(dates.HebrewDate(5778, 4, 17)) is None def test_av(self): fasts = [dates.HebrewDate(5777, 5, 9), dates.HebrewDate(5778, 5, 10)] for fast in fasts: assert holiday(fast) == '9 of Av' assert holiday(dates.HebrewDate(5778, 5, 9)) is None def test_to_hebrew_numeral(): assert hebrewcal.to_hebrew_numeral(5782) == 'תשפ״ב' @fixture def cal(): return hebrewcal.HebrewCalendar() class TestCalendar: def test_setfirstweekday(self, cal): cal.firstweekday = 2 assert cal.firstweekday == 2 assert cal._firstpyweekday == 0 cal.firstweekday = 1 def test_iterweekdays(self): for startingweekday in range(1, 8): cal = hebrewcal.HebrewCalendar(startingweekday) weekdays = list(cal.iterweekdays()) assert len(weekdays) == 7 assert weekdays[0] == startingweekday last = startingweekday - 1 or 7 assert weekdays[-1] == last def test_first_month(self, cal): list(cal.itermonthdates(1, 7)) def test_itermonthdates(self, cal): adar2 = list(cal.itermonthdates(5782, 13)) assert adar2[0] == dates.HebrewDate(5782, 12, 26) assert adar2[-1] == dates.HebrewDate(5782, 1, 1) def test_minyear(self, cal): list(cal.itermonthdates(1, 1)) def test_itermonthdays4(self, cal): nissan = list(cal.itermonthdays4(5782, 1)) assert nissan[0] == (5782, 13, 24, 1) assert nissan[-1] == (5782, 2, 6, 7) def test_itermonthdays(self): for firstweekday in range(1, 8): cal = hebrewcal.HebrewCalendar(firstweekday) for y, m in [(1, 7), (6000, 12)]: days = list(cal.itermonthdays(y, m)) assert len(days) in [35, 42] def test_itermonthdays2(self, cal): tishrei = list(cal.itermonthdays2(5783, 7)) assert tishrei[0] == (0, 1) assert tishrei[-1] == (0, 7) def test_yeardatescalendar(self, cal): year = cal.yeardatescalendar(5783, 4) assert len(year) == 3 assert len(year[1]) == 4 assert year[2][3][4][6] == dates.HebrewDate(5784, 7, 1) def test_yeardays2calendar(self, cal): year = cal.yeardays2calendar(5784) assert len(year) == 5 assert len(year[4]) == 1 assert year[1][2][5][6] == (0, 7) assert year[2][0][2][0] == (14, 1) def test_yeardayscalendar(self, cal): year = cal.yeardayscalendar(5784) assert year[2][0][2][0] == 14 def test_errors(self): with raises(hebrewcal.IllegalWeekdayError): hebrewcal.HebrewCalendar(0) with raises(hebrewcal.IllegalWeekdayError): hebrewcal.HebrewCalendar(8) def test_error_message(self): try: hebrewcal.HebrewCalendar(0) except hebrewcal.IllegalWeekdayError as e: assert str(e).startswith('bad weekday number 0;') try: Month(5781, 13) except hebrewcal.IllegalMonthError as e: assert str(e).startswith('bad month number 13;') PARSER = 'html.parser' def _soupbuilder(input, tag, class_): return BeautifulSoup(input, PARSER).find_all(tag, class_=class_) @fixture def htmlcal(): return HebrewHTMLCalendar() class TestHebrewHTMLCalendar: def test_formatday(self, htmlcal): assert htmlcal.formatday(0, 1) == ' ' assert htmlcal.formatday(1, 3) == 'א' assert htmlcal.formatday(15, 7) == 'טו' htmlcal.hebrewnumerals = False assert htmlcal.formatday(2, 2) == '2' assert htmlcal.formatday(21, 5) == '21' def test_length_in_months(self, htmlcal): months = _soupbuilder(htmlcal.formatyear(5784), 'table', 'month') assert len(months) == 13 months = _soupbuilder(htmlcal.formatyear(5783), 'table', 'month') assert len(months) == 12 def test_months(self, htmlcal): months = _soupbuilder(htmlcal.formatyear(5784), 'th', 'month') for i, month in enumerate(hebrewcal.Year(5784).itermonths()): assert months[i].string == month.month_name() htmlcal.hebrewmonths = True months = _soupbuilder(htmlcal.formatyear(5784), 'th', 'month') for i, month in enumerate(hebrewcal.Year(5784).itermonths()): assert months[i].string == month.month_name(True) def test_weekdays(self, htmlcal): year = htmlcal.formatyear(5783) sundays = _soupbuilder(year, 'th', 'sun') for sun in sundays: assert sun.string == calendar.day_abbr[6] htmlcal.hebrewweekdays = True mondays = _soupbuilder(htmlcal.formatyear(5782), 'th', 'mon') for mon in mondays: assert mon.string == 'שני' def test_days(self, htmlcal): year = htmlcal.formatyear(5782) soup = BeautifulSoup(year, PARSER) assert soup.find('td', class_='tue').string == 'א' assert soup.find_all('td', class_='tue')[2].string == 'טו' def test_year(self, htmlcal): month = htmlcal.formatmonth(5782, 2) header = BeautifulSoup(month, PARSER).find('th', class_='month') assert header.string == 'Iyar 5782' htmlcal.hebrewyear = True month = htmlcal.formatmonth(5782, 2) header = BeautifulSoup(month, PARSER).find('th', class_='month') assert header.string == 'Iyar תשפ״ב' def test_rtl(self, htmlcal): year = htmlcal.formatyear(5781) soup = BeautifulSoup(year, PARSER) assert soup.find('table', class_='year').get('dir', None) is None for month in soup.find_all('table', class_='month'): assert month.get('dir', None) is None htmlcal.rtl = True year = htmlcal.formatyear(5781) soup = BeautifulSoup(year, PARSER) assert soup.find('table', class_='year').get('dir', None) == 'rtl' for month in soup.find_all('table', class_='month'): assert month.get('dir', None) == 'rtl' @fixture def tcal(): return HebrewTextCalendar() class TestHebrewTextCalendar: def test_formatday(self, tcal): assert tcal.formatday(0, 1, 3) == ' ' assert tcal.formatday(2, 4, 4) == ' ב ' assert tcal.formatday(30, 5, 3) == ' ל' assert tcal.formatday(16, 7, 3) == ' טז' tcal.hebrewnumerals = False assert tcal.formatday(3, 2, 3) == ' 3' assert tcal.formatday(30, 7, 3) == ' 30' def test_formatweekday(self, tcal): py_tcal = calendar.TextCalendar(6) for w in [3, 10]: assert tcal.formatweekday(7, w) == py_tcal.formatweekday(5, w) tcal.hebrewweekdays = True assert tcal.formatweekday(2, 4) == ' ב׳ ' assert tcal.formatweekday(3, 5) == 'שלישי' def test_formatmonthname(self, tcal): assert tcal.formatmonthname(5782, 1) == 'Nissan 5782' assert tcal.formatmonthname(5784, 12) == 'Adar 1 5784' assert tcal.formatmonthname(5784, 12, withyear=False) == 'Adar 1' tcal.hebrewmonths = True tcal.hebrewyear = True assert tcal.formatmonthname(5784, 12) == 'אדר א׳ תשפ״ד' def test_formatyear(self, tcal): year = tcal.formatyear(5782) assert 'Adar 1' in year year = tcal.formatyear(5783) assert 'Adar 1' not in year pyluach-2.2.0/tests/test_parshios.py000066400000000000000000000050431437753445300176040ustar00rootroot00000000000000from pyluach import parshios, dates KNOWN_VALUES = { (2016, 1, 7): [13], (2017, 3, 21): [21, 22], (2017, 9, 26): None, (2020, 9, 19): None, } KNOWN_VALUES_STRINGS = { (2016, 1, 7): "Va'eira", (2017, 3, 21): "Vayakhel, Pekudei", (2017, 9, 26): None } class TestGetParsha: def test_getparsha(self): for key, value in KNOWN_VALUES.items(): assert parshios.getparsha(dates.GregorianDate(*key)) == value def test_getparsha_string(self): for key, value in KNOWN_VALUES_STRINGS.items(): assert ( parshios.getparsha_string(dates.GregorianDate(*key)) == value ) def test_chukas_balak(self): chukas_balak = dates.HebrewDate(5780, 4, 12) assert parshios.getparsha(chukas_balak) == [38, 39] assert parshios.getparsha(chukas_balak, True) == [39, ] assert parshios.getparsha(chukas_balak - 8) == [37, ] assert parshios.getparsha(chukas_balak - 13, True) == [38, ] shavuos = dates.HebrewDate(5780, 3, 6) assert parshios.getparsha_string(shavuos, True) == 'Nasso' assert parshios.getparsha_string(shavuos) is None assert parshios. getparsha_string(shavuos + 7, True) == "Beha'aloscha" assert parshios.getparsha_string(shavuos + 7) == 'Nasso' def test_eighth_day_pesach(self): eighth_day_pesach = dates.HebrewDate(5779, 1, 22) reunion_shabbos = dates.HebrewDate(5779, 5, 2) assert parshios.getparsha_string(eighth_day_pesach) is None assert ( parshios.getparsha_string(eighth_day_pesach, True) == 'Acharei Mos' ) assert parshios.getparsha(eighth_day_pesach + 7) == [28] assert parshios.getparsha(eighth_day_pesach + 7, True) == [29] assert parshios.getparsha_string(reunion_shabbos) == "Mattos, Masei" assert parshios.getparsha_string(reunion_shabbos, True) == 'Masei' def test_parshatable(): assert parshios.parshatable(5777) == parshios._gentable(5777) assert parshios.parshatable(5778, True) == parshios._gentable(5778, True) def test_iterparshios(): year = 5776 parshalist = list(parshios.parshatable(year).values()) index = 0 for p in parshios.iterparshios(year): assert p == parshalist[index] index += 1 def test_get_parshastring_hebrew(): date = dates.HebrewDate(5781, 3, 28) assert parshios.getparsha_string(date, hebrew=True) == 'קרח' date2 = dates.GregorianDate(2021, 7, 10) assert parshios.getparsha_string(date2, hebrew=True) == 'מטות, מסעי'