├── .nvmrc ├── .python-version ├── ocma_data ├── __init__.py ├── core │ ├── date │ │ ├── __init__.py │ │ ├── logic.py │ │ └── __main__.py │ ├── fasting │ │ ├── __init__.py │ │ ├── __main__.py │ │ ├── constants.py │ │ └── logic.py │ ├── pascha │ │ ├── __init__.py │ │ ├── logic.py │ │ └── __main__.py │ ├── weekday │ │ ├── __init__.py │ │ ├── logic.py │ │ └── __main__.py │ ├── lectionary │ │ ├── __init__.py │ │ ├── constants.py │ │ ├── logic.py │ │ └── __main__.py │ ├── moon_phase │ │ ├── __init__.py │ │ ├── logic.py │ │ └── __main__.py │ └── pascha_distance │ │ ├── __init__.py │ │ ├── logic.py │ │ └── __main__.py ├── __main__.py ├── constants.py ├── cli │ ├── constants.py │ └── main.py └── utils │ └── date_utils.py ├── .prettierignore ├── package.json ├── .gitignore ├── .github └── workflows │ ├── ruff.yml │ ├── prettier.yml │ ├── pytest.yml │ └── deploy.yml ├── tests └── core │ ├── test_pascha.py │ ├── test_moon_phase.py │ ├── test_weekday.py │ ├── test_date.py │ ├── test_pascha_distance.py │ ├── test_fasting_season_03_new.py │ ├── test_fasting_season_03_old.py │ ├── test_fasting_season_11_new.py │ ├── test_fasting_season_11_old.py │ ├── test_fasting_season_05_new.py │ ├── test_fasting_season_09_new.py │ ├── test_fasting_season_09_old.py │ ├── test_fasting_season_02_old.py │ ├── test_fasting_season_02_new.py │ ├── test_fasting_season_01_new.py │ ├── test_fasting_season_01_old.py │ ├── test_fasting_season_06_new.py │ ├── test_fasting_season_06_old.py │ ├── test_fasting_season_07_new.py │ ├── test_fasting_season_07_old.py │ ├── test_fasting_season_05_old.py │ ├── test_fasting_season_08_new.py │ ├── test_fasting_season_04_old.py │ ├── test_fasting_season_04_new.py │ ├── test_fasting_season_08_old.py │ ├── test_fasting_season_10_new.py │ └── test_fasting_season_10_old.py ├── LICENSE ├── pyproject.toml ├── README.md ├── public ├── index.html └── i18n │ ├── en.json │ ├── ru.json │ ├── ro.json │ └── el.json └── uv.lock /.nvmrc: -------------------------------------------------------------------------------- 1 | v24.11.1 2 | -------------------------------------------------------------------------------- /.python-version: -------------------------------------------------------------------------------- 1 | 3.11 2 | -------------------------------------------------------------------------------- /ocma_data/__init__.py: -------------------------------------------------------------------------------- 1 | """Package initialization file.""" 2 | -------------------------------------------------------------------------------- /ocma_data/core/date/__init__.py: -------------------------------------------------------------------------------- 1 | """Package initialization file.""" 2 | -------------------------------------------------------------------------------- /ocma_data/core/fasting/__init__.py: -------------------------------------------------------------------------------- 1 | """Package initialization file.""" 2 | -------------------------------------------------------------------------------- /ocma_data/core/pascha/__init__.py: -------------------------------------------------------------------------------- 1 | """Package initialization file.""" 2 | -------------------------------------------------------------------------------- /ocma_data/core/weekday/__init__.py: -------------------------------------------------------------------------------- 1 | """Package initialization file.""" 2 | -------------------------------------------------------------------------------- /ocma_data/core/lectionary/__init__.py: -------------------------------------------------------------------------------- 1 | """Package initialization file.""" 2 | -------------------------------------------------------------------------------- /ocma_data/core/moon_phase/__init__.py: -------------------------------------------------------------------------------- 1 | """Package initialization file.""" 2 | -------------------------------------------------------------------------------- /ocma_data/core/pascha_distance/__init__.py: -------------------------------------------------------------------------------- 1 | """Package initialization file.""" 2 | -------------------------------------------------------------------------------- /ocma_data/core/lectionary/constants.py: -------------------------------------------------------------------------------- 1 | """Constants defining Sunday Lectionary.""" 2 | -------------------------------------------------------------------------------- /ocma_data/core/lectionary/logic.py: -------------------------------------------------------------------------------- 1 | """Module for calculating Sunday Lectionary.""" 2 | -------------------------------------------------------------------------------- /ocma_data/core/lectionary/__main__.py: -------------------------------------------------------------------------------- 1 | """Subpackage for Sunday lectionary calculations.""" 2 | -------------------------------------------------------------------------------- /ocma_data/__main__.py: -------------------------------------------------------------------------------- 1 | """Main entry point for running the package as a CLI.""" 2 | 3 | from ocma_data.cli.main import main 4 | 5 | if __name__ == "__main__": 6 | main() 7 | -------------------------------------------------------------------------------- /.prettierignore: -------------------------------------------------------------------------------- 1 | node_modules/ 2 | public/data/ 3 | .git/ 4 | .pytest_cache/ 5 | .ruff_cache/ 6 | .venv/ 7 | LICENSE 8 | package-lock.json 9 | .gitignore 10 | .nvmrc 11 | .prettierignore -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "scripts": { 3 | "format": "prettier --check ." 4 | }, 5 | "type": "module", 6 | "devDependencies": { 7 | "prettier": "^3.7.3" 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Python-generated files 2 | __pycache__/ 3 | *.py[oc] 4 | build/ 5 | dist/ 6 | wheels/ 7 | *.egg-info 8 | ._* 9 | .ruff_cache/ 10 | utils/__pycache__/ 11 | 12 | # Virtual environments 13 | .venv 14 | 15 | .pytest_cache 16 | public/data/ 17 | node_modules/ -------------------------------------------------------------------------------- /.github/workflows/ruff.yml: -------------------------------------------------------------------------------- 1 | name: Ruff 2 | 3 | on: 4 | pull_request: 5 | branches: [main] 6 | 7 | jobs: 8 | ruff: 9 | runs-on: ubuntu-latest 10 | 11 | steps: 12 | - uses: actions/checkout@v6 13 | 14 | - uses: astral-sh/ruff-action@v3 15 | with: 16 | args: check 17 | 18 | - uses: astral-sh/ruff-action@v3 19 | with: 20 | args: format --check 21 | -------------------------------------------------------------------------------- /ocma_data/constants.py: -------------------------------------------------------------------------------- 1 | """Global constants.""" 2 | 3 | from enum import IntEnum, StrEnum 4 | 5 | 6 | class CalendarStyles(StrEnum): 7 | """Enumeration for Calendar Styles.""" 8 | 9 | OLD_CALENDAR = "old" 10 | NEW_CALENDAR = "new" 11 | 12 | 13 | class Weekdays(IntEnum): 14 | """Enumeration for Weekdays.""" 15 | 16 | MON = 1 17 | TUE = 2 18 | WED = 3 19 | THU = 4 20 | FRI = 5 21 | SAT = 6 22 | SUN = 7 23 | -------------------------------------------------------------------------------- /.github/workflows/prettier.yml: -------------------------------------------------------------------------------- 1 | name: Prettier 2 | 3 | on: 4 | pull_request: 5 | branches: [main] 6 | 7 | jobs: 8 | prettier: 9 | runs-on: ubuntu-latest 10 | 11 | steps: 12 | - uses: actions/checkout@v6 13 | 14 | - uses: actions/setup-node@v6 15 | with: 16 | node-version: 24 17 | cache: "npm" 18 | 19 | - run: npm ci 20 | 21 | - name: Run Prettier check 22 | run: npm run format 23 | -------------------------------------------------------------------------------- /.github/workflows/pytest.yml: -------------------------------------------------------------------------------- 1 | name: Pytest 2 | 3 | on: 4 | pull_request: 5 | branches: [main] 6 | 7 | jobs: 8 | test: 9 | runs-on: ubuntu-latest 10 | 11 | steps: 12 | - uses: actions/checkout@v6 13 | 14 | - name: Install uv 15 | uses: astral-sh/setup-uv@v7 16 | 17 | - name: Set up Python 18 | uses: actions/setup-python@v6 19 | with: 20 | python-version-file: "pyproject.toml" 21 | 22 | - name: Install the project 23 | run: uv sync --locked --all-extras --dev 24 | 25 | - name: Run tests 26 | run: uv run pytest 27 | -------------------------------------------------------------------------------- /ocma_data/core/pascha_distance/logic.py: -------------------------------------------------------------------------------- 1 | """Module providing a function that calculate the moon phase for a given date.""" 2 | 3 | from datetime import date 4 | 5 | from ocma_data.core.pascha.logic import calculate_pascha 6 | from ocma_data.utils.date_utils import string_to_date 7 | 8 | 9 | def pascha_distance(current_date: date, calendar_style: str) -> str: 10 | """Calculate the number of days between a given date and Pascha.""" 11 | pascha_date = string_to_date(calculate_pascha(current_date.year, calendar_style)) 12 | delta_date = current_date - pascha_date 13 | 14 | return str(delta_date.days) 15 | -------------------------------------------------------------------------------- /ocma_data/core/weekday/logic.py: -------------------------------------------------------------------------------- 1 | """Module providing a function that calculate the moon phase for a given date.""" 2 | 3 | from datetime import date 4 | 5 | from ocma_data.constants import CalendarStyles 6 | from ocma_data.utils.date_utils import modify_date 7 | 8 | 9 | def get_weekday(current_date: date, calendar_style: str) -> str: 10 | """Get the weekday (1-based index) for a given date.""" 11 | if calendar_style == CalendarStyles.OLD_CALENDAR.value: 12 | return str(modify_date(current_date, 13).isoweekday()) 13 | 14 | if calendar_style == CalendarStyles.NEW_CALENDAR.value: 15 | return str(current_date.isoweekday()) 16 | 17 | return "" 18 | -------------------------------------------------------------------------------- /tests/core/test_pascha.py: -------------------------------------------------------------------------------- 1 | """Test for pascha subpackage.""" 2 | 3 | import pytest 4 | 5 | from ocma_data.core.pascha.logic import calculate_pascha 6 | 7 | test_cases = [ 8 | (1923, "old", "1923-3-26"), 9 | (1923, "new", "1923-4-8"), 10 | (1924, "old", "1924-4-14"), 11 | (1924, "new", "1924-4-27"), 12 | (2010, "old", "2010-3-22"), 13 | (2010, "new", "2010-4-4"), 14 | (2025, "old", "2025-4-7"), 15 | (2025, "new", "2025-4-20"), 16 | (2078, "old", "2078-4-25"), 17 | (2078, "new", "2078-5-8"), 18 | (2099, "old", "2099-3-30"), 19 | (2099, "new", "2099-4-12"), 20 | ] 21 | 22 | 23 | @pytest.mark.parametrize("current_year, calendar_style, expected", test_cases) 24 | def test_calculate_pascha(current_year: int, calendar_style: str, expected: str) -> None: 25 | """Test for calculate_pascha.""" 26 | assert calculate_pascha(current_year, calendar_style) == expected 27 | -------------------------------------------------------------------------------- /ocma_data/core/date/logic.py: -------------------------------------------------------------------------------- 1 | """Module providing a function that calculate the moon phase for a given date.""" 2 | 3 | from datetime import date 4 | 5 | from ocma_data.constants import CalendarStyles 6 | from ocma_data.utils.date_utils import date_to_string, modify_date 7 | 8 | 9 | def get_date(current_date: date, calendar_style: str) -> dict[str, str]: 10 | """Convert a given date to its equivalent in both the Old and New Calendars.""" 11 | if calendar_style == CalendarStyles.OLD_CALENDAR.value: 12 | return { 13 | "date_old": f"{date_to_string(current_date)}", 14 | "date_new": f"{date_to_string(modify_date(current_date, 13))}", 15 | } 16 | 17 | if calendar_style == CalendarStyles.NEW_CALENDAR.value: 18 | return { 19 | "date_old": f"{date_to_string(modify_date(current_date, -13))}", 20 | "date_new": f"{date_to_string(current_date)}", 21 | } 22 | 23 | return {"date_old": "", "date_new": ""} 24 | -------------------------------------------------------------------------------- /tests/core/test_moon_phase.py: -------------------------------------------------------------------------------- 1 | """Test for moon_phase subpackage.""" 2 | 3 | import pytest 4 | 5 | from ocma_data.core.moon_phase.logic import get_moon_phase 6 | from ocma_data.utils.date_utils import string_to_date 7 | 8 | test_cases = [ 9 | ("1924-01-09", "old", "3"), 10 | ("1924-12-28", "old", "3"), 11 | ("2024-12-24", "old", "2"), 12 | ("2025-11-28", "old", "4"), 13 | ("2099-01-08", "old", "1"), 14 | ("2099-12-14", "old", "3"), 15 | ("2099-12-21", "old", "4"), 16 | ("2099-12-28", "old", "1"), 17 | ("1924-01-06", "new", "1"), 18 | ("1924-12-26", "new", "1"), 19 | ("2025-01-06", "new", "2"), 20 | ("2025-12-11", "new", "4"), 21 | ("2099-01-07", "new", "3"), 22 | ("2099-12-27", "new", "3"), 23 | ] 24 | 25 | 26 | @pytest.mark.parametrize("current_date, calendar_style, expected", test_cases) 27 | def test_get_moon_phase(current_date: str, calendar_style: str, expected: str) -> None: 28 | """Test for get_moon_phase.""" 29 | assert get_moon_phase(string_to_date(current_date), calendar_style) == expected 30 | -------------------------------------------------------------------------------- /ocma_data/cli/constants.py: -------------------------------------------------------------------------------- 1 | """Constants used in the CLI.""" 2 | 3 | from dataclasses import dataclass 4 | from enum import StrEnum 5 | 6 | 7 | @dataclass(frozen=True) 8 | class CLIConstants: 9 | """Immutable constants for CLI.""" 10 | 11 | BUILD_FOLDER: str = "public/data" 12 | PASCHALION: str = "paschalion" 13 | YEAR_END: int = 2100 14 | YEAR_START: int = 1924 15 | 16 | 17 | class JsonKeys(StrEnum): 18 | """Immutable JsonKeys constants.""" 19 | 20 | DATE_OLD = "date_old" 21 | DATE_NEW = "date_new" 22 | WEEKDAY_INDEX = "weekday_index" 23 | MOON_PHASE_INDEX = "moon_phase_index" 24 | PASCHA_DATE = "pascha_date" 25 | PASCHA_DISTANCE = "pascha_distance" 26 | FASTING_SEASON_INDEX = "fasting_season_index" 27 | FASTING_LAYMEN_INDEX = "fasting_laymen_index" 28 | FASTING_MONKS_INDEX = "fasting_monks_index" 29 | SUNDAY_DESCRIPTION_GR_INDEX = "sunday_description_gr_index" 30 | SUNDAY_DESCRIPTION_RO_INDEX = "sunday_description_ro_index" 31 | SUNDAY_DESCRIPTION_RU_INDEX = "sunday_description_ru_index" 32 | SUNDAY_LECTIONARY_INDEX = "sunday_lectionary_index" 33 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2025 ephmo 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /tests/core/test_weekday.py: -------------------------------------------------------------------------------- 1 | """Test for weekday subpackage.""" 2 | 3 | import pytest 4 | 5 | from ocma_data.core.weekday.logic import get_weekday 6 | from ocma_data.utils.date_utils import string_to_date 7 | 8 | test_cases = [ 9 | ("1924-01-01", "old", "1"), 10 | ("1924-01-01", "new", "2"), 11 | ("1924-12-31", "old", "2"), 12 | ("1924-12-31", "new", "3"), 13 | ("2025-04-21", "new", "1"), 14 | ("2025-04-04", "old", "4"), 15 | ("2025-04-24", "new", "4"), 16 | ("2025-04-05", "old", "5"), 17 | ("2025-04-25", "new", "5"), 18 | ("2025-04-06", "old", "6"), 19 | ("2025-04-26", "new", "6"), 20 | ("2025-04-07", "old", "7"), 21 | ("2025-04-27", "new", "7"), 22 | ("2099-01-01", "old", "3"), 23 | ("2099-01-01", "new", "4"), 24 | ("2099-12-31", "old", "3"), 25 | ("2099-12-31", "new", "4"), 26 | ] 27 | 28 | 29 | @pytest.mark.parametrize("current_date, calendar_style, expected", test_cases) 30 | def test_get_weekday(current_date: str, calendar_style: str, expected: str) -> None: 31 | """Test for get_weekday.""" 32 | assert get_weekday(string_to_date(current_date), calendar_style) == expected 33 | -------------------------------------------------------------------------------- /ocma_data/core/pascha/logic.py: -------------------------------------------------------------------------------- 1 | """Module providing a function to calculate the date of Pascha. 2 | 3 | This calculation uses the Jacques Oudin algorithm for the given year. 4 | """ 5 | 6 | from datetime import date 7 | 8 | from ocma_data.constants import CalendarStyles 9 | from ocma_data.utils.date_utils import date_to_string, modify_date 10 | 11 | 12 | def calculate_pascha(current_year: int, calendar_style: str) -> str: 13 | """Calculate the date of Pascha for the given year.""" 14 | golden_number = current_year % 19 15 | epact = (19 * golden_number + 15) % 30 16 | century_offset = (current_year + current_year // 4 + epact) % 7 17 | paschal_full_moon = epact - century_offset 18 | easter_month = 3 + (paschal_full_moon + 40) // 44 19 | easter_day = paschal_full_moon + 28 - 31 * (easter_month // 4) 20 | 21 | julian_pascha_date = date(current_year, easter_month, easter_day) 22 | 23 | if calendar_style == CalendarStyles.OLD_CALENDAR.value: 24 | return date_to_string(julian_pascha_date) 25 | 26 | if calendar_style == CalendarStyles.NEW_CALENDAR.value: 27 | return date_to_string(modify_date(julian_pascha_date, 13)) 28 | 29 | return "" 30 | -------------------------------------------------------------------------------- /pyproject.toml: -------------------------------------------------------------------------------- 1 | [project] 2 | name = "ocma-data" 3 | version = "0.7.16-alpha" 4 | description = "JSON data for each liturgical day of the year" 5 | readme = "README.md" 6 | requires-python = ">=3.11" 7 | dependencies = [] 8 | 9 | [dependency-groups] 10 | dev = ["pytest>=8.3.5", "ruff>=0.11.1"] 11 | 12 | [tool.ruff] 13 | line-length = 120 14 | indent-width = 4 15 | target-version = "py311" 16 | 17 | [tool.ruff.lint] 18 | select = [ 19 | "ARG", # unused arguments 20 | "B", # bugbear 21 | "C90", # mccabe complexity 22 | "D", # pydocstyle 23 | "E", # pycodestyle errors 24 | "F", # pyflakes 25 | "I", # isort 26 | "N", # pep8-naming 27 | "RUF", # ruff-specific rules 28 | "SIM", # simplify 29 | "UP", # pyupgrade 30 | "W", # pycodestyle warnings 31 | ] 32 | ignore = [ 33 | "D203", # one-blank-line-before-class (conflicts with D211) 34 | "D211", # no-blank-line-before-class (conflicts with D203) 35 | "D213", # multi-line-summary-second-line (conflicts with D212) 36 | ] 37 | 38 | [tool.ruff.format] 39 | quote-style = "double" 40 | indent-style = "space" 41 | 42 | [tool.pytest.ini_options] 43 | pythonpath = "." 44 | testpaths = ["tests"] 45 | -------------------------------------------------------------------------------- /.github/workflows/deploy.yml: -------------------------------------------------------------------------------- 1 | name: Deploy JSON API 2 | 3 | on: 4 | push: 5 | branches: [main] 6 | 7 | permissions: 8 | contents: read 9 | pages: write 10 | id-token: write 11 | 12 | jobs: 13 | build: 14 | runs-on: ubuntu-latest 15 | 16 | steps: 17 | - uses: actions/checkout@v6 18 | 19 | - uses: actions/configure-pages@v5 20 | 21 | - name: Install uv 22 | uses: astral-sh/setup-uv@v7 23 | 24 | - name: Set up Python 25 | uses: actions/setup-python@v6 26 | with: 27 | python-version-file: "pyproject.toml" 28 | 29 | - name: Install the project 30 | run: uv sync --locked --all-extras --dev 31 | 32 | - name: Build static files 33 | run: uv run -m ocma_data 34 | 35 | - name: Upload static files as artifact 36 | uses: actions/upload-pages-artifact@v4 37 | with: 38 | path: public/ 39 | 40 | deploy: 41 | needs: build 42 | 43 | runs-on: ubuntu-latest 44 | 45 | environment: 46 | name: github-pages 47 | url: ${{ steps.deployment.outputs.page_url }} 48 | 49 | steps: 50 | - name: Deploy to GitHub Pages 51 | id: deployment 52 | uses: actions/deploy-pages@v4 53 | -------------------------------------------------------------------------------- /tests/core/test_date.py: -------------------------------------------------------------------------------- 1 | """Test for date subpackage.""" 2 | 3 | import pytest 4 | 5 | from ocma_data.core.date.logic import get_date 6 | from ocma_data.utils.date_utils import string_to_date 7 | 8 | test_cases = [ 9 | ("1924-01-01", "old", {"date_old": "1924-1-1", "date_new": "1924-1-14"}), 10 | ("1924-01-01", "new", {"date_old": "1923-12-19", "date_new": "1924-1-1"}), 11 | ("1924-12-31", "old", {"date_old": "1924-12-31", "date_new": "1925-1-13"}), 12 | ("1924-12-31", "new", {"date_old": "1924-12-18", "date_new": "1924-12-31"}), 13 | ("2025-04-29", "old", {"date_old": "2025-4-29", "date_new": "2025-5-12"}), 14 | ("2025-04-29", "new", {"date_old": "2025-4-16", "date_new": "2025-4-29"}), 15 | ("2099-01-01", "old", {"date_old": "2099-1-1", "date_new": "2099-1-14"}), 16 | ("2099-01-01", "new", {"date_old": "2098-12-19", "date_new": "2099-1-1"}), 17 | ("2099-12-31", "old", {"date_old": "2099-12-31", "date_new": "2100-1-13"}), 18 | ("2099-12-31", "new", {"date_old": "2099-12-18", "date_new": "2099-12-31"}), 19 | ] 20 | 21 | 22 | @pytest.mark.parametrize("current_date, calendar_style, expected", test_cases) 23 | def test_get_date(current_date: str, calendar_style: str, expected: str) -> None: 24 | """Test for get_date.""" 25 | assert get_date(string_to_date(current_date), calendar_style) == expected 26 | -------------------------------------------------------------------------------- /tests/core/test_pascha_distance.py: -------------------------------------------------------------------------------- 1 | """Test for pascha_distance subpackage.""" 2 | 3 | import pytest 4 | 5 | from ocma_data.core.pascha_distance.logic import pascha_distance 6 | from ocma_data.utils.date_utils import string_to_date 7 | 8 | test_cases = [ 9 | ("1923-01-01", "old", "-84"), 10 | ("1923-01-01", "new", "-97"), 11 | ("1923-12-31", "old", "280"), 12 | ("1923-12-31", "new", "267"), 13 | ("1924-01-01", "old", "-104"), 14 | ("1924-01-01", "new", "-117"), 15 | ("1924-12-31", "old", "261"), 16 | ("1924-12-31", "new", "248"), 17 | ("2025-04-21", "new", "1"), 18 | ("2025-04-04", "old", "-3"), 19 | ("2025-04-24", "new", "4"), 20 | ("2025-04-05", "old", "-2"), 21 | ("2025-04-25", "new", "5"), 22 | ("2025-04-06", "old", "-1"), 23 | ("2025-04-26", "new", "6"), 24 | ("2025-04-07", "old", "0"), 25 | ("2025-04-27", "new", "7"), 26 | ("2099-01-01", "old", "-88"), 27 | ("2099-01-01", "new", "-101"), 28 | ("2099-12-31", "old", "276"), 29 | ("2099-12-31", "new", "263"), 30 | ] 31 | 32 | 33 | @pytest.mark.parametrize("current_date, calendar_style, expected", test_cases) 34 | def test_pascha_distance(current_date: str, calendar_style: str, expected: str) -> None: 35 | """Test for pascha_distance.""" 36 | assert pascha_distance(string_to_date(current_date), calendar_style) == expected 37 | -------------------------------------------------------------------------------- /ocma_data/core/date/__main__.py: -------------------------------------------------------------------------------- 1 | """Subpackage for calculating the date for Old and New Calendars.""" 2 | 3 | import argparse 4 | from datetime import date 5 | 6 | from ocma_data.constants import CalendarStyles 7 | from ocma_data.core.date.logic import get_date 8 | from ocma_data.utils.date_utils import string_to_date 9 | 10 | OLD = CalendarStyles.OLD_CALENDAR.value 11 | NEW = CalendarStyles.NEW_CALENDAR.value 12 | 13 | 14 | def valid_date(value: str) -> date: 15 | """Validate the given year.""" 16 | try: 17 | parsed_date = string_to_date(value) 18 | 19 | except ValueError as err: 20 | raise argparse.ArgumentTypeError(f"Invalid date format: '{value}'. Use YYYY-MM-DD.") from err 21 | return parsed_date 22 | 23 | 24 | def main(): 25 | """Parse arguments and print the date for Old and New Calendars.""" 26 | parser = argparse.ArgumentParser(description="Calculate the dates.") 27 | parser.add_argument( 28 | "-d", 29 | "--date", 30 | type=valid_date, 31 | required=True, 32 | help="Date to calculate according to the Old and New Calendars", 33 | ) 34 | parser.add_argument( 35 | "-c", 36 | "--calendar", 37 | choices=[OLD, NEW], 38 | required=True, 39 | help=f"Calendar style: '{OLD}' or '{NEW}'", 40 | ) 41 | args = parser.parse_args() 42 | 43 | print(get_date(args.date, args.calendar)) 44 | 45 | 46 | if __name__ == "__main__": 47 | main() 48 | -------------------------------------------------------------------------------- /ocma_data/core/weekday/__main__.py: -------------------------------------------------------------------------------- 1 | """Subpackage for calculating the weekday of a given date.""" 2 | 3 | import argparse 4 | from datetime import date 5 | 6 | from ocma_data.constants import CalendarStyles 7 | from ocma_data.core.weekday.logic import get_weekday 8 | from ocma_data.utils.date_utils import string_to_date 9 | 10 | OLD = CalendarStyles.OLD_CALENDAR.value 11 | NEW = CalendarStyles.NEW_CALENDAR.value 12 | 13 | 14 | def valid_date(value: str) -> date: 15 | """Validate the given year.""" 16 | try: 17 | parsed_date = string_to_date(value) 18 | 19 | except ValueError as err: 20 | raise argparse.ArgumentTypeError(f"Invalid date format: '{value}'. Use YYYY-MM-DD.") from err 21 | return parsed_date 22 | 23 | 24 | def main(): 25 | """Parse arguments and print the weekday for the given date and calendar.""" 26 | parser = argparse.ArgumentParser(description="Calculate the weekday.") 27 | parser.add_argument( 28 | "-d", 29 | "--date", 30 | type=valid_date, 31 | required=True, 32 | help="The date for which to calculate the weekday", 33 | ) 34 | parser.add_argument( 35 | "-c", 36 | "--calendar", 37 | choices=[OLD, NEW], 38 | required=True, 39 | help=f"Calendar style: '{OLD}' or '{NEW}'", 40 | ) 41 | args = parser.parse_args() 42 | 43 | print(get_weekday(args.date, args.calendar)) 44 | 45 | 46 | if __name__ == "__main__": 47 | main() 48 | -------------------------------------------------------------------------------- /ocma_data/core/pascha/__main__.py: -------------------------------------------------------------------------------- 1 | """Subpackage for Paschal date calculations.""" 2 | 3 | import argparse 4 | 5 | from ocma_data.cli.constants import CLIConstants 6 | from ocma_data.constants import CalendarStyles 7 | from ocma_data.core.pascha.logic import calculate_pascha 8 | 9 | YEAR_START = CLIConstants().YEAR_START 10 | YEAR_END = CLIConstants().YEAR_END - 1 11 | OLD = CalendarStyles.OLD_CALENDAR.value 12 | NEW = CalendarStyles.NEW_CALENDAR.value 13 | 14 | 15 | def valid_year(value: str) -> int: 16 | """Validate the given year.""" 17 | parsed_year = int(value) 18 | if not (YEAR_START - 1 <= parsed_year <= YEAR_END): 19 | raise argparse.ArgumentTypeError(f"Year must be between {YEAR_START - 1} and {YEAR_END}.") 20 | return parsed_year 21 | 22 | 23 | def main(): 24 | """Parse arguments and print the Paschal date for the given year and calendar.""" 25 | parser = argparse.ArgumentParser(description="Calculate the Paschal date.") 26 | parser.add_argument( 27 | "-y", 28 | "--year", 29 | type=valid_year, 30 | required=True, 31 | help=f"Year to calculate Pascha ({YEAR_START - 1} - {YEAR_END})", 32 | ) 33 | parser.add_argument( 34 | "-c", 35 | "--calendar", 36 | choices=[OLD, NEW], 37 | required=True, 38 | help=f"Calendar style: '{OLD}' or '{NEW}'", 39 | ) 40 | args = parser.parse_args() 41 | 42 | print(calculate_pascha(args.year, args.calendar)) 43 | 44 | 45 | if __name__ == "__main__": 46 | main() 47 | -------------------------------------------------------------------------------- /ocma_data/core/moon_phase/logic.py: -------------------------------------------------------------------------------- 1 | """Module providing a function that calculate the moon phase for a given date.""" 2 | 3 | from datetime import date 4 | 5 | from ocma_data.constants import CalendarStyles 6 | from ocma_data.core.moon_phase.constants import MOON_PHASES 7 | from ocma_data.utils.date_utils import modify_date 8 | 9 | 10 | def get_moon_phase(current_date: date, calendar_style: str) -> str: 11 | """Calculate the moon phase for a given date.""" 12 | 13 | def find_moon_phase( 14 | modified_year: int, 15 | modified_month: int, 16 | modified_day: int, 17 | ) -> str: 18 | """Determine the moon phase for a given date.""" 19 | phase_map = { 20 | "new_moon": "1", 21 | "first_quarter": "2", 22 | "full_moon": "3", 23 | "last_quarter": "4", 24 | } 25 | date_key = f"{modified_month}-{modified_day}" 26 | 27 | for phase, value in phase_map.items(): 28 | if date_key in MOON_PHASES.get(str(modified_year), {}).get(phase, []): 29 | return value 30 | 31 | return "" 32 | 33 | if calendar_style == CalendarStyles.OLD_CALENDAR.value: 34 | modified_date = modify_date(current_date, 13) 35 | return find_moon_phase( 36 | modified_date.year, 37 | modified_date.month, 38 | modified_date.day, 39 | ) 40 | 41 | if calendar_style == CalendarStyles.NEW_CALENDAR.value: 42 | return find_moon_phase( 43 | current_date.year, 44 | current_date.month, 45 | current_date.day, 46 | ) 47 | 48 | return "" 49 | -------------------------------------------------------------------------------- /ocma_data/core/fasting/__main__.py: -------------------------------------------------------------------------------- 1 | """Subpackage for fasting rules calculations.""" 2 | 3 | import argparse 4 | from datetime import date 5 | 6 | from ocma_data.cli.constants import CLIConstants 7 | from ocma_data.constants import CalendarStyles 8 | from ocma_data.core.fasting.logic import get_fasting 9 | from ocma_data.utils.date_utils import string_to_date 10 | 11 | YEAR_START = CLIConstants().YEAR_START 12 | YEAR_END = CLIConstants().YEAR_END - 1 13 | OLD = CalendarStyles.OLD_CALENDAR.value 14 | NEW = CalendarStyles.NEW_CALENDAR.value 15 | 16 | 17 | def valid_date(value: str) -> date: 18 | """Validate the given year.""" 19 | try: 20 | parsed_date = string_to_date(value) 21 | 22 | except ValueError as err: 23 | raise argparse.ArgumentTypeError(f"Invalid date format: '{value}'. Use YYYY-MM-DD.") from err 24 | 25 | if not (YEAR_START <= parsed_date.year <= YEAR_END): 26 | raise argparse.ArgumentTypeError(f"Year must be between {YEAR_START} and {YEAR_END}.") 27 | return parsed_date 28 | 29 | 30 | def main(): 31 | """Parse arguments and print the fasting rules for the given date and calendar.""" 32 | parser = argparse.ArgumentParser(description="Calculate the fasting rules.") 33 | parser.add_argument( 34 | "-d", 35 | "--date", 36 | type=valid_date, 37 | required=True, 38 | help="Date to calculate the fasting rules", 39 | ) 40 | parser.add_argument( 41 | "-c", 42 | "--calendar", 43 | choices=[OLD, NEW], 44 | required=True, 45 | help=f"Calendar style: '{OLD}' or '{NEW}'", 46 | ) 47 | args = parser.parse_args() 48 | 49 | print(get_fasting(args.date, args.calendar)) 50 | 51 | 52 | if __name__ == "__main__": 53 | main() 54 | -------------------------------------------------------------------------------- /ocma_data/core/moon_phase/__main__.py: -------------------------------------------------------------------------------- 1 | """Subpackage for moon phase calculations.""" 2 | 3 | import argparse 4 | from datetime import date 5 | 6 | from ocma_data.cli.constants import CLIConstants 7 | from ocma_data.constants import CalendarStyles 8 | from ocma_data.core.moon_phase.logic import get_moon_phase 9 | from ocma_data.utils.date_utils import string_to_date 10 | 11 | YEAR_START = CLIConstants().YEAR_START 12 | YEAR_END = CLIConstants().YEAR_END - 1 13 | OLD = CalendarStyles.OLD_CALENDAR.value 14 | NEW = CalendarStyles.NEW_CALENDAR.value 15 | 16 | 17 | def valid_date(value: str) -> date: 18 | """Validate the given year.""" 19 | try: 20 | parsed_date = string_to_date(value) 21 | 22 | except ValueError as err: 23 | raise argparse.ArgumentTypeError(f"Invalid date format: '{value}'. Use YYYY-MM-DD.") from err 24 | 25 | if not (YEAR_START <= parsed_date.year <= YEAR_END): 26 | raise argparse.ArgumentTypeError(f"Year must be between {YEAR_START} and {YEAR_END}.") 27 | return parsed_date 28 | 29 | 30 | def main(): 31 | """Parse arguments and print the moon phase for the given date and calendar.""" 32 | parser = argparse.ArgumentParser(description="Calculate the moon phase date.") 33 | parser.add_argument( 34 | "-d", 35 | "--date", 36 | type=valid_date, 37 | required=True, 38 | help="Date to calculate the moon phase", 39 | ) 40 | parser.add_argument( 41 | "-c", 42 | "--calendar", 43 | choices=[OLD, NEW], 44 | required=True, 45 | help=f"Calendar style: '{OLD}' or '{NEW}'", 46 | ) 47 | args = parser.parse_args() 48 | 49 | print(get_moon_phase(args.date, args.calendar)) 50 | 51 | 52 | if __name__ == "__main__": 53 | main() 54 | -------------------------------------------------------------------------------- /ocma_data/core/pascha_distance/__main__.py: -------------------------------------------------------------------------------- 1 | """Subpackage for calculating the distance from Pascha.""" 2 | 3 | import argparse 4 | from datetime import date 5 | 6 | from ocma_data.cli.constants import CLIConstants 7 | from ocma_data.constants import CalendarStyles 8 | from ocma_data.core.pascha_distance.logic import pascha_distance 9 | from ocma_data.utils.date_utils import string_to_date 10 | 11 | YEAR_START = CLIConstants().YEAR_START 12 | YEAR_END = CLIConstants().YEAR_END - 1 13 | OLD = CalendarStyles.OLD_CALENDAR.value 14 | NEW = CalendarStyles.NEW_CALENDAR.value 15 | 16 | 17 | def valid_date(value: str) -> date: 18 | """Validate the given year.""" 19 | try: 20 | parsed_date = string_to_date(value) 21 | 22 | except ValueError as err: 23 | raise argparse.ArgumentTypeError(f"Invalid date format: '{value}'. Use YYYY-MM-DD.") from err 24 | 25 | if not (YEAR_START - 1 <= parsed_date.year <= YEAR_END): 26 | raise argparse.ArgumentTypeError(f"Year must be between {YEAR_START - 1} and {YEAR_END}.") 27 | return parsed_date 28 | 29 | 30 | def main(): 31 | """Parse arguments and print the days between a date and Pascha.""" 32 | parser = argparse.ArgumentParser(description="Days between given date and Pascha.") 33 | parser.add_argument( 34 | "-d", 35 | "--date", 36 | type=valid_date, 37 | required=True, 38 | help="The date for which to calculate the Pascha distance", 39 | ) 40 | parser.add_argument( 41 | "-c", 42 | "--calendar", 43 | choices=[OLD, NEW], 44 | required=True, 45 | help=f"Calendar style: '{OLD}' or '{NEW}'", 46 | ) 47 | args = parser.parse_args() 48 | 49 | print(pascha_distance(args.date, args.calendar)) 50 | 51 | 52 | if __name__ == "__main__": 53 | main() 54 | -------------------------------------------------------------------------------- /ocma_data/utils/date_utils.py: -------------------------------------------------------------------------------- 1 | """Utility functions for date manipulation, validation, comparison, and formatting.""" 2 | 3 | from datetime import date, datetime, timedelta 4 | 5 | 6 | def date_to_string(current_date: date) -> str: 7 | """Convert a given date into a string.""" 8 | return current_date.strftime("%Y-%m-%d").replace("-0", "-") 9 | 10 | 11 | def is_date_before(current_date: date, comparison_date: date) -> bool: 12 | """Check if the given date comes before another specified date.""" 13 | return current_date < comparison_date 14 | 15 | 16 | def is_date_in_range(current_date: date, start_date: date, end_date: date) -> bool: 17 | """Check if the given date is within the specified range.""" 18 | if start_date.year > end_date.year: 19 | return False 20 | 21 | if start_date.year < end_date.year: 22 | return start_date.year <= current_date.year < end_date.year 23 | 24 | return start_date <= current_date <= end_date 25 | 26 | 27 | def is_date_in_tuple(month_day: str, comparison_tuple: tuple[str, ...]) -> bool: 28 | """Check if the given date is in a given tuple.""" 29 | return month_day in comparison_tuple 30 | 31 | 32 | def is_valid_date(current_year: int, current_month: int, current_day: int) -> bool: 33 | """Check if the provided date string is a valid date.""" 34 | try: 35 | date(current_year, current_month, current_day) 36 | except ValueError: 37 | return False 38 | else: 39 | return True 40 | 41 | 42 | def modify_date(current_date: date, number_days: int) -> date: 43 | """Modify a given date by adding or subtracting a number of days.""" 44 | return current_date + timedelta(days=number_days) 45 | 46 | 47 | def string_to_date(current_date: str) -> date: 48 | """Convert a given string into a date.""" 49 | return datetime.strptime(current_date, "%Y-%m-%d").date() 50 | -------------------------------------------------------------------------------- /tests/core/test_fasting_season_03_new.py: -------------------------------------------------------------------------------- 1 | """Test for fasting subpackage.""" 2 | 3 | import pytest 4 | 5 | from ocma_data.core.fasting.logic import get_fasting 6 | from ocma_data.utils.date_utils import string_to_date 7 | 8 | test_cases = [ 9 | # Maximum range for each fasting season 10 | # Format: ["Calendar", "Start Month-Day", "End Month-Day"] 11 | # Fasting season 3: ["New Calendar", "2-15", "3-22"] 12 | ( 13 | "2010-2-15", 14 | "new", 15 | { 16 | "fasting_season_index": "3", 17 | "fasting_laymen_index": "6", 18 | "fasting_monks_index": "6", 19 | }, 20 | ), # Monday 21 | ( 22 | "2010-2-16", 23 | "new", 24 | { 25 | "fasting_season_index": "3", 26 | "fasting_laymen_index": "6", 27 | "fasting_monks_index": "6", 28 | }, 29 | ), # Tuesday 30 | ( 31 | "2041-3-4", 32 | "new", 33 | { 34 | "fasting_season_index": "3", 35 | "fasting_laymen_index": "6", 36 | "fasting_monks_index": "6", 37 | }, 38 | ), # Monday 39 | ( 40 | "2041-3-5", 41 | "new", 42 | { 43 | "fasting_season_index": "3", 44 | "fasting_laymen_index": "6", 45 | "fasting_monks_index": "6", 46 | }, 47 | ), # Tuesday 48 | ( 49 | "2078-3-21", 50 | "new", 51 | { 52 | "fasting_season_index": "3", 53 | "fasting_laymen_index": "6", 54 | "fasting_monks_index": "6", 55 | }, 56 | ), # Monday 57 | ( 58 | "2078-3-22", 59 | "new", 60 | { 61 | "fasting_season_index": "3", 62 | "fasting_laymen_index": "6", 63 | "fasting_monks_index": "6", 64 | }, 65 | ), # Tuesday 66 | ] 67 | 68 | 69 | @pytest.mark.parametrize("current_date, calendar_style, expected", test_cases) 70 | def test_get_fasting(current_date: str, calendar_style: str, expected: str) -> None: 71 | """Test for get_fasting.""" 72 | assert get_fasting(string_to_date(current_date), calendar_style) == expected 73 | -------------------------------------------------------------------------------- /tests/core/test_fasting_season_03_old.py: -------------------------------------------------------------------------------- 1 | """Test for fasting subpackage.""" 2 | 3 | import pytest 4 | 5 | from ocma_data.core.fasting.logic import get_fasting 6 | from ocma_data.utils.date_utils import string_to_date 7 | 8 | test_cases = [ 9 | # Maximum range for each fasting season 10 | # Format: ["Calendar", "Start Month-Day", "End Month-Day"] 11 | # Fasting season 3: ["Old Calendar", "2-2", "3-9"] 12 | ( 13 | "2010-2-2", 14 | "old", 15 | { 16 | "fasting_season_index": "3", 17 | "fasting_laymen_index": "5", 18 | "fasting_monks_index": "5", 19 | }, 20 | ), # FISH_ALLOWED, Monday 21 | ( 22 | "2010-2-3", 23 | "old", 24 | { 25 | "fasting_season_index": "3", 26 | "fasting_laymen_index": "6", 27 | "fasting_monks_index": "6", 28 | }, 29 | ), # Tuesday 30 | ( 31 | "2041-2-19", 32 | "old", 33 | { 34 | "fasting_season_index": "3", 35 | "fasting_laymen_index": "6", 36 | "fasting_monks_index": "6", 37 | }, 38 | ), # Monday 39 | ( 40 | "2041-2-20", 41 | "old", 42 | { 43 | "fasting_season_index": "3", 44 | "fasting_laymen_index": "6", 45 | "fasting_monks_index": "6", 46 | }, 47 | ), # Tuesday 48 | ( 49 | "2078-3-8", 50 | "old", 51 | { 52 | "fasting_season_index": "3", 53 | "fasting_laymen_index": "6", 54 | "fasting_monks_index": "6", 55 | }, 56 | ), # Monday 57 | ( 58 | "2078-3-9", 59 | "old", 60 | { 61 | "fasting_season_index": "3", 62 | "fasting_laymen_index": "6", 63 | "fasting_monks_index": "6", 64 | }, 65 | ), # Tuesday 66 | ] 67 | 68 | 69 | @pytest.mark.parametrize("current_date, calendar_style, expected", test_cases) 70 | def test_get_fasting(current_date: str, calendar_style: str, expected: str) -> None: 71 | """Test for get_fasting.""" 72 | assert get_fasting(string_to_date(current_date), calendar_style) == expected 73 | -------------------------------------------------------------------------------- /tests/core/test_fasting_season_11_new.py: -------------------------------------------------------------------------------- 1 | """Test for fasting subpackage.""" 2 | 3 | import pytest 4 | 5 | from ocma_data.core.fasting.logic import get_fasting 6 | from ocma_data.utils.date_utils import string_to_date 7 | 8 | test_cases = [ 9 | # Maximum range for each fasting season 10 | # Format: ["Calendar", "Start Month-Day", "End Month-Day"] 11 | # Fasting season 11: ["New Calendar", "12-25", "1-6"] 12 | ( 13 | "1924-12-25", 14 | "new", 15 | { 16 | "fasting_season_index": "11", 17 | "fasting_laymen_index": "1", 18 | "fasting_monks_index": "2", 19 | }, 20 | ), # Thursday 21 | ( 22 | "1924-1-5", 23 | "new", 24 | { 25 | "fasting_season_index": "11", 26 | "fasting_laymen_index": "4", 27 | "fasting_monks_index": "4", 28 | }, 29 | ), # STRICT_FAST, Saturday 30 | ( 31 | "1924-1-6", 32 | "new", 33 | { 34 | "fasting_season_index": "11", 35 | "fasting_laymen_index": "1", 36 | "fasting_monks_index": "2", 37 | }, 38 | ), # Sunday 39 | ( 40 | "2099-12-25", 41 | "new", 42 | { 43 | "fasting_season_index": "11", 44 | "fasting_laymen_index": "1", 45 | "fasting_monks_index": "2", 46 | }, 47 | ), # Friday 48 | ( 49 | "2099-1-5", 50 | "new", 51 | { 52 | "fasting_season_index": "11", 53 | "fasting_laymen_index": "5", 54 | "fasting_monks_index": "5", 55 | }, 56 | ), # STRICT_FAST, Monday 57 | ( 58 | "2099-1-6", 59 | "new", 60 | { 61 | "fasting_season_index": "11", 62 | "fasting_laymen_index": "1", 63 | "fasting_monks_index": "2", 64 | }, 65 | ), # Tuesday 66 | ] 67 | 68 | 69 | @pytest.mark.parametrize("current_date, calendar_style, expected", test_cases) 70 | def test_get_fasting(current_date: str, calendar_style: str, expected: str) -> None: 71 | """Test for get_fasting.""" 72 | assert get_fasting(string_to_date(current_date), calendar_style) == expected 73 | -------------------------------------------------------------------------------- /tests/core/test_fasting_season_11_old.py: -------------------------------------------------------------------------------- 1 | """Test for fasting subpackage.""" 2 | 3 | import pytest 4 | 5 | from ocma_data.core.fasting.logic import get_fasting 6 | from ocma_data.utils.date_utils import string_to_date 7 | 8 | test_cases = [ 9 | # Maximum range for each fasting season 10 | # Format: ["Calendar", "Start Month-Day", "End Month-Day"] 11 | # Fasting season 11: ["Old Calendar", "12-25", "1-6"] 12 | ( 13 | "1924-12-25", 14 | "old", 15 | { 16 | "fasting_season_index": "11", 17 | "fasting_laymen_index": "1", 18 | "fasting_monks_index": "2", 19 | }, 20 | ), # Wednesday 21 | ( 22 | "1924-1-5", 23 | "old", 24 | { 25 | "fasting_season_index": "11", 26 | "fasting_laymen_index": "5", 27 | "fasting_monks_index": "5", 28 | }, 29 | ), # STRICT_FAST, Friday 30 | ( 31 | "1924-1-6", 32 | "old", 33 | { 34 | "fasting_season_index": "11", 35 | "fasting_laymen_index": "1", 36 | "fasting_monks_index": "2", 37 | }, 38 | ), # Saturday 39 | ( 40 | "2099-12-25", 41 | "old", 42 | { 43 | "fasting_season_index": "11", 44 | "fasting_laymen_index": "1", 45 | "fasting_monks_index": "2", 46 | }, 47 | ), # Thursday 48 | ( 49 | "2099-1-5", 50 | "old", 51 | { 52 | "fasting_season_index": "11", 53 | "fasting_laymen_index": "4", 54 | "fasting_monks_index": "4", 55 | }, 56 | ), # STRICT_FAST, Sunday 57 | ( 58 | "2099-1-6", 59 | "old", 60 | { 61 | "fasting_season_index": "11", 62 | "fasting_laymen_index": "1", 63 | "fasting_monks_index": "2", 64 | }, 65 | ), # Monday 66 | ] 67 | 68 | 69 | @pytest.mark.parametrize("current_date, calendar_style, expected", test_cases) 70 | def test_get_fasting(current_date: str, calendar_style: str, expected: str) -> None: 71 | """Test for get_fasting.""" 72 | assert get_fasting(string_to_date(current_date), calendar_style) == expected 73 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # OCMA-Data 2 | 3 | ![Stage](https://img.shields.io/badge/stage-alpha-orange) 4 | ![Build](https://img.shields.io/github/actions/workflow/status/ephmo/ocma-data/deploy.yml) 5 | ![License](https://img.shields.io/github/license/ephmo/ocma-data) 6 | 7 | ## Overview 8 | 9 | **OCMA-Data** is a structured **Orthodox Calendar** dataset that provides JSON files for each liturgical day of the year, supporting both the **Old Calendar (Julian)** and the **New Calendar (Revised Julian)** from 1924 to 2099. This dataset serves as the foundation for the [OCMA](https://github.com/ephmo/ocma) application, offering comprehensive liturgical details. 10 | 11 | ## Features 12 | 13 | - **Feasts**: Movable and Fixed Feasts. 14 | - **Saints**: Common and Additional Saints. 15 | - **Fasting**: Information on Fasting Seasons and Levels. 16 | - **Lectionary**: Tone, Matins Gospel, Epistle and Gospel for each Sunday. 17 | - **Paschalion**: Pascha date for each year. 18 | - **Moon Phases**: Primary lunar phases relevant to the Calendar. 19 | 20 | ## Multilingual Support & Translation-Friendly Structure 21 | 22 | **OCMA-Data** is available in **English, Greek, Romanian and Russian**, with each language stored in **separate JSON files**. This separation ensures a clear distinction between **liturgical logic** and **language**, allowing easy expansion to additional languages in the future. 23 | 24 | ## Usage 25 | 26 | The JSON files provide structured and reusable data, making **OCMA-Data** a valuable resource for: 27 | 28 | - **Developers** building applications with Orthodox liturgical content. 29 | - **Researchers** studying Orthodox calendars and feast calculations. 30 | - **Faithful** who want easy access to detailed liturgical information. 31 | 32 | **OCMA-Data** provides a structured and reusable resource for developers, researchers, and Orthodox faithful, facilitating access to comprehensive liturgical information. 33 | 34 | ## Contributing 35 | 36 | Contributions are welcome! If you wish to add additional languages or improve the dataset, feel free to submit a pull request. 37 | 38 | ## License 39 | 40 | This application is licensed under the MIT License - see the [LICENSE](LICENSE) file for details. 41 | 42 | ## Reporting Issues 43 | 44 | If you encounter any issues or have suggestions for improvements, please [create an issue](https://github.com/ephmo/ocma-data/issues) on our issue tracker. We appreciate your feedback! 45 | -------------------------------------------------------------------------------- /tests/core/test_fasting_season_05_new.py: -------------------------------------------------------------------------------- 1 | """Test for fasting subpackage.""" 2 | 3 | import pytest 4 | 5 | from ocma_data.core.fasting.logic import get_fasting 6 | from ocma_data.utils.date_utils import string_to_date 7 | 8 | test_cases = [ 9 | # Maximum range for each fasting season 10 | # Format: ["Calendar", "Start Month-Day", "End Month-Day"] 11 | # Fasting season 5: ["New Calendar", "3-29", "5-7"] 12 | ( 13 | "2010-3-29", 14 | "new", 15 | { 16 | "fasting_season_index": "5", 17 | "fasting_laymen_index": "5", 18 | "fasting_monks_index": "5", 19 | }, 20 | ), # Monday 21 | ( 22 | "2010-3-30", 23 | "new", 24 | { 25 | "fasting_season_index": "5", 26 | "fasting_laymen_index": "5", 27 | "fasting_monks_index": "5", 28 | }, 29 | ), # Tuesday 30 | ( 31 | "2010-3-31", 32 | "new", 33 | { 34 | "fasting_season_index": "5", 35 | "fasting_laymen_index": "5", 36 | "fasting_monks_index": "5", 37 | }, 38 | ), # Wednesday 39 | ( 40 | "2010-4-1", 41 | "new", 42 | { 43 | "fasting_season_index": "5", 44 | "fasting_laymen_index": "5", 45 | "fasting_monks_index": "5", 46 | }, 47 | ), # Thursday 48 | ( 49 | "2010-4-2", 50 | "new", 51 | { 52 | "fasting_season_index": "5", 53 | "fasting_laymen_index": "6", 54 | "fasting_monks_index": "6", 55 | }, 56 | ), # Friday 57 | ( 58 | "2010-4-3", 59 | "new", 60 | { 61 | "fasting_season_index": "5", 62 | "fasting_laymen_index": "5", 63 | "fasting_monks_index": "5", 64 | }, 65 | ), # Saturday 66 | ( 67 | "2041-4-15", 68 | "new", 69 | { 70 | "fasting_season_index": "5", 71 | "fasting_laymen_index": "5", 72 | "fasting_monks_index": "5", 73 | }, 74 | ), # Monday 75 | ( 76 | "2041-4-20", 77 | "new", 78 | { 79 | "fasting_season_index": "5", 80 | "fasting_laymen_index": "5", 81 | "fasting_monks_index": "5", 82 | }, 83 | ), # Saturday 84 | ( 85 | "2078-5-2", 86 | "new", 87 | { 88 | "fasting_season_index": "5", 89 | "fasting_laymen_index": "5", 90 | "fasting_monks_index": "5", 91 | }, 92 | ), # Monday 93 | ( 94 | "2078-5-7", 95 | "new", 96 | { 97 | "fasting_season_index": "5", 98 | "fasting_laymen_index": "5", 99 | "fasting_monks_index": "5", 100 | }, 101 | ), # Saturday 102 | ] 103 | 104 | 105 | @pytest.mark.parametrize("current_date, calendar_style, expected", test_cases) 106 | def test_get_fasting(current_date: str, calendar_style: str, expected: str) -> None: 107 | """Test for get_fasting.""" 108 | assert get_fasting(string_to_date(current_date), calendar_style) == expected 109 | -------------------------------------------------------------------------------- /tests/core/test_fasting_season_09_new.py: -------------------------------------------------------------------------------- 1 | """Test for fasting subpackage.""" 2 | 3 | import pytest 4 | 5 | from ocma_data.core.fasting.logic import get_fasting 6 | from ocma_data.utils.date_utils import string_to_date 7 | 8 | test_cases = [ 9 | # Maximum range for each fasting season 10 | # Format: ["Calendar", "Start Month-Day", "End Month-Day"] 11 | # Fasting season 9: ["New Calendar", "8-1", "8-14"] 12 | ( 13 | "1924-8-1", 14 | "new", 15 | { 16 | "fasting_season_index": "9", 17 | "fasting_laymen_index": "5", 18 | "fasting_monks_index": "5", 19 | }, 20 | ), # Friday 21 | ( 22 | "1924-8-3", 23 | "new", 24 | { 25 | "fasting_season_index": "9", 26 | "fasting_laymen_index": "4", 27 | "fasting_monks_index": "4", 28 | }, 29 | ), # Sunday 30 | ( 31 | "1924-8-6", 32 | "new", 33 | { 34 | "fasting_season_index": "9", 35 | "fasting_laymen_index": "3", 36 | "fasting_monks_index": "3", 37 | }, 38 | ), # FISH_ALLOWED, Wednesday 39 | ( 40 | "1924-8-14", 41 | "new", 42 | { 43 | "fasting_season_index": "9", 44 | "fasting_laymen_index": "5", 45 | "fasting_monks_index": "5", 46 | }, 47 | ), # Thursday 48 | ( 49 | "2099-8-1", 50 | "new", 51 | { 52 | "fasting_season_index": "9", 53 | "fasting_laymen_index": "4", 54 | "fasting_monks_index": "4", 55 | }, 56 | ), # Saturday 57 | ( 58 | "2099-8-3", 59 | "new", 60 | { 61 | "fasting_season_index": "9", 62 | "fasting_laymen_index": "5", 63 | "fasting_monks_index": "5", 64 | }, 65 | ), # Monday 66 | ( 67 | "2099-8-4", 68 | "new", 69 | { 70 | "fasting_season_index": "9", 71 | "fasting_laymen_index": "5", 72 | "fasting_monks_index": "5", 73 | }, 74 | ), # Tuesday 75 | ( 76 | "2099-8-5", 77 | "new", 78 | { 79 | "fasting_season_index": "9", 80 | "fasting_laymen_index": "5", 81 | "fasting_monks_index": "5", 82 | }, 83 | ), # Wednesday 84 | ( 85 | "2099-8-6", 86 | "new", 87 | { 88 | "fasting_season_index": "9", 89 | "fasting_laymen_index": "3", 90 | "fasting_monks_index": "3", 91 | }, 92 | ), # FISH_ALLOWED, Thursday 93 | ( 94 | "2099-8-14", 95 | "new", 96 | { 97 | "fasting_season_index": "9", 98 | "fasting_laymen_index": "5", 99 | "fasting_monks_index": "5", 100 | }, 101 | ), # Friday 102 | ] 103 | 104 | 105 | @pytest.mark.parametrize("current_date, calendar_style, expected", test_cases) 106 | def test_get_fasting(current_date: str, calendar_style: str, expected: str) -> None: 107 | """Test for get_fasting.""" 108 | assert get_fasting(string_to_date(current_date), calendar_style) == expected 109 | -------------------------------------------------------------------------------- /tests/core/test_fasting_season_09_old.py: -------------------------------------------------------------------------------- 1 | """Test for fasting subpackage.""" 2 | 3 | import pytest 4 | 5 | from ocma_data.core.fasting.logic import get_fasting 6 | from ocma_data.utils.date_utils import string_to_date 7 | 8 | test_cases = [ 9 | # Maximum range for each fasting season 10 | # Format: ["Calendar", "Start Month-Day", "End Month-Day"] 11 | # Fasting season 9: ["Old Calendar", "8-1", "8-14"] 12 | ( 13 | "1924-8-1", 14 | "old", 15 | { 16 | "fasting_season_index": "9", 17 | "fasting_laymen_index": "5", 18 | "fasting_monks_index": "5", 19 | }, 20 | ), # Thursday 21 | ( 22 | "1924-8-6", 23 | "old", 24 | { 25 | "fasting_season_index": "9", 26 | "fasting_laymen_index": "3", 27 | "fasting_monks_index": "3", 28 | }, 29 | ), # FISH_ALLOWED, Tuesday 30 | ( 31 | "1924-8-10", 32 | "old", 33 | { 34 | "fasting_season_index": "9", 35 | "fasting_laymen_index": "4", 36 | "fasting_monks_index": "4", 37 | }, 38 | ), # Saturday 39 | ( 40 | "1924-8-11", 41 | "old", 42 | { 43 | "fasting_season_index": "9", 44 | "fasting_laymen_index": "4", 45 | "fasting_monks_index": "4", 46 | }, 47 | ), # Sunday 48 | ( 49 | "1924-8-12", 50 | "old", 51 | { 52 | "fasting_season_index": "9", 53 | "fasting_laymen_index": "5", 54 | "fasting_monks_index": "5", 55 | }, 56 | ), # Monday 57 | ( 58 | "1924-8-13", 59 | "old", 60 | { 61 | "fasting_season_index": "9", 62 | "fasting_laymen_index": "5", 63 | "fasting_monks_index": "5", 64 | }, 65 | ), # Tuesday 66 | ( 67 | "1924-8-14", 68 | "old", 69 | { 70 | "fasting_season_index": "9", 71 | "fasting_laymen_index": "5", 72 | "fasting_monks_index": "5", 73 | }, 74 | ), # Wednesday 75 | ( 76 | "2099-8-1", 77 | "old", 78 | { 79 | "fasting_season_index": "9", 80 | "fasting_laymen_index": "5", 81 | "fasting_monks_index": "5", 82 | }, 83 | ), # Friday 84 | ( 85 | "2099-8-6", 86 | "old", 87 | { 88 | "fasting_season_index": "9", 89 | "fasting_laymen_index": "3", 90 | "fasting_monks_index": "3", 91 | }, 92 | ), # FISH_ALLOWED, Wednesday 93 | ( 94 | "2099-8-14", 95 | "old", 96 | { 97 | "fasting_season_index": "9", 98 | "fasting_laymen_index": "5", 99 | "fasting_monks_index": "5", 100 | }, 101 | ), # Thursday 102 | ] 103 | 104 | 105 | @pytest.mark.parametrize("current_date, calendar_style, expected", test_cases) 106 | def test_get_fasting(current_date: str, calendar_style: str, expected: str) -> None: 107 | """Test for get_fasting.""" 108 | assert get_fasting(string_to_date(current_date), calendar_style) == expected 109 | -------------------------------------------------------------------------------- /tests/core/test_fasting_season_02_old.py: -------------------------------------------------------------------------------- 1 | """Test for fasting subpackage.""" 2 | 3 | import pytest 4 | 5 | from ocma_data.core.fasting.logic import get_fasting 6 | from ocma_data.utils.date_utils import string_to_date 7 | 8 | test_cases = [ 9 | # Maximum range for each fasting season 10 | # Format: ["Calendar", "Start Month-Day", "End Month-Day"] 11 | # Fasting season 2: ["Old Calendar", "1-26", "3-7"] 12 | ( 13 | "2010-1-26", 14 | "old", 15 | { 16 | "fasting_season_index": "2", 17 | "fasting_laymen_index": "2", 18 | "fasting_monks_index": "2", 19 | }, 20 | ), # Monday 21 | ( 22 | "2010-1-27", 23 | "old", 24 | { 25 | "fasting_season_index": "2", 26 | "fasting_laymen_index": "2", 27 | "fasting_monks_index": "2", 28 | }, 29 | ), # Tuesday 30 | ( 31 | "2010-2-1", 32 | "old", 33 | { 34 | "fasting_season_index": "2", 35 | "fasting_laymen_index": "2", 36 | "fasting_monks_index": "2", 37 | }, 38 | ), # Sunday 39 | ( 40 | "2041-2-12", 41 | "old", 42 | { 43 | "fasting_season_index": "2", 44 | "fasting_laymen_index": "2", 45 | "fasting_monks_index": "2", 46 | }, 47 | ), # Monday 48 | ( 49 | "2041-2-14", 50 | "old", 51 | { 52 | "fasting_season_index": "2", 53 | "fasting_laymen_index": "2", 54 | "fasting_monks_index": "2", 55 | }, 56 | ), # Wednesday 57 | ( 58 | "2041-2-15", 59 | "old", 60 | { 61 | "fasting_season_index": "2", 62 | "fasting_laymen_index": "2", 63 | "fasting_monks_index": "2", 64 | }, 65 | ), # Thursday 66 | ( 67 | "2041-2-18", 68 | "old", 69 | { 70 | "fasting_season_index": "2", 71 | "fasting_laymen_index": "2", 72 | "fasting_monks_index": "2", 73 | }, 74 | ), # Sunday 75 | ( 76 | "2078-3-1", 77 | "old", 78 | { 79 | "fasting_season_index": "2", 80 | "fasting_laymen_index": "2", 81 | "fasting_monks_index": "2", 82 | }, 83 | ), # Monday 84 | ( 85 | "2078-3-5", 86 | "old", 87 | { 88 | "fasting_season_index": "2", 89 | "fasting_laymen_index": "2", 90 | "fasting_monks_index": "2", 91 | }, 92 | ), # Friday 93 | ( 94 | "2078-3-6", 95 | "old", 96 | { 97 | "fasting_season_index": "2", 98 | "fasting_laymen_index": "2", 99 | "fasting_monks_index": "2", 100 | }, 101 | ), # Saturday 102 | ( 103 | "2078-3-7", 104 | "old", 105 | { 106 | "fasting_season_index": "2", 107 | "fasting_laymen_index": "2", 108 | "fasting_monks_index": "2", 109 | }, 110 | ), # Sunday 111 | ] 112 | 113 | 114 | @pytest.mark.parametrize("current_date, calendar_style, expected", test_cases) 115 | def test_get_fasting(current_date: str, calendar_style: str, expected: str) -> None: 116 | """Test for get_fasting.""" 117 | assert get_fasting(string_to_date(current_date), calendar_style) == expected 118 | -------------------------------------------------------------------------------- /tests/core/test_fasting_season_02_new.py: -------------------------------------------------------------------------------- 1 | """Test for fasting subpackage.""" 2 | 3 | import pytest 4 | 5 | from ocma_data.core.fasting.logic import get_fasting 6 | from ocma_data.utils.date_utils import string_to_date 7 | 8 | test_cases = [ 9 | # Maximum range for each fasting season 10 | # Format: ["Calendar", "Start Month-Day", "End Month-Day"] 11 | # Fasting season 2: ["New Calendar", "2-8", "3-20"] 12 | ( 13 | "2010-2-8", 14 | "new", 15 | { 16 | "fasting_season_index": "2", 17 | "fasting_laymen_index": "2", 18 | "fasting_monks_index": "2", 19 | }, 20 | ), # Monday 21 | ( 22 | "2010-2-9", 23 | "new", 24 | { 25 | "fasting_season_index": "2", 26 | "fasting_laymen_index": "2", 27 | "fasting_monks_index": "2", 28 | }, 29 | ), # Tuesday 30 | ( 31 | "2010-2-14", 32 | "new", 33 | { 34 | "fasting_season_index": "2", 35 | "fasting_laymen_index": "2", 36 | "fasting_monks_index": "2", 37 | }, 38 | ), # Sunday 39 | ( 40 | "2041-2-25", 41 | "new", 42 | { 43 | "fasting_season_index": "2", 44 | "fasting_laymen_index": "2", 45 | "fasting_monks_index": "2", 46 | }, 47 | ), # Monday 48 | ( 49 | "2041-2-27", 50 | "new", 51 | { 52 | "fasting_season_index": "2", 53 | "fasting_laymen_index": "2", 54 | "fasting_monks_index": "2", 55 | }, 56 | ), # Wednesday 57 | ( 58 | "2041-2-28", 59 | "new", 60 | { 61 | "fasting_season_index": "2", 62 | "fasting_laymen_index": "2", 63 | "fasting_monks_index": "2", 64 | }, 65 | ), # Thursday 66 | ( 67 | "2041-3-3", 68 | "new", 69 | { 70 | "fasting_season_index": "2", 71 | "fasting_laymen_index": "2", 72 | "fasting_monks_index": "2", 73 | }, 74 | ), # Sunday 75 | ( 76 | "2078-3-14", 77 | "new", 78 | { 79 | "fasting_season_index": "2", 80 | "fasting_laymen_index": "2", 81 | "fasting_monks_index": "2", 82 | }, 83 | ), # Monday 84 | ( 85 | "2078-3-18", 86 | "new", 87 | { 88 | "fasting_season_index": "2", 89 | "fasting_laymen_index": "2", 90 | "fasting_monks_index": "2", 91 | }, 92 | ), # Friday 93 | ( 94 | "2078-3-19", 95 | "new", 96 | { 97 | "fasting_season_index": "2", 98 | "fasting_laymen_index": "2", 99 | "fasting_monks_index": "2", 100 | }, 101 | ), # Saturday 102 | ( 103 | "2078-3-20", 104 | "new", 105 | { 106 | "fasting_season_index": "2", 107 | "fasting_laymen_index": "2", 108 | "fasting_monks_index": "2", 109 | }, 110 | ), # Sunday 111 | ] 112 | 113 | 114 | @pytest.mark.parametrize("current_date, calendar_style, expected", test_cases) 115 | def test_get_fasting(current_date: str, calendar_style: str, expected: str) -> None: 116 | """Test for get_fasting.""" 117 | assert get_fasting(string_to_date(current_date), calendar_style) == expected 118 | -------------------------------------------------------------------------------- /ocma_data/core/fasting/constants.py: -------------------------------------------------------------------------------- 1 | """Constants related to Orthodox fasting rules.""" 2 | 3 | from enum import IntEnum, StrEnum 4 | 5 | 6 | class FastingLevels(IntEnum): 7 | """Enumeration for fasting levels.""" 8 | 9 | NO_FASTING = 1 10 | DAIRY_PRODUCTS_ALLOWED = 2 11 | FISH_ALLOWED = 3 12 | WINE_AND_OLIVE_OIL_ALLOWED = 4 13 | STRICT_FAST = 5 14 | ABSOLUTE_FAST = 6 15 | 16 | 17 | class FastingSeasons(IntEnum): 18 | """Enumeration for fasting seasons.""" 19 | 20 | FIRST_WEEK_OF_THE_TRIODION = 1 21 | CHEESEFARE_WEEK = 2 22 | THREE_DAY_FAST = 3 23 | GREAT_LENT = 4 24 | HOLY_WEEK = 5 25 | BRIGHT_WEEK = 6 26 | WEEK_OF_THE_HOLY_SPIRIT = 7 27 | APOSTLES_FAST = 8 28 | DORMITION_FAST = 9 29 | NATIVITY_FAST = 10 30 | TWELVE_DAY_FAST_FREE = 11 31 | REGULAR_SEASON = 12 32 | 33 | 34 | class PaschaDistanceBefore(IntEnum): 35 | """Enumeration for PaschaDistanceBefore.""" 36 | 37 | FIRST_WEEK_OF_THE_TRIODION = -70 38 | CHEESEFARE_WEEK = -55 39 | THREE_DAY_FAST = -48 40 | GREAT_LENT = -46 41 | HOLY_WEEK = -6 42 | BRIGHT_WEEK = 0 43 | WEEK_OF_THE_HOLY_SPIRIT = 49 44 | APOSTLES_FAST = 57 45 | 46 | 47 | class PaschaDistanceAfter(IntEnum): 48 | """Enumeration for PaschaDistanceAfter.""" 49 | 50 | FIRST_WEEK_OF_THE_TRIODION = -63 51 | CHEESEFARE_WEEK = -49 52 | THREE_DAY_FAST = -47 53 | GREAT_LENT = -7 54 | HOLY_WEEK = -1 55 | BRIGHT_WEEK = 7 56 | WEEK_OF_THE_HOLY_SPIRIT = 56 57 | 58 | 59 | class DateMovable(IntEnum): 60 | """Enumeration for DateMovable.""" 61 | 62 | PALM_SUNDAY = -7 63 | MIDFEAST_OF_PENTECOST = 24 64 | LEAVETAKING_OF_PASCHA = 38 65 | 66 | 67 | class DateFixed(StrEnum): 68 | """Enumeration for DateFixed.""" 69 | 70 | TWELVE_DAY_FAST_FREE_AFTER = "1-6" 71 | NATIVITY_OF_THE_BAPTIST = "6-24" 72 | APOSTLES_FAST = "6-29" 73 | DORMITION_FAST_BEFORE = "8-1" 74 | DORMITION_FAST_AFTER = "8-14" 75 | NATIVITY_FAST_BEFORE = "11-15" 76 | THE_ENTRANCE_OF_THE_THEOTOKOS = "11-21" 77 | SAINT_SPYRIDON = "12-12" 78 | NATIVITY_FAST_AFTER = "12-24" 79 | TWELVE_DAY_FAST_FREE_BEFORE = "12-25" 80 | 81 | 82 | STRICT_FAST = ("1-5", "8-29", "9-14", "12-24") 83 | WINE_AND_OLIVE_OIL_ALLOWED = ( 84 | "1-11", 85 | "1-16", 86 | "1-17", 87 | "1-18", 88 | "1-20", 89 | "1-22", 90 | "1-25", 91 | "1-27", 92 | "1-30", 93 | "2-8", 94 | "2-10", 95 | "2-11", 96 | "2-17", 97 | "2-24", 98 | "3-9", 99 | "4-23", 100 | "4-25", 101 | "4-30", 102 | "5-2", 103 | "5-8", 104 | "5-15", 105 | "5-21", 106 | "5-25", 107 | "6-8", 108 | "6-11", 109 | "6-30", 110 | "7-1", 111 | "7-2", 112 | "7-17", 113 | "7-20", 114 | "7-22", 115 | "7-25", 116 | "7-26", 117 | "7-27", 118 | "8-31", 119 | "9-1", 120 | "9-9", 121 | "9-13", 122 | "9-20", 123 | "9-23", 124 | "9-26", 125 | "10-6", 126 | "10-18", 127 | "10-23", 128 | "10-26", 129 | "11-1", 130 | "11-8", 131 | "11-12", 132 | "11-13", 133 | "11-16", 134 | "11-25", 135 | "11-30", 136 | "12-4", 137 | "12-5", 138 | "12-6", 139 | "12-9", 140 | "12-12", 141 | "12-15", 142 | "12-17", 143 | "12-20", 144 | ) 145 | FISH_ALLOWED = ( 146 | "1-7", 147 | "2-2", 148 | "3-25", 149 | "6-24", 150 | "6-29", 151 | "8-6", 152 | "8-15", 153 | "9-8", 154 | "11-14", 155 | "11-21", 156 | ) 157 | -------------------------------------------------------------------------------- /public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | OCMA-Data API 7 | 46 | 47 | 48 |

OCMA-Data API

49 | 50 |
51 |

52 | OCMA-Data is a structured Orthodox Calendar dataset 53 | providing JSON files for each liturgical day from 54 | 1924 to 2099, supporting both the 55 | Old Calendar (Julian) and 56 | New Calendar (Revised Julian). 57 |

58 | 59 |

60 | It includes Feasts, Saints, Fasting rules, Lectionary information, 61 | Paschalion calculations, and Moon Phases. Data is fully 62 | multilingual with separate JSON files for English, 63 | Greek, Romanian and Russian. 64 |

65 |
66 | 67 |

API Examples

68 |

Below are example endpoints for accessing the dataset.

69 | 70 |
71 |

Daily Calendar (by Year)

72 |

Example:

73 | 83 |
84 | 85 |
86 |

Multilingual Files

87 |

Each calendar day supports multiple languages.

88 | 97 |
98 | 99 |
100 |

Paschalion

101 | 111 |
112 | 113 | 121 | 122 | 123 | -------------------------------------------------------------------------------- /tests/core/test_fasting_season_01_new.py: -------------------------------------------------------------------------------- 1 | """Test for fasting subpackage.""" 2 | 3 | import pytest 4 | 5 | from ocma_data.core.fasting.logic import get_fasting 6 | from ocma_data.utils.date_utils import string_to_date 7 | 8 | test_cases = [ 9 | # Maximum range for each fasting season 10 | # Format: ["Calendar", "Start Month-Day", "End Month-Day"] 11 | # Fasting season 1: ["New Calendar", "1-24", "3-6"] 12 | ( 13 | "2010-1-24", 14 | "new", 15 | { 16 | "fasting_season_index": "1", 17 | "fasting_laymen_index": "1", 18 | "fasting_monks_index": "2", 19 | }, 20 | ), # Sunday 21 | ( 22 | "2010-1-25", 23 | "new", 24 | { 25 | "fasting_season_index": "1", 26 | "fasting_laymen_index": "1", 27 | "fasting_monks_index": "2", 28 | }, 29 | ), # Monday 30 | ( 31 | "2010-1-26", 32 | "new", 33 | { 34 | "fasting_season_index": "1", 35 | "fasting_laymen_index": "1", 36 | "fasting_monks_index": "2", 37 | }, 38 | ), # Tuesday 39 | ( 40 | "2010-1-31", 41 | "new", 42 | { 43 | "fasting_season_index": "1", 44 | "fasting_laymen_index": "1", 45 | "fasting_monks_index": "2", 46 | }, 47 | ), # Sunday 48 | ( 49 | "2041-2-10", 50 | "new", 51 | { 52 | "fasting_season_index": "1", 53 | "fasting_laymen_index": "1", 54 | "fasting_monks_index": "2", 55 | }, 56 | ), # Sunday 57 | ( 58 | "2041-2-13", 59 | "new", 60 | { 61 | "fasting_season_index": "1", 62 | "fasting_laymen_index": "1", 63 | "fasting_monks_index": "2", 64 | }, 65 | ), # Wednesday 66 | ( 67 | "2041-2-14", 68 | "new", 69 | { 70 | "fasting_season_index": "1", 71 | "fasting_laymen_index": "1", 72 | "fasting_monks_index": "2", 73 | }, 74 | ), # Thursday 75 | ( 76 | "2041-2-17", 77 | "new", 78 | { 79 | "fasting_season_index": "1", 80 | "fasting_laymen_index": "1", 81 | "fasting_monks_index": "2", 82 | }, 83 | ), # Sunday 84 | ( 85 | "2078-2-27", 86 | "new", 87 | { 88 | "fasting_season_index": "1", 89 | "fasting_laymen_index": "1", 90 | "fasting_monks_index": "2", 91 | }, 92 | ), # Sunday 93 | ( 94 | "2078-3-4", 95 | "new", 96 | { 97 | "fasting_season_index": "1", 98 | "fasting_laymen_index": "1", 99 | "fasting_monks_index": "2", 100 | }, 101 | ), # Friday 102 | ( 103 | "2078-3-5", 104 | "new", 105 | { 106 | "fasting_season_index": "1", 107 | "fasting_laymen_index": "1", 108 | "fasting_monks_index": "2", 109 | }, 110 | ), # Saturday 111 | ( 112 | "2078-3-6", 113 | "new", 114 | { 115 | "fasting_season_index": "1", 116 | "fasting_laymen_index": "1", 117 | "fasting_monks_index": "2", 118 | }, 119 | ), # Sunday 120 | ] 121 | 122 | 123 | @pytest.mark.parametrize("current_date, calendar_style, expected", test_cases) 124 | def test_get_fasting(current_date: str, calendar_style: str, expected: str) -> None: 125 | """Test for get_fasting.""" 126 | assert get_fasting(string_to_date(current_date), calendar_style) == expected 127 | -------------------------------------------------------------------------------- /tests/core/test_fasting_season_01_old.py: -------------------------------------------------------------------------------- 1 | """Test for fasting subpackage.""" 2 | 3 | import pytest 4 | 5 | from ocma_data.core.fasting.logic import get_fasting 6 | from ocma_data.utils.date_utils import string_to_date 7 | 8 | test_cases = [ 9 | # Maximum range for each fasting season 10 | # Format: ["Calendar", "Start Month-Day", "End Month-Day"] 11 | # Fasting season 1: ["Old Calendar", "1-11", "2-21"] 12 | ( 13 | "2010-1-11", 14 | "old", 15 | { 16 | "fasting_season_index": "1", 17 | "fasting_laymen_index": "1", 18 | "fasting_monks_index": "2", 19 | }, 20 | ), # Sunday 21 | ( 22 | "2010-1-12", 23 | "old", 24 | { 25 | "fasting_season_index": "1", 26 | "fasting_laymen_index": "1", 27 | "fasting_monks_index": "2", 28 | }, 29 | ), # Monday 30 | ( 31 | "2010-1-13", 32 | "old", 33 | { 34 | "fasting_season_index": "1", 35 | "fasting_laymen_index": "1", 36 | "fasting_monks_index": "2", 37 | }, 38 | ), # Tuesday 39 | ( 40 | "2010-1-18", 41 | "old", 42 | { 43 | "fasting_season_index": "1", 44 | "fasting_laymen_index": "1", 45 | "fasting_monks_index": "2", 46 | }, 47 | ), # Sunday 48 | ( 49 | "2041-1-28", 50 | "old", 51 | { 52 | "fasting_season_index": "1", 53 | "fasting_laymen_index": "1", 54 | "fasting_monks_index": "2", 55 | }, 56 | ), # Sunday 57 | ( 58 | "2041-1-31", 59 | "old", 60 | { 61 | "fasting_season_index": "1", 62 | "fasting_laymen_index": "1", 63 | "fasting_monks_index": "2", 64 | }, 65 | ), # Wednesday 66 | ( 67 | "2041-2-1", 68 | "old", 69 | { 70 | "fasting_season_index": "1", 71 | "fasting_laymen_index": "1", 72 | "fasting_monks_index": "2", 73 | }, 74 | ), # Thursday 75 | ( 76 | "2041-2-4", 77 | "old", 78 | { 79 | "fasting_season_index": "1", 80 | "fasting_laymen_index": "1", 81 | "fasting_monks_index": "2", 82 | }, 83 | ), # Sunday 84 | ( 85 | "2078-2-14", 86 | "old", 87 | { 88 | "fasting_season_index": "1", 89 | "fasting_laymen_index": "1", 90 | "fasting_monks_index": "2", 91 | }, 92 | ), # Sunday 93 | ( 94 | "2078-2-19", 95 | "old", 96 | { 97 | "fasting_season_index": "1", 98 | "fasting_laymen_index": "1", 99 | "fasting_monks_index": "2", 100 | }, 101 | ), # Friday 102 | ( 103 | "2078-2-20", 104 | "old", 105 | { 106 | "fasting_season_index": "1", 107 | "fasting_laymen_index": "1", 108 | "fasting_monks_index": "2", 109 | }, 110 | ), # Saturday 111 | ( 112 | "2078-2-21", 113 | "old", 114 | { 115 | "fasting_season_index": "1", 116 | "fasting_laymen_index": "1", 117 | "fasting_monks_index": "2", 118 | }, 119 | ), # Sunday 120 | ] 121 | 122 | 123 | @pytest.mark.parametrize("current_date, calendar_style, expected", test_cases) 124 | def test_get_fasting(current_date: str, calendar_style: str, expected: str) -> None: 125 | """Test for get_fasting.""" 126 | assert get_fasting(string_to_date(current_date), calendar_style) == expected 127 | -------------------------------------------------------------------------------- /tests/core/test_fasting_season_06_new.py: -------------------------------------------------------------------------------- 1 | """Test for fasting subpackage.""" 2 | 3 | import pytest 4 | 5 | from ocma_data.core.fasting.logic import get_fasting 6 | from ocma_data.utils.date_utils import string_to_date 7 | 8 | test_cases = [ 9 | # Maximum range for each fasting season 10 | # Format: ["Calendar", "Start Month-Day", "End Month-Day"] 11 | # Fasting season 6: ["New Calendar", "4-4", "5-15"] 12 | ( 13 | "2010-4-4", 14 | "new", 15 | { 16 | "fasting_season_index": "6", 17 | "fasting_laymen_index": "1", 18 | "fasting_monks_index": "2", 19 | }, 20 | ), # Sunday 21 | ( 22 | "2010-4-5", 23 | "new", 24 | { 25 | "fasting_season_index": "6", 26 | "fasting_laymen_index": "1", 27 | "fasting_monks_index": "2", 28 | }, 29 | ), # Monday 30 | ( 31 | "2010-4-6", 32 | "new", 33 | { 34 | "fasting_season_index": "6", 35 | "fasting_laymen_index": "1", 36 | "fasting_monks_index": "2", 37 | }, 38 | ), # Tuesday 39 | ( 40 | "2010-4-11", 41 | "new", 42 | { 43 | "fasting_season_index": "6", 44 | "fasting_laymen_index": "1", 45 | "fasting_monks_index": "2", 46 | }, 47 | ), # Sunday 48 | ( 49 | "2041-4-21", 50 | "new", 51 | { 52 | "fasting_season_index": "6", 53 | "fasting_laymen_index": "1", 54 | "fasting_monks_index": "2", 55 | }, 56 | ), # Sunday 57 | ( 58 | "2041-4-24", 59 | "new", 60 | { 61 | "fasting_season_index": "6", 62 | "fasting_laymen_index": "1", 63 | "fasting_monks_index": "2", 64 | }, 65 | ), # Wednesday 66 | ( 67 | "2041-4-25", 68 | "new", 69 | { 70 | "fasting_season_index": "6", 71 | "fasting_laymen_index": "1", 72 | "fasting_monks_index": "2", 73 | }, 74 | ), # Thursday 75 | ( 76 | "2041-4-28", 77 | "new", 78 | { 79 | "fasting_season_index": "6", 80 | "fasting_laymen_index": "1", 81 | "fasting_monks_index": "2", 82 | }, 83 | ), # Sunday 84 | ( 85 | "2078-5-8", 86 | "new", 87 | { 88 | "fasting_season_index": "6", 89 | "fasting_laymen_index": "1", 90 | "fasting_monks_index": "2", 91 | }, 92 | ), # Sunday 93 | ( 94 | "2078-5-13", 95 | "new", 96 | { 97 | "fasting_season_index": "6", 98 | "fasting_laymen_index": "1", 99 | "fasting_monks_index": "2", 100 | }, 101 | ), # Friday 102 | ( 103 | "2078-5-14", 104 | "new", 105 | { 106 | "fasting_season_index": "6", 107 | "fasting_laymen_index": "1", 108 | "fasting_monks_index": "2", 109 | }, 110 | ), # Saturday 111 | ( 112 | "2078-5-15", 113 | "new", 114 | { 115 | "fasting_season_index": "6", 116 | "fasting_laymen_index": "1", 117 | "fasting_monks_index": "2", 118 | }, 119 | ), # Sunday 120 | ] 121 | 122 | 123 | @pytest.mark.parametrize("current_date, calendar_style, expected", test_cases) 124 | def test_get_fasting(current_date: str, calendar_style: str, expected: str) -> None: 125 | """Test for get_fasting.""" 126 | assert get_fasting(string_to_date(current_date), calendar_style) == expected 127 | -------------------------------------------------------------------------------- /tests/core/test_fasting_season_06_old.py: -------------------------------------------------------------------------------- 1 | """Test for fasting subpackage.""" 2 | 3 | import pytest 4 | 5 | from ocma_data.core.fasting.logic import get_fasting 6 | from ocma_data.utils.date_utils import string_to_date 7 | 8 | test_cases = [ 9 | # Maximum range for each fasting season 10 | # Format: ["Calendar", "Start Month-Day", "End Month-Day"] 11 | # Fasting season 6: ["Old Calendar", "3-22", "5-2"] 12 | ( 13 | "2010-3-22", 14 | "old", 15 | { 16 | "fasting_season_index": "6", 17 | "fasting_laymen_index": "1", 18 | "fasting_monks_index": "2", 19 | }, 20 | ), # Sunday 21 | ( 22 | "2010-3-23", 23 | "old", 24 | { 25 | "fasting_season_index": "6", 26 | "fasting_laymen_index": "1", 27 | "fasting_monks_index": "2", 28 | }, 29 | ), # Monday 30 | ( 31 | "2010-3-24", 32 | "old", 33 | { 34 | "fasting_season_index": "6", 35 | "fasting_laymen_index": "1", 36 | "fasting_monks_index": "2", 37 | }, 38 | ), # Tuesday 39 | ( 40 | "2010-3-29", 41 | "old", 42 | { 43 | "fasting_season_index": "6", 44 | "fasting_laymen_index": "1", 45 | "fasting_monks_index": "2", 46 | }, 47 | ), # Sunday 48 | ( 49 | "2041-4-8", 50 | "old", 51 | { 52 | "fasting_season_index": "6", 53 | "fasting_laymen_index": "1", 54 | "fasting_monks_index": "2", 55 | }, 56 | ), # Sunday 57 | ( 58 | "2041-4-11", 59 | "old", 60 | { 61 | "fasting_season_index": "6", 62 | "fasting_laymen_index": "1", 63 | "fasting_monks_index": "2", 64 | }, 65 | ), # Wednesday 66 | ( 67 | "2041-4-12", 68 | "old", 69 | { 70 | "fasting_season_index": "6", 71 | "fasting_laymen_index": "1", 72 | "fasting_monks_index": "2", 73 | }, 74 | ), # Thursday 75 | ( 76 | "2041-4-15", 77 | "old", 78 | { 79 | "fasting_season_index": "6", 80 | "fasting_laymen_index": "1", 81 | "fasting_monks_index": "2", 82 | }, 83 | ), # Sunday 84 | ( 85 | "2078-4-25", 86 | "old", 87 | { 88 | "fasting_season_index": "6", 89 | "fasting_laymen_index": "1", 90 | "fasting_monks_index": "2", 91 | }, 92 | ), # Sunday 93 | ( 94 | "2078-4-30", 95 | "old", 96 | { 97 | "fasting_season_index": "6", 98 | "fasting_laymen_index": "1", 99 | "fasting_monks_index": "2", 100 | }, 101 | ), # Friday 102 | ( 103 | "2078-5-1", 104 | "old", 105 | { 106 | "fasting_season_index": "6", 107 | "fasting_laymen_index": "1", 108 | "fasting_monks_index": "2", 109 | }, 110 | ), # Saturday 111 | ( 112 | "2078-5-2", 113 | "old", 114 | { 115 | "fasting_season_index": "6", 116 | "fasting_laymen_index": "1", 117 | "fasting_monks_index": "2", 118 | }, 119 | ), # Sunday 120 | ] 121 | 122 | 123 | @pytest.mark.parametrize("current_date, calendar_style, expected", test_cases) 124 | def test_get_fasting(current_date: str, calendar_style: str, expected: str) -> None: 125 | """Test for get_fasting.""" 126 | assert get_fasting(string_to_date(current_date), calendar_style) == expected 127 | -------------------------------------------------------------------------------- /tests/core/test_fasting_season_07_new.py: -------------------------------------------------------------------------------- 1 | """Test for fasting subpackage.""" 2 | 3 | import pytest 4 | 5 | from ocma_data.core.fasting.logic import get_fasting 6 | from ocma_data.utils.date_utils import string_to_date 7 | 8 | test_cases = [ 9 | # Maximum range for each fasting season 10 | # Format: ["Calendar", "Start Month-Day", "End Month-Day"] 11 | # Fasting season 7: ["New Calendar", "5-23", "7-3"] 12 | ( 13 | "2010-5-23", 14 | "new", 15 | { 16 | "fasting_season_index": "7", 17 | "fasting_laymen_index": "1", 18 | "fasting_monks_index": "2", 19 | }, 20 | ), # Sunday 21 | ( 22 | "2010-5-24", 23 | "new", 24 | { 25 | "fasting_season_index": "7", 26 | "fasting_laymen_index": "1", 27 | "fasting_monks_index": "2", 28 | }, 29 | ), # Monday 30 | ( 31 | "2010-5-25", 32 | "new", 33 | { 34 | "fasting_season_index": "7", 35 | "fasting_laymen_index": "1", 36 | "fasting_monks_index": "2", 37 | }, 38 | ), # Tuesday 39 | ( 40 | "2010-5-30", 41 | "new", 42 | { 43 | "fasting_season_index": "7", 44 | "fasting_laymen_index": "1", 45 | "fasting_monks_index": "2", 46 | }, 47 | ), # Sunday 48 | ( 49 | "2041-6-9", 50 | "new", 51 | { 52 | "fasting_season_index": "7", 53 | "fasting_laymen_index": "1", 54 | "fasting_monks_index": "2", 55 | }, 56 | ), # Sunday 57 | ( 58 | "2041-6-12", 59 | "new", 60 | { 61 | "fasting_season_index": "7", 62 | "fasting_laymen_index": "1", 63 | "fasting_monks_index": "2", 64 | }, 65 | ), # Wednesday 66 | ( 67 | "2041-6-13", 68 | "new", 69 | { 70 | "fasting_season_index": "7", 71 | "fasting_laymen_index": "1", 72 | "fasting_monks_index": "2", 73 | }, 74 | ), # Thursday 75 | ( 76 | "2041-6-16", 77 | "new", 78 | { 79 | "fasting_season_index": "7", 80 | "fasting_laymen_index": "1", 81 | "fasting_monks_index": "2", 82 | }, 83 | ), # Sunday 84 | ( 85 | "2078-6-26", 86 | "new", 87 | { 88 | "fasting_season_index": "7", 89 | "fasting_laymen_index": "1", 90 | "fasting_monks_index": "2", 91 | }, 92 | ), # Sunday 93 | ( 94 | "2078-7-1", 95 | "new", 96 | { 97 | "fasting_season_index": "7", 98 | "fasting_laymen_index": "1", 99 | "fasting_monks_index": "2", 100 | }, 101 | ), # Friday 102 | ( 103 | "2078-7-2", 104 | "new", 105 | { 106 | "fasting_season_index": "7", 107 | "fasting_laymen_index": "1", 108 | "fasting_monks_index": "2", 109 | }, 110 | ), # Saturday 111 | ( 112 | "2078-7-3", 113 | "new", 114 | { 115 | "fasting_season_index": "7", 116 | "fasting_laymen_index": "1", 117 | "fasting_monks_index": "2", 118 | }, 119 | ), # Sunday 120 | ] 121 | 122 | 123 | @pytest.mark.parametrize("current_date, calendar_style, expected", test_cases) 124 | def test_get_fasting(current_date: str, calendar_style: str, expected: str) -> None: 125 | """Test for get_fasting.""" 126 | assert get_fasting(string_to_date(current_date), calendar_style) == expected 127 | -------------------------------------------------------------------------------- /tests/core/test_fasting_season_07_old.py: -------------------------------------------------------------------------------- 1 | """Test for fasting subpackage.""" 2 | 3 | import pytest 4 | 5 | from ocma_data.core.fasting.logic import get_fasting 6 | from ocma_data.utils.date_utils import string_to_date 7 | 8 | test_cases = [ 9 | # Maximum range for each fasting season 10 | # Format: ["Calendar", "Start Month-Day", "End Month-Day"] 11 | # Fasting season 7: ["Old Calendar", "5-10", "6-20"] 12 | ( 13 | "2010-5-10", 14 | "old", 15 | { 16 | "fasting_season_index": "7", 17 | "fasting_laymen_index": "1", 18 | "fasting_monks_index": "2", 19 | }, 20 | ), # Sunday 21 | ( 22 | "2010-5-11", 23 | "old", 24 | { 25 | "fasting_season_index": "7", 26 | "fasting_laymen_index": "1", 27 | "fasting_monks_index": "2", 28 | }, 29 | ), # Monday 30 | ( 31 | "2010-5-12", 32 | "old", 33 | { 34 | "fasting_season_index": "7", 35 | "fasting_laymen_index": "1", 36 | "fasting_monks_index": "2", 37 | }, 38 | ), # Tuesday 39 | ( 40 | "2010-5-17", 41 | "old", 42 | { 43 | "fasting_season_index": "7", 44 | "fasting_laymen_index": "1", 45 | "fasting_monks_index": "2", 46 | }, 47 | ), # Sunday 48 | ( 49 | "2041-5-27", 50 | "old", 51 | { 52 | "fasting_season_index": "7", 53 | "fasting_laymen_index": "1", 54 | "fasting_monks_index": "2", 55 | }, 56 | ), # Sunday 57 | ( 58 | "2041-5-30", 59 | "old", 60 | { 61 | "fasting_season_index": "7", 62 | "fasting_laymen_index": "1", 63 | "fasting_monks_index": "2", 64 | }, 65 | ), # Wednesday 66 | ( 67 | "2041-5-31", 68 | "old", 69 | { 70 | "fasting_season_index": "7", 71 | "fasting_laymen_index": "1", 72 | "fasting_monks_index": "2", 73 | }, 74 | ), # Thursday 75 | ( 76 | "2041-6-3", 77 | "old", 78 | { 79 | "fasting_season_index": "7", 80 | "fasting_laymen_index": "1", 81 | "fasting_monks_index": "2", 82 | }, 83 | ), # Sunday 84 | ( 85 | "2078-6-13", 86 | "old", 87 | { 88 | "fasting_season_index": "7", 89 | "fasting_laymen_index": "1", 90 | "fasting_monks_index": "2", 91 | }, 92 | ), # Sunday 93 | ( 94 | "2078-6-18", 95 | "old", 96 | { 97 | "fasting_season_index": "7", 98 | "fasting_laymen_index": "1", 99 | "fasting_monks_index": "2", 100 | }, 101 | ), # Friday 102 | ( 103 | "2078-6-19", 104 | "old", 105 | { 106 | "fasting_season_index": "7", 107 | "fasting_laymen_index": "1", 108 | "fasting_monks_index": "2", 109 | }, 110 | ), # Saturday 111 | ( 112 | "2078-6-20", 113 | "old", 114 | { 115 | "fasting_season_index": "7", 116 | "fasting_laymen_index": "1", 117 | "fasting_monks_index": "2", 118 | }, 119 | ), # Sunday 120 | ] 121 | 122 | 123 | @pytest.mark.parametrize("current_date, calendar_style, expected", test_cases) 124 | def test_get_fasting(current_date: str, calendar_style: str, expected: str) -> None: 125 | """Test for get_fasting.""" 126 | assert get_fasting(string_to_date(current_date), calendar_style) == expected 127 | -------------------------------------------------------------------------------- /public/i18n/en.json: -------------------------------------------------------------------------------- 1 | { 2 | "descriptions": { 3 | "language": "Language", 4 | "language_en": "English", 5 | "language_gr": "Ελληνικά", 6 | "language_ro": "Română", 7 | "language_ru": "Русский", 8 | "calendar_style": "Calendar Style", 9 | "old_calendar": "Old Calendar", 10 | "new_calendar": "New Calendar", 11 | "fasting_rules": "Fasting Rules", 12 | "monastic": "Monastic", 13 | "laypeople": "Laypeople", 14 | "lectionary": "Lectionary", 15 | "tone": "Tone", 16 | "matins_gospel": "Matins Gospel", 17 | "epistle": "Epistle", 18 | "gospel": "Gospel", 19 | "paschalion": "Paschalion" 20 | }, 21 | "months": { 22 | "1": { 23 | "short": "Jan", 24 | "long": "January" 25 | }, 26 | "2": { 27 | "short": "Feb", 28 | "long": "February" 29 | }, 30 | "3": { 31 | "short": "Mar", 32 | "long": "March" 33 | }, 34 | "4": { 35 | "short": "Apr", 36 | "long": "April" 37 | }, 38 | "5": { 39 | "short": "May", 40 | "long": "May" 41 | }, 42 | "6": { 43 | "short": "Jun", 44 | "long": "June" 45 | }, 46 | "7": { 47 | "short": "Jul", 48 | "long": "July" 49 | }, 50 | "8": { 51 | "short": "Aug", 52 | "long": "August" 53 | }, 54 | "9": { 55 | "short": "Sep", 56 | "long": "September" 57 | }, 58 | "10": { 59 | "short": "Oct", 60 | "long": "October" 61 | }, 62 | "11": { 63 | "short": "Nov", 64 | "long": "November" 65 | }, 66 | "12": { 67 | "short": "Dec", 68 | "long": "December" 69 | } 70 | }, 71 | "weekdays": { 72 | "1": { 73 | "short": "Mon", 74 | "long": "Monday" 75 | }, 76 | "2": { 77 | "short": "Tue", 78 | "long": "Tuesday" 79 | }, 80 | "3": { 81 | "short": "Wed", 82 | "long": "Wednesday" 83 | }, 84 | "4": { 85 | "short": "Thu", 86 | "long": "Thursday" 87 | }, 88 | "5": { 89 | "short": "Fri", 90 | "long": "Friday" 91 | }, 92 | "6": { 93 | "short": "Sat", 94 | "long": "Saturday" 95 | }, 96 | "7": { 97 | "short": "Sun", 98 | "long": "Sunday" 99 | } 100 | }, 101 | "moon_phases": { 102 | "1": "New Moon", 103 | "2": "First Quarter", 104 | "3": "Full Moon", 105 | "4": "Last Quarter" 106 | }, 107 | "fastings": { 108 | "seasons": { 109 | "1": "First Week of the Triodion", 110 | "2": "Cheesefare Week", 111 | "3": "Three-Day Fast", 112 | "4": "Great Lent", 113 | "5": "Holy Week", 114 | "6": "Bright Week", 115 | "7": "Week of the Holy Spirit", 116 | "8": "Apostles' Fast", 117 | "9": "Dormition Fast", 118 | "10": "Nativity Fast", 119 | "11": "Twelve-Day Fast-free", 120 | "12": "Regular Season" 121 | }, 122 | "levels": { 123 | "1": "No Fasting", 124 | "2": "Dairy Products Allowed", 125 | "3": "Fish Allowed", 126 | "4": "Wine and Olive Oil Allowed", 127 | "5": "Strict Fast", 128 | "6": "Absolute Fast" 129 | } 130 | }, 131 | "sundays": { 132 | "descriptions": { 133 | "": "" 134 | }, 135 | "lectionaries": { 136 | "1": { 137 | "tone": "", 138 | "matins_gospel": "", 139 | "epistle": "", 140 | "gospel": "" 141 | } 142 | } 143 | }, 144 | "feasts": { 145 | "movable": { 146 | "major_feasts": { 147 | "": [] 148 | }, 149 | "minor_feasts": { 150 | "": [] 151 | } 152 | }, 153 | "fixed": { 154 | "great_feasts": { 155 | "": [] 156 | }, 157 | "middle_feasts": { 158 | "": [] 159 | }, 160 | "lesser_feasts": { 161 | "": [] 162 | } 163 | } 164 | }, 165 | "saints": { 166 | "common_saints": { 167 | "": [] 168 | }, 169 | "additional_saints": { 170 | "": [] 171 | }, 172 | "calendar_specific_saints": { 173 | "": [] 174 | } 175 | } 176 | } 177 | -------------------------------------------------------------------------------- /public/i18n/ru.json: -------------------------------------------------------------------------------- 1 | { 2 | "descriptions": { 3 | "language": "Язык", 4 | "language_en": "English", 5 | "language_gr": "Ελληνικά", 6 | "language_ro": "Română", 7 | "language_ru": "Русский", 8 | "calendar_style": "Стиль календаря", 9 | "old_calendar": "Старый стиль", 10 | "new_calendar": "Новый стиль", 11 | "fasting_rules": "Правила поста", 12 | "monastic": "Монашеский", 13 | "laypeople": "Мирской", 14 | "lectionary": "Чтения", 15 | "tone": "Глас", 16 | "matins_gospel": "Евангелие на утрене", 17 | "epistle": "Апостол", 18 | "gospel": "Евангелие", 19 | "paschalion": "Пасхалия" 20 | }, 21 | "months": { 22 | "1": { 23 | "short": "Янв", 24 | "long": "Январь" 25 | }, 26 | "2": { 27 | "short": "Фев", 28 | "long": "Февраль" 29 | }, 30 | "3": { 31 | "short": "Мар", 32 | "long": "Март" 33 | }, 34 | "4": { 35 | "short": "Апр", 36 | "long": "Апрель" 37 | }, 38 | "5": { 39 | "short": "Май", 40 | "long": "Май" 41 | }, 42 | "6": { 43 | "short": "Июн", 44 | "long": "Июнь" 45 | }, 46 | "7": { 47 | "short": "Июл", 48 | "long": "Июль" 49 | }, 50 | "8": { 51 | "short": "Авг", 52 | "long": "Август" 53 | }, 54 | "9": { 55 | "short": "Сен", 56 | "long": "Сентябрь" 57 | }, 58 | "10": { 59 | "short": "Окт", 60 | "long": "Октябрь" 61 | }, 62 | "11": { 63 | "short": "Ноя", 64 | "long": "Ноябрь" 65 | }, 66 | "12": { 67 | "short": "Дек", 68 | "long": "Декабрь" 69 | } 70 | }, 71 | "weekdays": { 72 | "1": { 73 | "short": "Пн", 74 | "long": "Понедельник" 75 | }, 76 | "2": { 77 | "short": "Вт", 78 | "long": "Вторник" 79 | }, 80 | "3": { 81 | "short": "Ср", 82 | "long": "Среда" 83 | }, 84 | "4": { 85 | "short": "Чт", 86 | "long": "Четверг" 87 | }, 88 | "5": { 89 | "short": "Пт", 90 | "long": "Пятница" 91 | }, 92 | "6": { 93 | "short": "Сб", 94 | "long": "Суббота" 95 | }, 96 | "7": { 97 | "short": "Вс", 98 | "long": "Воскресенье" 99 | } 100 | }, 101 | "moon_phases": { 102 | "1": "Новолуние", 103 | "2": "Первая четверть", 104 | "3": "Полнолуние", 105 | "4": "Последняя четверть" 106 | }, 107 | "fastings": { 108 | "seasons": { 109 | "1": "Первая седмица Триоди", 110 | "2": "Сырная седмица", 111 | "3": "Трёхдневный пост", 112 | "4": "Великий пост", 113 | "5": "Страстная седмица", 114 | "6": "Светлая седмица", 115 | "7": "Седмица Святого Духа", 116 | "8": "Петров пост", 117 | "9": "Успенский пост", 118 | "10": "Рождественский пост", 119 | "11": "Двенадцатидневие (сплошная седмица)", 120 | "12": "Обычное время" 121 | }, 122 | "levels": { 123 | "1": "Поста нет", 124 | "2": "Разрешается молоко", 125 | "3": "Разрешается рыба", 126 | "4": "Разрешается вино и елей", 127 | "5": "Строгий пост", 128 | "6": "Полное воздержание" 129 | } 130 | }, 131 | "sundays": { 132 | "descriptions": { 133 | "": "" 134 | }, 135 | "lectionaries": { 136 | "1": { 137 | "tone": "", 138 | "matins_gospel": "", 139 | "epistle": "", 140 | "gospel": "" 141 | } 142 | } 143 | }, 144 | "feasts": { 145 | "movable": { 146 | "major_feasts": { 147 | "": [] 148 | }, 149 | "minor_feasts": { 150 | "": [] 151 | } 152 | }, 153 | "fixed": { 154 | "great_feasts": { 155 | "": [] 156 | }, 157 | "middle_feasts": { 158 | "": [] 159 | }, 160 | "lesser_feasts": { 161 | "": [] 162 | } 163 | } 164 | }, 165 | "saints": { 166 | "common_saints": { 167 | "": [] 168 | }, 169 | "additional_saints": { 170 | "": [] 171 | }, 172 | "calendar_specific_saints": { 173 | "": [] 174 | } 175 | } 176 | } 177 | -------------------------------------------------------------------------------- /public/i18n/ro.json: -------------------------------------------------------------------------------- 1 | { 2 | "descriptions": { 3 | "language": "Limba", 4 | "language_en": "English", 5 | "language_gr": "Ελληνικά", 6 | "language_ro": "Română", 7 | "language_ru": "Русский", 8 | "calendar_style": "Stil calendar", 9 | "old_calendar": "Stil vechi", 10 | "new_calendar": "Stil nou", 11 | "fasting_rules": "Reguli de postire", 12 | "monastic": "Monahale", 13 | "laypeople": "Pentru mireni", 14 | "lectionary": "Evanghelistar", 15 | "tone": "Glas", 16 | "matins_gospel": "Voscreasnă", 17 | "epistle": "Apostol", 18 | "gospel": "Evanghelie", 19 | "paschalion": "Pascalie" 20 | }, 21 | "months": { 22 | "1": { 23 | "short": "Ian", 24 | "long": "Ianuarie" 25 | }, 26 | "2": { 27 | "short": "Feb", 28 | "long": "Februarie" 29 | }, 30 | "3": { 31 | "short": "Mar", 32 | "long": "Martie" 33 | }, 34 | "4": { 35 | "short": "Apr", 36 | "long": "Aprilie" 37 | }, 38 | "5": { 39 | "short": "Mai", 40 | "long": "Mai" 41 | }, 42 | "6": { 43 | "short": "Iun", 44 | "long": "Iunie" 45 | }, 46 | "7": { 47 | "short": "Iul", 48 | "long": "Iulie" 49 | }, 50 | "8": { 51 | "short": "Aug", 52 | "long": "August" 53 | }, 54 | "9": { 55 | "short": "Sep", 56 | "long": "Septembrie" 57 | }, 58 | "10": { 59 | "short": "Oct", 60 | "long": "Octombrie" 61 | }, 62 | "11": { 63 | "short": "Nov", 64 | "long": "Noiembrie" 65 | }, 66 | "12": { 67 | "short": "Dec", 68 | "long": "Decembrie" 69 | } 70 | }, 71 | "weekdays": { 72 | "1": { 73 | "short": "Lun", 74 | "long": "Luni" 75 | }, 76 | "2": { 77 | "short": "Mar", 78 | "long": "Marți" 79 | }, 80 | "3": { 81 | "short": "Mie", 82 | "long": "Miercuri" 83 | }, 84 | "4": { 85 | "short": "Joi", 86 | "long": "Joi" 87 | }, 88 | "5": { 89 | "short": "Vin", 90 | "long": "Vineri" 91 | }, 92 | "6": { 93 | "short": "Sâm", 94 | "long": "Sâmbătă" 95 | }, 96 | "7": { 97 | "short": "Dum", 98 | "long": "Duminică" 99 | } 100 | }, 101 | "moon_phases": { 102 | "1": "Lună nouă", 103 | "2": "Primul pătrar", 104 | "3": "Lună plină", 105 | "4": "Ultimul pătrar" 106 | }, 107 | "fastings": { 108 | "seasons": { 109 | "1": "Prima săptămână a Triodului", 110 | "2": "Săptămâna brânzei", 111 | "3": "Postul de trei zile", 112 | "4": "Postul Mare", 113 | "5": "Săptămâna Mare", 114 | "6": "Săptămâna Luminată", 115 | "7": "Săptămâna Sfântului Duh", 116 | "8": "Postul Sfinților Apostoli", 117 | "9": "Postul Adormirii Maicii Domnului", 118 | "10": "Postul Nașterii Domnului", 119 | "11": "Doisprezece zile de harți", 120 | "12": "Perioadă obișnuită" 121 | }, 122 | "levels": { 123 | "1": "Dezlegare la toate", 124 | "2": "Dezlegare la produse lactate", 125 | "3": "Dezlegare la pește", 126 | "4": "Dezlegare la vin și untdelemn", 127 | "5": "Post aspru", 128 | "6": "Post total" 129 | } 130 | }, 131 | "sundays": { 132 | "descriptions": { 133 | "": "" 134 | }, 135 | "lectionaries": { 136 | "1": { 137 | "tone": "", 138 | "matins_gospel": "", 139 | "epistle": "", 140 | "gospel": "" 141 | } 142 | } 143 | }, 144 | "feasts": { 145 | "movable": { 146 | "major_feasts": { 147 | "": [] 148 | }, 149 | "minor_feasts": { 150 | "": [] 151 | } 152 | }, 153 | "fixed": { 154 | "great_feasts": { 155 | "": [] 156 | }, 157 | "middle_feasts": { 158 | "": [] 159 | }, 160 | "lesser_feasts": { 161 | "": [] 162 | } 163 | } 164 | }, 165 | "saints": { 166 | "common_saints": { 167 | "": [] 168 | }, 169 | "additional_saints": { 170 | "": [] 171 | }, 172 | "calendar_specific_saints": { 173 | "": [] 174 | } 175 | } 176 | } 177 | -------------------------------------------------------------------------------- /public/i18n/el.json: -------------------------------------------------------------------------------- 1 | { 2 | "descriptions": { 3 | "language": "Γλώσσα", 4 | "language_en": "English", 5 | "language_gr": "Ελληνικά", 6 | "language_ro": "Română", 7 | "language_ru": "Русский", 8 | "calendar_style": "Τύπος Ημερολογίου", 9 | "old_calendar": "Παλαιό Ημερολόγιο", 10 | "new_calendar": "Νέο Ημερολόγιο", 11 | "fasting_rules": "Κανόνες νηστείας", 12 | "monastic": "Μοναστικοί", 13 | "laypeople": "Λαϊκοί", 14 | "lectionary": "Κυριακοδρόμιο", 15 | "tone": "Ήχος", 16 | "matins_gospel": "Εωθινό", 17 | "epistle": "Απόστολος", 18 | "gospel": "Ευαγγέλιο", 19 | "paschalion": "Πασχάλιον" 20 | }, 21 | "months": { 22 | "1": { 23 | "short": "Ιαν", 24 | "long": "Ιανουάριος" 25 | }, 26 | "2": { 27 | "short": "Φεβ", 28 | "long": "Φεβρουάριος" 29 | }, 30 | "3": { 31 | "short": "Μαρ", 32 | "long": "Μάρτιος" 33 | }, 34 | "4": { 35 | "short": "Απρ", 36 | "long": "Απρίλιος" 37 | }, 38 | "5": { 39 | "short": "Μάι", 40 | "long": "Μάιος" 41 | }, 42 | "6": { 43 | "short": "Ιούν", 44 | "long": "Ιούνιος" 45 | }, 46 | "7": { 47 | "short": "Ιούλ", 48 | "long": "Ιούλιος" 49 | }, 50 | "8": { 51 | "short": "Αύγ", 52 | "long": "Αύγουστος" 53 | }, 54 | "9": { 55 | "short": "Σεπ", 56 | "long": "Σεπτέμβριος" 57 | }, 58 | "10": { 59 | "short": "Οκτ", 60 | "long": "Οκτώβριος" 61 | }, 62 | "11": { 63 | "short": "Νοε", 64 | "long": "Νοέμβριος" 65 | }, 66 | "12": { 67 | "short": "Δεκ", 68 | "long": "Δεκέμβριος" 69 | } 70 | }, 71 | "weekdays": { 72 | "1": { 73 | "short": "Δευ", 74 | "long": "Δευτέρα" 75 | }, 76 | "2": { 77 | "short": "Τρι", 78 | "long": "Τρίτη" 79 | }, 80 | "3": { 81 | "short": "Τετ", 82 | "long": "Τετάρτη" 83 | }, 84 | "4": { 85 | "short": "Πεμ", 86 | "long": "Πέμπτη" 87 | }, 88 | "5": { 89 | "short": "Παρ", 90 | "long": "Παρασκευή" 91 | }, 92 | "6": { 93 | "short": "Σαβ", 94 | "long": "Σάββατο" 95 | }, 96 | "7": { 97 | "short": "Κυρ", 98 | "long": "Κυριακή" 99 | } 100 | }, 101 | "moon_phases": { 102 | "1": "Νέα Σελήνη", 103 | "2": "Πρώτο Τέταρτο", 104 | "3": "Πανσέληνος", 105 | "4": "Τελευταίο Τέταρτο" 106 | }, 107 | "fastings": { 108 | "seasons": { 109 | "1": "Αʼ Εβδομάδα του Τριωδίου", 110 | "2": "Εβδομάδα της Τυροφάγου", 111 | "3": "Τριήμερο", 112 | "4": "Μεγάλη Τεσσαρακοστή", 113 | "5": "Μεγάλη Εβδομάδα", 114 | "6": "Διακαινήσιμος Εβδομάδα", 115 | "7": "Εβδομάδα του Αγίου Πνεύματος", 116 | "8": "Νηστεία Αγίων Αποστόλων", 117 | "9": "Νηστεία Δεκαπενταυγούστου", 118 | "10": "Νηστεία Χριστουγέννων", 119 | "11": "Δωδεκαήμερο", 120 | "12": "Κανονική Περίοδος" 121 | }, 122 | "levels": { 123 | "1": "Κατάλυσις εις πάντα", 124 | "2": "Κατάλυσις γαλακτοκομικών", 125 | "3": "Κατάλυσις ιχθύος", 126 | "4": "Κατάλυσις οίνου και ελαίου", 127 | "5": "Ξηροφαγία", 128 | "6": "Απόλυτη Νηστεία" 129 | } 130 | }, 131 | "sundays": { 132 | "descriptions": { 133 | "": "" 134 | }, 135 | "lectionaries": { 136 | "1": { 137 | "tone": "", 138 | "matins_gospel": "", 139 | "epistle": "", 140 | "gospel": "" 141 | } 142 | } 143 | }, 144 | "feasts": { 145 | "movable": { 146 | "major_feasts": { 147 | "": [] 148 | }, 149 | "minor_feasts": { 150 | "": [] 151 | } 152 | }, 153 | "fixed": { 154 | "great_feasts": { 155 | "": [] 156 | }, 157 | "middle_feasts": { 158 | "": [] 159 | }, 160 | "lesser_feasts": { 161 | "": [] 162 | } 163 | } 164 | }, 165 | "saints": { 166 | "common_saints": { 167 | "": [] 168 | }, 169 | "additional_saints": { 170 | "": [] 171 | }, 172 | "calendar_specific_saints": { 173 | "": [] 174 | } 175 | } 176 | } 177 | -------------------------------------------------------------------------------- /tests/core/test_fasting_season_05_old.py: -------------------------------------------------------------------------------- 1 | """Test for fasting subpackage.""" 2 | 3 | import pytest 4 | 5 | from ocma_data.core.fasting.logic import get_fasting 6 | from ocma_data.utils.date_utils import string_to_date 7 | 8 | test_cases = [ 9 | # Maximum range for each fasting season 10 | # Format: ["Calendar", "Start Month-Day", "End Month-Day"] 11 | # Fasting season 5: ["Old Calendar", "3-16", "4-24"] 12 | ( 13 | "2031-3-25", 14 | "old", 15 | { 16 | "fasting_season_index": "5", 17 | "fasting_laymen_index": "4", 18 | "fasting_monks_index": "4", 19 | }, 20 | ), # FISH_ALLOWED, Monday 21 | ( 22 | "2015-3-25", 23 | "old", 24 | { 25 | "fasting_season_index": "5", 26 | "fasting_laymen_index": "4", 27 | "fasting_monks_index": "4", 28 | }, 29 | ), # FISH_ALLOWED, Tuesday 30 | ( 31 | "2004-3-25", 32 | "old", 33 | { 34 | "fasting_season_index": "5", 35 | "fasting_laymen_index": "4", 36 | "fasting_monks_index": "4", 37 | }, 38 | ), # FISH_ALLOWED, Wednesday 39 | ( 40 | "2061-3-25", 41 | "old", 42 | { 43 | "fasting_season_index": "5", 44 | "fasting_laymen_index": "4", 45 | "fasting_monks_index": "4", 46 | }, 47 | ), # FISH_ALLOWED, Thursday 48 | ( 49 | "2034-3-25", 50 | "old", 51 | { 52 | "fasting_season_index": "5", 53 | "fasting_laymen_index": "5", 54 | "fasting_monks_index": "5", 55 | }, 56 | ), # FISH_ALLOWED, Friday 57 | ( 58 | "2018-3-25", 59 | "old", 60 | { 61 | "fasting_season_index": "5", 62 | "fasting_laymen_index": "5", 63 | "fasting_monks_index": "5", 64 | }, 65 | ), # FISH_ALLOWED, Saturday 66 | ( 67 | "2010-3-16", 68 | "old", 69 | { 70 | "fasting_season_index": "5", 71 | "fasting_laymen_index": "5", 72 | "fasting_monks_index": "5", 73 | }, 74 | ), # Monday 75 | ( 76 | "2010-3-17", 77 | "old", 78 | { 79 | "fasting_season_index": "5", 80 | "fasting_laymen_index": "5", 81 | "fasting_monks_index": "5", 82 | }, 83 | ), # Tuesday 84 | ( 85 | "2010-3-18", 86 | "old", 87 | { 88 | "fasting_season_index": "5", 89 | "fasting_laymen_index": "5", 90 | "fasting_monks_index": "5", 91 | }, 92 | ), # Wednesday 93 | ( 94 | "2010-3-19", 95 | "old", 96 | { 97 | "fasting_season_index": "5", 98 | "fasting_laymen_index": "5", 99 | "fasting_monks_index": "5", 100 | }, 101 | ), # Thursday 102 | ( 103 | "2010-3-20", 104 | "old", 105 | { 106 | "fasting_season_index": "5", 107 | "fasting_laymen_index": "6", 108 | "fasting_monks_index": "6", 109 | }, 110 | ), # Friday 111 | ( 112 | "2010-3-21", 113 | "old", 114 | { 115 | "fasting_season_index": "5", 116 | "fasting_laymen_index": "5", 117 | "fasting_monks_index": "5", 118 | }, 119 | ), # Saturday 120 | ( 121 | "2041-4-2", 122 | "old", 123 | { 124 | "fasting_season_index": "5", 125 | "fasting_laymen_index": "5", 126 | "fasting_monks_index": "5", 127 | }, 128 | ), # Monday 129 | ( 130 | "2041-4-7", 131 | "old", 132 | { 133 | "fasting_season_index": "5", 134 | "fasting_laymen_index": "5", 135 | "fasting_monks_index": "5", 136 | }, 137 | ), # Saturday 138 | ( 139 | "2078-4-19", 140 | "old", 141 | { 142 | "fasting_season_index": "5", 143 | "fasting_laymen_index": "5", 144 | "fasting_monks_index": "5", 145 | }, 146 | ), # Monday 147 | ( 148 | "2078-4-24", 149 | "old", 150 | { 151 | "fasting_season_index": "5", 152 | "fasting_laymen_index": "5", 153 | "fasting_monks_index": "5", 154 | }, 155 | ), # Saturday 156 | ] 157 | 158 | 159 | @pytest.mark.parametrize("current_date, calendar_style, expected", test_cases) 160 | def test_get_fasting(current_date: str, calendar_style: str, expected: str) -> None: 161 | """Test for get_fasting.""" 162 | assert get_fasting(string_to_date(current_date), calendar_style) == expected 163 | -------------------------------------------------------------------------------- /ocma_data/cli/main.py: -------------------------------------------------------------------------------- 1 | """Module providing a function generating JSON data for Calendar.""" 2 | 3 | import json 4 | from datetime import date 5 | from pathlib import Path 6 | 7 | from ocma_data.cli.constants import CLIConstants, JsonKeys 8 | from ocma_data.constants import CalendarStyles 9 | from ocma_data.core.date.logic import get_date 10 | from ocma_data.core.moon_phase.logic import get_moon_phase 11 | from ocma_data.core.pascha.logic import calculate_pascha 12 | from ocma_data.core.pascha_distance.logic import pascha_distance 13 | from ocma_data.core.weekday.logic import get_weekday 14 | from ocma_data.utils.date_utils import is_valid_date, string_to_date 15 | 16 | 17 | def main() -> None: 18 | """Generate JSON data for the Calendar.""" 19 | Path(CLIConstants().BUILD_FOLDER).mkdir(parents=True, exist_ok=True) 20 | Path(f"{CLIConstants().BUILD_FOLDER}/{CLIConstants().PASCHALION}").mkdir(parents=True, exist_ok=True) 21 | 22 | for calendar_style in CalendarStyles: 23 | Path(f"{CLIConstants().BUILD_FOLDER}/{calendar_style.value}").mkdir(parents=True, exist_ok=True) 24 | paschalion_data = {} 25 | paschalion_data[JsonKeys.PASCHA_DATE.value] = {} 26 | 27 | for current_year in range(CLIConstants().YEAR_START, CLIConstants().YEAR_END): 28 | paschalion_data[JsonKeys.PASCHA_DATE.value][current_year] = calculate_pascha( 29 | current_year, calendar_style.value 30 | ) 31 | 32 | with Path( 33 | f"{CLIConstants().BUILD_FOLDER}/{CLIConstants().PASCHALION}/{calendar_style.value}.json", 34 | ).open("w", encoding="utf-8") as json_file: 35 | json.dump(paschalion_data, json_file, indent=2) 36 | 37 | calendar_data = {} 38 | calendar_data[str(current_year)] = {} 39 | 40 | for current_month in range(1, 13): 41 | calendar_data[str(current_year)][str(current_month)] = {} 42 | 43 | for current_day in range(1, 32): 44 | if is_valid_date(current_year, current_month, current_day): 45 | current_date = date(current_year, current_month, current_day) 46 | date_old_new = get_date(current_date, calendar_style.value) 47 | 48 | calendar_data[str(current_year)][str(current_month)][str(current_day)] = {} 49 | calendar_data[str(current_year)][str(current_month)][str(current_day)][ 50 | JsonKeys.DATE_OLD.value 51 | ] = date_old_new[JsonKeys.DATE_OLD.value] 52 | calendar_data[str(current_year)][str(current_month)][str(current_day)][ 53 | JsonKeys.DATE_NEW.value 54 | ] = date_old_new[JsonKeys.DATE_NEW.value] 55 | calendar_data[str(current_year)][str(current_month)][str(current_day)][ 56 | JsonKeys.WEEKDAY_INDEX.value 57 | ] = get_weekday(current_date, calendar_style.value) 58 | calendar_data[str(current_year)][str(current_month)][str(current_day)][ 59 | JsonKeys.MOON_PHASE_INDEX.value 60 | ] = get_moon_phase( 61 | string_to_date(f"{current_year}-{current_month}-{current_day}"), 62 | calendar_style.value, 63 | ) 64 | calendar_data[str(current_year)][str(current_month)][str(current_day)][ 65 | JsonKeys.PASCHA_DISTANCE.value 66 | ] = pascha_distance(current_date, calendar_style.value) 67 | calendar_data[str(current_year)][str(current_month)][str(current_day)][ 68 | JsonKeys.FASTING_SEASON_INDEX.value 69 | ] = "" 70 | calendar_data[str(current_year)][str(current_month)][str(current_day)][ 71 | JsonKeys.FASTING_LAYMEN_INDEX.value 72 | ] = "" 73 | calendar_data[str(current_year)][str(current_month)][str(current_day)][ 74 | JsonKeys.FASTING_MONKS_INDEX.value 75 | ] = "" 76 | calendar_data[str(current_year)][str(current_month)][str(current_day)][ 77 | JsonKeys.SUNDAY_DESCRIPTION_GR_INDEX.value 78 | ] = "" 79 | calendar_data[str(current_year)][str(current_month)][str(current_day)][ 80 | JsonKeys.SUNDAY_DESCRIPTION_RO_INDEX.value 81 | ] = "" 82 | calendar_data[str(current_year)][str(current_month)][str(current_day)][ 83 | JsonKeys.SUNDAY_DESCRIPTION_RU_INDEX.value 84 | ] = "" 85 | calendar_data[str(current_year)][str(current_month)][str(current_day)][ 86 | JsonKeys.SUNDAY_LECTIONARY_INDEX.value 87 | ] = "" 88 | 89 | with Path( 90 | f"{CLIConstants().BUILD_FOLDER}/{calendar_style.value}/{current_year}.json", 91 | ).open("w", encoding="utf-8") as json_file: 92 | json.dump(calendar_data, json_file, indent=2) 93 | 94 | 95 | if __name__ == "__main__": 96 | main() 97 | -------------------------------------------------------------------------------- /tests/core/test_fasting_season_08_new.py: -------------------------------------------------------------------------------- 1 | """Test for fasting subpackage.""" 2 | 3 | import pytest 4 | 5 | from ocma_data.core.fasting.logic import get_fasting 6 | from ocma_data.utils.date_utils import string_to_date 7 | 8 | test_cases = [ 9 | # Maximum range for each fasting season 10 | # Format: ["Calendar", "Start Month-Day", "End Month-Day"] 11 | # Fasting season 8: ["New Calendar", "5-31", "6-28"] 12 | ( 13 | "2010-5-31", 14 | "new", 15 | { 16 | "fasting_season_index": "8", 17 | "fasting_laymen_index": "5", 18 | "fasting_monks_index": "5", 19 | }, 20 | ), # Monday 21 | ( 22 | "2010-6-5", 23 | "new", 24 | { 25 | "fasting_season_index": "8", 26 | "fasting_laymen_index": "3", 27 | "fasting_monks_index": "3", 28 | }, 29 | ), # Saturday 30 | ( 31 | "2010-6-6", 32 | "new", 33 | { 34 | "fasting_season_index": "8", 35 | "fasting_laymen_index": "3", 36 | "fasting_monks_index": "3", 37 | }, 38 | ), # Sunday 39 | ( 40 | "2010-6-8", 41 | "new", 42 | { 43 | "fasting_season_index": "8", 44 | "fasting_laymen_index": "4", 45 | "fasting_monks_index": "4", 46 | }, 47 | ), # WINE_AND_OLIVE_OIL_ALLOWED, Tuesday 48 | ( 49 | "2010-6-11", 50 | "new", 51 | { 52 | "fasting_season_index": "8", 53 | "fasting_laymen_index": "4", 54 | "fasting_monks_index": "4", 55 | }, 56 | ), # WINE_AND_OLIVE_OIL_ALLOWED, Friday 57 | ( 58 | "2010-6-19", 59 | "new", 60 | { 61 | "fasting_season_index": "8", 62 | "fasting_laymen_index": "3", 63 | "fasting_monks_index": "3", 64 | }, 65 | ), # Saturday 66 | ( 67 | "2010-6-20", 68 | "new", 69 | { 70 | "fasting_season_index": "8", 71 | "fasting_laymen_index": "3", 72 | "fasting_monks_index": "3", 73 | }, 74 | ), # Sunday 75 | ( 76 | "2010-6-24", 77 | "new", 78 | { 79 | "fasting_season_index": "8", 80 | "fasting_laymen_index": "3", 81 | "fasting_monks_index": "3", 82 | }, 83 | ), # FISH_ALLOWED, Thursday 84 | ( 85 | "2010-6-26", 86 | "new", 87 | { 88 | "fasting_season_index": "8", 89 | "fasting_laymen_index": "4", 90 | "fasting_monks_index": "4", 91 | }, 92 | ), # Saturday 93 | ( 94 | "2010-6-27", 95 | "new", 96 | { 97 | "fasting_season_index": "8", 98 | "fasting_laymen_index": "4", 99 | "fasting_monks_index": "4", 100 | }, 101 | ), # Sunday 102 | ( 103 | "2010-6-28", 104 | "new", 105 | { 106 | "fasting_season_index": "8", 107 | "fasting_laymen_index": "5", 108 | "fasting_monks_index": "5", 109 | }, 110 | ), # Monday 111 | ( 112 | "2041-6-17", 113 | "new", 114 | { 115 | "fasting_season_index": "8", 116 | "fasting_laymen_index": "5", 117 | "fasting_monks_index": "5", 118 | }, 119 | ), # Monday 120 | ( 121 | "2041-6-18", 122 | "new", 123 | { 124 | "fasting_season_index": "8", 125 | "fasting_laymen_index": "4", 126 | "fasting_monks_index": "4", 127 | }, 128 | ), # Tuesday 129 | ( 130 | "2041-6-19", 131 | "new", 132 | { 133 | "fasting_season_index": "8", 134 | "fasting_laymen_index": "5", 135 | "fasting_monks_index": "5", 136 | }, 137 | ), # Wednesday 138 | ( 139 | "2041-6-20", 140 | "new", 141 | { 142 | "fasting_season_index": "8", 143 | "fasting_laymen_index": "4", 144 | "fasting_monks_index": "4", 145 | }, 146 | ), # Thursday 147 | ( 148 | "2041-6-22", 149 | "new", 150 | { 151 | "fasting_season_index": "8", 152 | "fasting_laymen_index": "3", 153 | "fasting_monks_index": "3", 154 | }, 155 | ), # Saturday 156 | ( 157 | "2041-6-23", 158 | "new", 159 | { 160 | "fasting_season_index": "8", 161 | "fasting_laymen_index": "3", 162 | "fasting_monks_index": "3", 163 | }, 164 | ), # Sunday 165 | ( 166 | "2041-6-24", 167 | "new", 168 | { 169 | "fasting_season_index": "8", 170 | "fasting_laymen_index": "3", 171 | "fasting_monks_index": "3", 172 | }, 173 | ), # FISH_ALLOWED, Monday 174 | ( 175 | "2041-6-28", 176 | "new", 177 | { 178 | "fasting_season_index": "8", 179 | "fasting_laymen_index": "5", 180 | "fasting_monks_index": "5", 181 | }, 182 | ), # Friday 183 | ] 184 | 185 | 186 | @pytest.mark.parametrize("current_date, calendar_style, expected", test_cases) 187 | def test_get_fasting(current_date: str, calendar_style: str, expected: str) -> None: 188 | """Test for get_fasting.""" 189 | assert get_fasting(string_to_date(current_date), calendar_style) == expected 190 | -------------------------------------------------------------------------------- /tests/core/test_fasting_season_04_old.py: -------------------------------------------------------------------------------- 1 | """Test for fasting subpackage.""" 2 | 3 | import pytest 4 | 5 | from ocma_data.core.fasting.logic import get_fasting 6 | from ocma_data.utils.date_utils import string_to_date 7 | 8 | test_cases = [ 9 | # Maximum range for each fasting season 10 | # Format: ["Calendar", "Start Month-Day", "End Month-Day"] 11 | # Fasting season 4: ["Old Calendar", "2-4", "4-18"] 12 | ( 13 | "2024-3-25", 14 | "old", 15 | { 16 | "fasting_season_index": "4", 17 | "fasting_laymen_index": "3", 18 | "fasting_monks_index": "3", 19 | }, 20 | ), # FISH_ALLOWED, Sunday 21 | ( 22 | "2059-3-25", 23 | "old", 24 | { 25 | "fasting_season_index": "4", 26 | "fasting_laymen_index": "3", 27 | "fasting_monks_index": "3", 28 | }, 29 | ), # FISH_ALLOWED, Monday 30 | ( 31 | "2043-3-25", 32 | "old", 33 | { 34 | "fasting_season_index": "4", 35 | "fasting_laymen_index": "3", 36 | "fasting_monks_index": "3", 37 | }, 38 | ), # FISH_ALLOWED, Tuesday 39 | ( 40 | "2021-3-25", 41 | "old", 42 | { 43 | "fasting_season_index": "4", 44 | "fasting_laymen_index": "3", 45 | "fasting_monks_index": "3", 46 | }, 47 | ), # FISH_ALLOWED, Wednesday 48 | ( 49 | "2005-3-25", 50 | "old", 51 | { 52 | "fasting_season_index": "4", 53 | "fasting_laymen_index": "3", 54 | "fasting_monks_index": "3", 55 | }, 56 | ), # FISH_ALLOWED, Thursday 57 | ( 58 | "2000-3-25", 59 | "old", 60 | { 61 | "fasting_season_index": "4", 62 | "fasting_laymen_index": "3", 63 | "fasting_monks_index": "3", 64 | }, 65 | ), # FISH_ALLOWED, Friday 66 | ( 67 | "2035-3-25", 68 | "old", 69 | { 70 | "fasting_season_index": "4", 71 | "fasting_laymen_index": "3", 72 | "fasting_monks_index": "3", 73 | }, 74 | ), # FISH_ALLOWED, Saturday 75 | ( 76 | "2010-2-4", 77 | "old", 78 | { 79 | "fasting_season_index": "4", 80 | "fasting_laymen_index": "5", 81 | "fasting_monks_index": "5", 82 | }, 83 | ), # Wednesday 84 | ( 85 | "2010-2-7", 86 | "old", 87 | { 88 | "fasting_season_index": "4", 89 | "fasting_laymen_index": "4", 90 | "fasting_monks_index": "4", 91 | }, 92 | ), # Saturday 93 | ( 94 | "2010-2-8", 95 | "old", 96 | { 97 | "fasting_season_index": "4", 98 | "fasting_laymen_index": "4", 99 | "fasting_monks_index": "4", 100 | }, 101 | ), # WINE_AND_OLIVE_OIL_ALLOWED, Sunday 102 | ( 103 | "2010-3-9", 104 | "old", 105 | { 106 | "fasting_season_index": "4", 107 | "fasting_laymen_index": "4", 108 | "fasting_monks_index": "4", 109 | }, 110 | ), # WINE_AND_OLIVE_OIL_ALLOWED, Monday 111 | ( 112 | "2010-3-15", 113 | "old", 114 | { 115 | "fasting_season_index": "4", 116 | "fasting_laymen_index": "3", 117 | "fasting_monks_index": "3", 118 | }, 119 | ), # PALM_SUNDAY, Sunday 120 | ( 121 | "2041-2-21", 122 | "old", 123 | { 124 | "fasting_season_index": "4", 125 | "fasting_laymen_index": "5", 126 | "fasting_monks_index": "5", 127 | }, 128 | ), # Wednesday 129 | ( 130 | "2041-2-24", 131 | "old", 132 | { 133 | "fasting_season_index": "4", 134 | "fasting_laymen_index": "4", 135 | "fasting_monks_index": "4", 136 | }, 137 | ), # WINE_AND_OLIVE_OIL_ALLOWED, Saturday 138 | ( 139 | "2041-2-25", 140 | "old", 141 | { 142 | "fasting_season_index": "4", 143 | "fasting_laymen_index": "4", 144 | "fasting_monks_index": "4", 145 | }, 146 | ), # Sunday 147 | ( 148 | "2041-2-26", 149 | "old", 150 | { 151 | "fasting_season_index": "4", 152 | "fasting_laymen_index": "5", 153 | "fasting_monks_index": "5", 154 | }, 155 | ), # Monday 156 | ( 157 | "2041-2-27", 158 | "old", 159 | { 160 | "fasting_season_index": "4", 161 | "fasting_laymen_index": "5", 162 | "fasting_monks_index": "5", 163 | }, 164 | ), # Tuesday 165 | ( 166 | "2041-3-1", 167 | "old", 168 | { 169 | "fasting_season_index": "4", 170 | "fasting_laymen_index": "5", 171 | "fasting_monks_index": "5", 172 | }, 173 | ), # Thursday 174 | ( 175 | "2041-3-2", 176 | "old", 177 | { 178 | "fasting_season_index": "4", 179 | "fasting_laymen_index": "5", 180 | "fasting_monks_index": "5", 181 | }, 182 | ), # Friday 183 | ( 184 | "2041-4-1", 185 | "old", 186 | { 187 | "fasting_season_index": "4", 188 | "fasting_laymen_index": "3", 189 | "fasting_monks_index": "3", 190 | }, 191 | ), # PALM_SUNDAY, Sunday 192 | ( 193 | "2078-3-10", 194 | "old", 195 | { 196 | "fasting_season_index": "4", 197 | "fasting_laymen_index": "5", 198 | "fasting_monks_index": "5", 199 | }, 200 | ), # Wednesday 201 | ( 202 | "2078-4-18", 203 | "old", 204 | { 205 | "fasting_season_index": "4", 206 | "fasting_laymen_index": "3", 207 | "fasting_monks_index": "3", 208 | }, 209 | ), # PALM_SUNDAY, Sunday 210 | ] 211 | 212 | 213 | @pytest.mark.parametrize("current_date, calendar_style, expected", test_cases) 214 | def test_get_fasting(current_date: str, calendar_style: str, expected: str) -> None: 215 | """Test for get_fasting.""" 216 | assert get_fasting(string_to_date(current_date), calendar_style) == expected 217 | -------------------------------------------------------------------------------- /tests/core/test_fasting_season_04_new.py: -------------------------------------------------------------------------------- 1 | """Test for fasting subpackage.""" 2 | 3 | import pytest 4 | 5 | from ocma_data.core.fasting.logic import get_fasting 6 | from ocma_data.utils.date_utils import string_to_date 7 | 8 | test_cases = [ 9 | # Maximum range for each fasting season 10 | # Format: ["Calendar", "Start Month-Day", "End Month-Day"] 11 | # Fasting season 4: ["New Calendar", "2-17", "5-1"] 12 | ( 13 | "2040-3-25", 14 | "new", 15 | { 16 | "fasting_season_index": "4", 17 | "fasting_laymen_index": "3", 18 | "fasting_monks_index": "3", 19 | }, 20 | ), # FISH_ALLOWED, Sunday 21 | ( 22 | "2013-3-25", 23 | "new", 24 | { 25 | "fasting_season_index": "4", 26 | "fasting_laymen_index": "3", 27 | "fasting_monks_index": "3", 28 | }, 29 | ), # FISH_ALLOWED, Monday 30 | ( 31 | "2070-3-25", 32 | "new", 33 | { 34 | "fasting_season_index": "4", 35 | "fasting_laymen_index": "3", 36 | "fasting_monks_index": "3", 37 | }, 38 | ), # FISH_ALLOWED, Tuesday 39 | ( 40 | "2054-3-25", 41 | "new", 42 | { 43 | "fasting_season_index": "4", 44 | "fasting_laymen_index": "3", 45 | "fasting_monks_index": "3", 46 | }, 47 | ), # FISH_ALLOWED, Wednesday 48 | ( 49 | "2027-3-25", 50 | "new", 51 | { 52 | "fasting_season_index": "4", 53 | "fasting_laymen_index": "3", 54 | "fasting_monks_index": "3", 55 | }, 56 | ), # FISH_ALLOWED, Thursday 57 | ( 58 | "2016-3-25", 59 | "new", 60 | { 61 | "fasting_season_index": "4", 62 | "fasting_laymen_index": "3", 63 | "fasting_monks_index": "3", 64 | }, 65 | ), # FISH_ALLOWED, Friday 66 | ( 67 | "2062-3-25", 68 | "new", 69 | { 70 | "fasting_season_index": "4", 71 | "fasting_laymen_index": "3", 72 | "fasting_monks_index": "3", 73 | }, 74 | ), # FISH_ALLOWED, Saturday 75 | ( 76 | "2010-2-17", 77 | "new", 78 | { 79 | "fasting_season_index": "4", 80 | "fasting_laymen_index": "4", 81 | "fasting_monks_index": "4", 82 | }, 83 | ), # WINE_AND_OLIVE_OIL_ALLOWED, Wednesday 84 | ( 85 | "2010-3-9", 86 | "new", 87 | { 88 | "fasting_season_index": "4", 89 | "fasting_laymen_index": "4", 90 | "fasting_monks_index": "4", 91 | }, 92 | ), # WINE_AND_OLIVE_OIL_ALLOWED, Tuesday 93 | ( 94 | "2010-3-28", 95 | "new", 96 | { 97 | "fasting_season_index": "4", 98 | "fasting_laymen_index": "3", 99 | "fasting_monks_index": "3", 100 | }, 101 | ), # PALM_SUNDAY, Sunday 102 | ( 103 | "2041-3-6", 104 | "new", 105 | { 106 | "fasting_season_index": "4", 107 | "fasting_laymen_index": "5", 108 | "fasting_monks_index": "5", 109 | }, 110 | ), # Wednesday 111 | ( 112 | "2041-3-9", 113 | "new", 114 | { 115 | "fasting_season_index": "4", 116 | "fasting_laymen_index": "4", 117 | "fasting_monks_index": "4", 118 | }, 119 | ), # WINE_AND_OLIVE_OIL_ALLOWED, Saturday 120 | ( 121 | "2041-3-10", 122 | "new", 123 | { 124 | "fasting_season_index": "4", 125 | "fasting_laymen_index": "4", 126 | "fasting_monks_index": "4", 127 | }, 128 | ), # Sunday 129 | ( 130 | "2041-3-11", 131 | "new", 132 | { 133 | "fasting_season_index": "4", 134 | "fasting_laymen_index": "5", 135 | "fasting_monks_index": "5", 136 | }, 137 | ), # Monday 138 | ( 139 | "2041-3-12", 140 | "new", 141 | { 142 | "fasting_season_index": "4", 143 | "fasting_laymen_index": "5", 144 | "fasting_monks_index": "5", 145 | }, 146 | ), # Tuesday 147 | ( 148 | "2041-3-13", 149 | "new", 150 | { 151 | "fasting_season_index": "4", 152 | "fasting_laymen_index": "5", 153 | "fasting_monks_index": "5", 154 | }, 155 | ), # Wednesday 156 | ( 157 | "2041-3-14", 158 | "new", 159 | { 160 | "fasting_season_index": "4", 161 | "fasting_laymen_index": "5", 162 | "fasting_monks_index": "5", 163 | }, 164 | ), # Thursday 165 | ( 166 | "2041-3-15", 167 | "new", 168 | { 169 | "fasting_season_index": "4", 170 | "fasting_laymen_index": "5", 171 | "fasting_monks_index": "5", 172 | }, 173 | ), # Friday 174 | ( 175 | "2041-3-16", 176 | "new", 177 | { 178 | "fasting_season_index": "4", 179 | "fasting_laymen_index": "4", 180 | "fasting_monks_index": "4", 181 | }, 182 | ), # Saturday 183 | ( 184 | "2041-4-14", 185 | "new", 186 | { 187 | "fasting_season_index": "4", 188 | "fasting_laymen_index": "3", 189 | "fasting_monks_index": "3", 190 | }, 191 | ), # PALM_SUNDAY, Sunday 192 | ( 193 | "2078-3-23", 194 | "new", 195 | { 196 | "fasting_season_index": "4", 197 | "fasting_laymen_index": "5", 198 | "fasting_monks_index": "5", 199 | }, 200 | ), # Wednesday 201 | ( 202 | "2078-4-23", 203 | "new", 204 | { 205 | "fasting_season_index": "4", 206 | "fasting_laymen_index": "4", 207 | "fasting_monks_index": "4", 208 | }, 209 | ), # WINE_AND_OLIVE_OIL_ALLOWED, Saturday 210 | ( 211 | "2078-4-30", 212 | "new", 213 | { 214 | "fasting_season_index": "4", 215 | "fasting_laymen_index": "4", 216 | "fasting_monks_index": "4", 217 | }, 218 | ), # WINE_AND_OLIVE_OIL_ALLOWED, Saturday 219 | ( 220 | "2078-5-1", 221 | "new", 222 | { 223 | "fasting_season_index": "4", 224 | "fasting_laymen_index": "3", 225 | "fasting_monks_index": "3", 226 | }, 227 | ), # PALM_SUNDAY, Sunday 228 | ] 229 | 230 | 231 | @pytest.mark.parametrize("current_date, calendar_style, expected", test_cases) 232 | def test_get_fasting(current_date: str, calendar_style: str, expected: str) -> None: 233 | """Test for get_fasting.""" 234 | assert get_fasting(string_to_date(current_date), calendar_style) == expected 235 | -------------------------------------------------------------------------------- /tests/core/test_fasting_season_08_old.py: -------------------------------------------------------------------------------- 1 | """Test for fasting subpackage.""" 2 | 3 | import pytest 4 | 5 | from ocma_data.core.fasting.logic import get_fasting 6 | from ocma_data.utils.date_utils import string_to_date 7 | 8 | test_cases = [ 9 | # Maximum range for each fasting season 10 | # Format: ["Calendar", "Start Month-Day", "End Month-Day"] 11 | # Fasting season 8: ["Old Calendar", "5-18", "6-28"] 12 | ( 13 | "2010-5-18", 14 | "old", 15 | { 16 | "fasting_season_index": "8", 17 | "fasting_laymen_index": "5", 18 | "fasting_monks_index": "5", 19 | }, 20 | ), # Monday 21 | ( 22 | "2010-5-19", 23 | "old", 24 | { 25 | "fasting_season_index": "8", 26 | "fasting_laymen_index": "4", 27 | "fasting_monks_index": "4", 28 | }, 29 | ), # Tuesday 30 | ( 31 | "2010-5-20", 32 | "old", 33 | { 34 | "fasting_season_index": "8", 35 | "fasting_laymen_index": "5", 36 | "fasting_monks_index": "5", 37 | }, 38 | ), # Wednesday 39 | ( 40 | "2010-5-21", 41 | "old", 42 | { 43 | "fasting_season_index": "8", 44 | "fasting_laymen_index": "4", 45 | "fasting_monks_index": "4", 46 | }, 47 | ), # WINE_AND_OLIVE_OIL_ALLOWED, Thursday 48 | ( 49 | "2010-5-22", 50 | "old", 51 | { 52 | "fasting_season_index": "8", 53 | "fasting_laymen_index": "5", 54 | "fasting_monks_index": "5", 55 | }, 56 | ), # Friday 57 | ( 58 | "2010-5-23", 59 | "old", 60 | { 61 | "fasting_season_index": "8", 62 | "fasting_laymen_index": "3", 63 | "fasting_monks_index": "3", 64 | }, 65 | ), # Saturday 66 | ( 67 | "2010-5-24", 68 | "old", 69 | { 70 | "fasting_season_index": "8", 71 | "fasting_laymen_index": "3", 72 | "fasting_monks_index": "3", 73 | }, 74 | ), # Sunday 75 | ( 76 | "2010-5-25", 77 | "old", 78 | { 79 | "fasting_season_index": "8", 80 | "fasting_laymen_index": "4", 81 | "fasting_monks_index": "4", 82 | }, 83 | ), # WINE_AND_OLIVE_OIL_ALLOWED, Monday 84 | ( 85 | "2010-6-8", 86 | "old", 87 | { 88 | "fasting_season_index": "8", 89 | "fasting_laymen_index": "4", 90 | "fasting_monks_index": "4", 91 | }, 92 | ), # WINE_AND_OLIVE_OIL_ALLOWED, Monday 93 | ( 94 | "2010-6-11", 95 | "old", 96 | { 97 | "fasting_season_index": "8", 98 | "fasting_laymen_index": "4", 99 | "fasting_monks_index": "4", 100 | }, 101 | ), # WINE_AND_OLIVE_OIL_ALLOWED, Thursday 102 | ( 103 | "2010-6-20", 104 | "old", 105 | { 106 | "fasting_season_index": "8", 107 | "fasting_laymen_index": "3", 108 | "fasting_monks_index": "3", 109 | }, 110 | ), # Saturday 111 | ( 112 | "2010-6-21", 113 | "old", 114 | { 115 | "fasting_season_index": "8", 116 | "fasting_laymen_index": "3", 117 | "fasting_monks_index": "3", 118 | }, 119 | ), # Sunday 120 | ( 121 | "2010-6-24", 122 | "old", 123 | { 124 | "fasting_season_index": "8", 125 | "fasting_laymen_index": "3", 126 | "fasting_monks_index": "3", 127 | }, 128 | ), # FISH_ALLOWED, Wednesday 129 | ( 130 | "2010-6-27", 131 | "old", 132 | { 133 | "fasting_season_index": "8", 134 | "fasting_laymen_index": "4", 135 | "fasting_monks_index": "4", 136 | }, 137 | ), # Saturday 138 | ( 139 | "2010-6-28", 140 | "old", 141 | { 142 | "fasting_season_index": "8", 143 | "fasting_laymen_index": "4", 144 | "fasting_monks_index": "4", 145 | }, 146 | ), # Sunday 147 | ( 148 | "2041-6-4", 149 | "old", 150 | { 151 | "fasting_season_index": "8", 152 | "fasting_laymen_index": "5", 153 | "fasting_monks_index": "5", 154 | }, 155 | ), # Monday 156 | ( 157 | "2041-6-8", 158 | "old", 159 | { 160 | "fasting_season_index": "8", 161 | "fasting_laymen_index": "4", 162 | "fasting_monks_index": "4", 163 | }, 164 | ), # WINE_AND_OLIVE_OIL_ALLOWED, Friday 165 | ( 166 | "2041-6-9", 167 | "old", 168 | { 169 | "fasting_season_index": "8", 170 | "fasting_laymen_index": "3", 171 | "fasting_monks_index": "3", 172 | }, 173 | ), # Saturday 174 | ( 175 | "2041-6-10", 176 | "old", 177 | { 178 | "fasting_season_index": "8", 179 | "fasting_laymen_index": "3", 180 | "fasting_monks_index": "3", 181 | }, 182 | ), # Sunday 183 | ( 184 | "2041-6-11", 185 | "old", 186 | { 187 | "fasting_season_index": "8", 188 | "fasting_laymen_index": "4", 189 | "fasting_monks_index": "4", 190 | }, 191 | ), # WINE_AND_OLIVE_OIL_ALLOWED, Monday 192 | ( 193 | "2041-6-23", 194 | "old", 195 | { 196 | "fasting_season_index": "8", 197 | "fasting_laymen_index": "3", 198 | "fasting_monks_index": "3", 199 | }, 200 | ), # Saturday 201 | ( 202 | "2041-6-24", 203 | "old", 204 | { 205 | "fasting_season_index": "8", 206 | "fasting_laymen_index": "3", 207 | "fasting_monks_index": "3", 208 | }, 209 | ), # FISH_ALLOWED, Sunday 210 | ( 211 | "2041-6-28", 212 | "old", 213 | { 214 | "fasting_season_index": "8", 215 | "fasting_laymen_index": "4", 216 | "fasting_monks_index": "4", 217 | }, 218 | ), # Thursday 219 | ( 220 | "2078-6-21", 221 | "old", 222 | { 223 | "fasting_season_index": "8", 224 | "fasting_laymen_index": "5", 225 | "fasting_monks_index": "5", 226 | }, 227 | ), # Monday 228 | ( 229 | "2078-6-24", 230 | "old", 231 | { 232 | "fasting_season_index": "8", 233 | "fasting_laymen_index": "3", 234 | "fasting_monks_index": "3", 235 | }, 236 | ), # FISH_ALLOWED, Thursday 237 | ( 238 | "2078-6-26", 239 | "old", 240 | { 241 | "fasting_season_index": "8", 242 | "fasting_laymen_index": "4", 243 | "fasting_monks_index": "4", 244 | }, 245 | ), # Saturday 246 | ( 247 | "2078-6-27", 248 | "old", 249 | { 250 | "fasting_season_index": "8", 251 | "fasting_laymen_index": "4", 252 | "fasting_monks_index": "4", 253 | }, 254 | ), # Sunday 255 | ( 256 | "2078-6-28", 257 | "old", 258 | { 259 | "fasting_season_index": "8", 260 | "fasting_laymen_index": "5", 261 | "fasting_monks_index": "5", 262 | }, 263 | ), # Monday 264 | ] 265 | 266 | 267 | @pytest.mark.parametrize("current_date, calendar_style, expected", test_cases) 268 | def test_get_fasting(current_date: str, calendar_style: str, expected: str) -> None: 269 | """Test for get_fasting.""" 270 | assert get_fasting(string_to_date(current_date), calendar_style) == expected 271 | -------------------------------------------------------------------------------- /uv.lock: -------------------------------------------------------------------------------- 1 | version = 1 2 | revision = 1 3 | requires-python = ">=3.11" 4 | 5 | [[package]] 6 | name = "colorama" 7 | version = "0.4.6" 8 | source = { registry = "https://pypi.org/simple" } 9 | sdist = { url = "https://files.pythonhosted.org/packages/d8/53/6f443c9a4a8358a93a6792e2acffb9d9d5cb0a5cfd8802644b7b1c9a02e4/colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44", size = 27697 } 10 | wheels = [ 11 | { url = "https://files.pythonhosted.org/packages/d1/d6/3965ed04c63042e047cb6a3e6ed1a63a35087b6a609aa3a15ed8ac56c221/colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6", size = 25335 }, 12 | ] 13 | 14 | [[package]] 15 | name = "iniconfig" 16 | version = "2.1.0" 17 | source = { registry = "https://pypi.org/simple" } 18 | sdist = { url = "https://files.pythonhosted.org/packages/f2/97/ebf4da567aa6827c909642694d71c9fcf53e5b504f2d96afea02718862f3/iniconfig-2.1.0.tar.gz", hash = "sha256:3abbd2e30b36733fee78f9c7f7308f2d0050e88f0087fd25c2645f63c773e1c7", size = 4793 } 19 | wheels = [ 20 | { url = "https://files.pythonhosted.org/packages/2c/e1/e6716421ea10d38022b952c159d5161ca1193197fb744506875fbb87ea7b/iniconfig-2.1.0-py3-none-any.whl", hash = "sha256:9deba5723312380e77435581c6bf4935c94cbfab9b1ed33ef8d238ea168eb760", size = 6050 }, 21 | ] 22 | 23 | [[package]] 24 | name = "ocma-data" 25 | version = "0.7.16a0" 26 | source = { virtual = "." } 27 | 28 | [package.dev-dependencies] 29 | dev = [ 30 | { name = "pytest" }, 31 | { name = "ruff" }, 32 | ] 33 | 34 | [package.metadata] 35 | 36 | [package.metadata.requires-dev] 37 | dev = [ 38 | { name = "pytest", specifier = ">=8.3.5" }, 39 | { name = "ruff", specifier = ">=0.11.1" }, 40 | ] 41 | 42 | [[package]] 43 | name = "packaging" 44 | version = "25.0" 45 | source = { registry = "https://pypi.org/simple" } 46 | sdist = { url = "https://files.pythonhosted.org/packages/a1/d4/1fc4078c65507b51b96ca8f8c3ba19e6a61c8253c72794544580a7b6c24d/packaging-25.0.tar.gz", hash = "sha256:d443872c98d677bf60f6a1f2f8c1cb748e8fe762d2bf9d3148b5599295b0fc4f", size = 165727 } 47 | wheels = [ 48 | { url = "https://files.pythonhosted.org/packages/20/12/38679034af332785aac8774540895e234f4d07f7545804097de4b666afd8/packaging-25.0-py3-none-any.whl", hash = "sha256:29572ef2b1f17581046b3a2227d5c611fb25ec70ca1ba8554b24b0e69331a484", size = 66469 }, 49 | ] 50 | 51 | [[package]] 52 | name = "pluggy" 53 | version = "1.5.0" 54 | source = { registry = "https://pypi.org/simple" } 55 | sdist = { url = "https://files.pythonhosted.org/packages/96/2d/02d4312c973c6050a18b314a5ad0b3210edb65a906f868e31c111dede4a6/pluggy-1.5.0.tar.gz", hash = "sha256:2cffa88e94fdc978c4c574f15f9e59b7f4201d439195c3715ca9e2486f1d0cf1", size = 67955 } 56 | wheels = [ 57 | { url = "https://files.pythonhosted.org/packages/88/5f/e351af9a41f866ac3f1fac4ca0613908d9a41741cfcf2228f4ad853b697d/pluggy-1.5.0-py3-none-any.whl", hash = "sha256:44e1ad92c8ca002de6377e165f3e0f1be63266ab4d554740532335b9d75ea669", size = 20556 }, 58 | ] 59 | 60 | [[package]] 61 | name = "pytest" 62 | version = "8.3.5" 63 | source = { registry = "https://pypi.org/simple" } 64 | dependencies = [ 65 | { name = "colorama", marker = "sys_platform == 'win32'" }, 66 | { name = "iniconfig" }, 67 | { name = "packaging" }, 68 | { name = "pluggy" }, 69 | ] 70 | sdist = { url = "https://files.pythonhosted.org/packages/ae/3c/c9d525a414d506893f0cd8a8d0de7706446213181570cdbd766691164e40/pytest-8.3.5.tar.gz", hash = "sha256:f4efe70cc14e511565ac476b57c279e12a855b11f48f212af1080ef2263d3845", size = 1450891 } 71 | wheels = [ 72 | { url = "https://files.pythonhosted.org/packages/30/3d/64ad57c803f1fa1e963a7946b6e0fea4a70df53c1a7fed304586539c2bac/pytest-8.3.5-py3-none-any.whl", hash = "sha256:c69214aa47deac29fad6c2a4f590b9c4a9fdb16a403176fe154b79c0b4d4d820", size = 343634 }, 73 | ] 74 | 75 | [[package]] 76 | name = "ruff" 77 | version = "0.11.1" 78 | source = { registry = "https://pypi.org/simple" } 79 | sdist = { url = "https://files.pythonhosted.org/packages/a0/ab/be73f46695c350147ffbfd98d42f34bfe370e6b4146bb0bfdc5b3c636fc7/ruff-0.11.1.tar.gz", hash = "sha256:f2e209a283c9fa423e268cad015ec4fb249178608f755fb67491ff175ecbffbf", size = 3850423 } 80 | wheels = [ 81 | { url = "https://files.pythonhosted.org/packages/de/09/fa72b32fb7a1f670c8d3cb925910447c496117850c6aa1601365ad11cd00/ruff-0.11.1-py3-none-linux_armv6l.whl", hash = "sha256:9c833671aaefcbe280aa54da387264402ffbb1e513ff3420c9c7265ea56d6c5c", size = 10114855 }, 82 | { url = "https://files.pythonhosted.org/packages/fa/c2/544da7ee0bf0a28117218065ccac69683d8d7a885544e6c84076cf72351b/ruff-0.11.1-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:a5a57cd457764228c73066b832040728b02a3837c53c8a781a960b68129c4e0b", size = 10870287 }, 83 | { url = "https://files.pythonhosted.org/packages/ee/56/0d1c0a3f3ab644e813f365c7eee5d0f9b75e755c5642400cd6ad7070f730/ruff-0.11.1-py3-none-macosx_11_0_arm64.whl", hash = "sha256:da91da0d42e70cd8bda8e6687fab2afd28513a3cc9434539f4149610e63baf8f", size = 10223642 }, 84 | { url = "https://files.pythonhosted.org/packages/d6/7c/88f06c23018ca02b8b0f78b62e4c701faa2ef6413d9f5e82277bc094d2e9/ruff-0.11.1-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:429a2e533e3a0dba2ba7e0608a736e728150aa9b6d179245aa11a1339baa968b", size = 10384382 }, 85 | { url = "https://files.pythonhosted.org/packages/67/55/15d2a355e4c64a712938df4c650d7239128865981eb8314a49782a53ef45/ruff-0.11.1-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:6bbcc2984a4d5cbc0f7b10409e74a00a043be45d813e5e81eb58e707455df7f1", size = 9965192 }, 86 | { url = "https://files.pythonhosted.org/packages/39/e8/4312a514fac213e90e5b45120339dbaa95a396f4cf3ab740e757fed4fa35/ruff-0.11.1-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:88d9c283ebc88faa5bc23fa33f399b6d47a93f5980c92edcddf1f2127aa376b3", size = 11563312 }, 87 | { url = "https://files.pythonhosted.org/packages/69/ff/4da09bea58e1d784f45169fd281643422727a1c0776802cd6d4b42a0037d/ruff-0.11.1-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:1f2b03d504516d6b22065ce7fac2564dac15d79a6a776452dabfdd7673a45b07", size = 12223815 }, 88 | { url = "https://files.pythonhosted.org/packages/ec/ba/7e3550f1478a53c4f7efcb0c31a03256eb032a871026da61324ad06dcd0b/ruff-0.11.1-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:52b95a9071f5ad8552af890bd814c6a04daf5b27297ac1054e3667016f3ab739", size = 11662551 }, 89 | { url = "https://files.pythonhosted.org/packages/7d/69/7e2f1c8a0c76de18f9c054382d31c6d5f9b55a3e476b2630fefe289d31a6/ruff-0.11.1-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:28e2d89e7ba8a1525cdb50bc86c07aba35e7bbeef86dad93781b14ad94dc732c", size = 13792940 }, 90 | { url = "https://files.pythonhosted.org/packages/02/4b/14f54bbf8f8dd03cb9a8a0294e7de1dc41f3be1806af07b8a104761db597/ruff-0.11.1-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e76be5a98dc6c29d85dfa72eb419e8d9276ee96ccf5c33f2b6828001907dcb17", size = 11328700 }, 91 | { url = "https://files.pythonhosted.org/packages/f5/0c/40c1ba7e9a8c2c7325267c214746b80da1b54fb7e70a9265608213f4b2fb/ruff-0.11.1-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:441f94c44fe250691c92382ef84f40acef290766fb3e819a9035e83eadd4dbbe", size = 10272650 }, 92 | { url = "https://files.pythonhosted.org/packages/a3/27/f86b786e0153c5b52768370f395086c4523dee1af37757a8ef73779b9fbd/ruff-0.11.1-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:62882a4cc7c0a48c2f34189bd4c7ba45f3d0efb990e02413eeb180aa042a39ca", size = 9973454 }, 93 | { url = "https://files.pythonhosted.org/packages/ab/46/47bf0c7ee80b0476226865d724ee7682323a2cb20aa5d134b8a665f58793/ruff-0.11.1-py3-none-musllinux_1_2_i686.whl", hash = "sha256:111dbad1706d8200a7138237b4766b45ba7ee45cc8299c02102f4327624f86a2", size = 10975303 }, 94 | { url = "https://files.pythonhosted.org/packages/9d/9d/deea8ece99f31aef07f378f83b5571baca74dff0d28fec720e54b171b862/ruff-0.11.1-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:e2df41763d7a9fd438b6b7bde7b75eb3a92ef2f4682ed2d8e4b997b5f0c76ca9", size = 11402605 }, 95 | { url = "https://files.pythonhosted.org/packages/b8/8c/f11e21f4a5ce0858c35745d43e9a5abd870ed33e151ada23c124bf433e71/ruff-0.11.1-py3-none-win32.whl", hash = "sha256:e17b85919d461583aa7e0171bb4f419a6545b261ca080984db49b1f8dced1d4b", size = 10335500 }, 96 | { url = "https://files.pythonhosted.org/packages/21/c9/ac2784bad48c7a8702d2f13851b69b2a08534b59657ee2b724f42aaaf861/ruff-0.11.1-py3-none-win_amd64.whl", hash = "sha256:caa872941b876f7ad73abc60144f9a7f6efb575e4f91c4fc1517f0339bcea01e", size = 11365097 }, 97 | { url = "https://files.pythonhosted.org/packages/9e/7d/f37251c102cf155cdf664322f1f362f4fd8fe1eb6870e9154255d3cbf0d8/ruff-0.11.1-py3-none-win_arm64.whl", hash = "sha256:7aa939fa57ef6770d18bd5cf0d6de77198dd762a559bd0d4a8763bdae4c8cc16", size = 10510306 }, 98 | ] 99 | -------------------------------------------------------------------------------- /tests/core/test_fasting_season_10_new.py: -------------------------------------------------------------------------------- 1 | """Test for fasting subpackage.""" 2 | 3 | import pytest 4 | 5 | from ocma_data.core.fasting.logic import get_fasting 6 | from ocma_data.utils.date_utils import string_to_date 7 | 8 | test_cases = [ 9 | # Maximum range for each fasting season 10 | # Format: ["Calendar", "Start Month-Day", "End Month-Day"] 11 | # Fasting season 10: ["New Calendar", "11-15", "12-24"] 12 | ( 13 | "1924-11-15", 14 | "new", 15 | { 16 | "fasting_season_index": "10", 17 | "fasting_laymen_index": "4", 18 | "fasting_monks_index": "4", 19 | }, 20 | ), # Saturday 21 | ( 22 | "1924-11-16", 23 | "new", 24 | { 25 | "fasting_season_index": "10", 26 | "fasting_laymen_index": "4", 27 | "fasting_monks_index": "4", 28 | }, 29 | ), # WINE_AND_OLIVE_OIL_ALLOWED, Sunday 30 | ( 31 | "1924-11-17", 32 | "new", 33 | { 34 | "fasting_season_index": "10", 35 | "fasting_laymen_index": "5", 36 | "fasting_monks_index": "5", 37 | }, 38 | ), # Monday 39 | ( 40 | "1924-11-18", 41 | "new", 42 | { 43 | "fasting_season_index": "10", 44 | "fasting_laymen_index": "4", 45 | "fasting_monks_index": "4", 46 | }, 47 | ), # Tuesday 48 | ( 49 | "1924-11-19", 50 | "new", 51 | { 52 | "fasting_season_index": "10", 53 | "fasting_laymen_index": "5", 54 | "fasting_monks_index": "5", 55 | }, 56 | ), # Wednesday 57 | ( 58 | "1924-11-20", 59 | "new", 60 | { 61 | "fasting_season_index": "10", 62 | "fasting_laymen_index": "4", 63 | "fasting_monks_index": "4", 64 | }, 65 | ), # Thursday 66 | ( 67 | "1924-11-21", 68 | "new", 69 | { 70 | "fasting_season_index": "10", 71 | "fasting_laymen_index": "3", 72 | "fasting_monks_index": "3", 73 | }, 74 | ), # FISH_ALLOWED, Friday 75 | ( 76 | "1924-11-22", 77 | "new", 78 | { 79 | "fasting_season_index": "10", 80 | "fasting_laymen_index": "3", 81 | "fasting_monks_index": "3", 82 | }, 83 | ), # Saturday 84 | ( 85 | "1924-11-23", 86 | "new", 87 | { 88 | "fasting_season_index": "10", 89 | "fasting_laymen_index": "3", 90 | "fasting_monks_index": "3", 91 | }, 92 | ), # Sunday 93 | ( 94 | "1924-11-25", 95 | "new", 96 | { 97 | "fasting_season_index": "10", 98 | "fasting_laymen_index": "4", 99 | "fasting_monks_index": "4", 100 | }, 101 | ), # WINE_AND_OLIVE_OIL_ALLOWED, Tuesday 102 | ( 103 | "1924-11-30", 104 | "new", 105 | { 106 | "fasting_season_index": "10", 107 | "fasting_laymen_index": "3", 108 | "fasting_monks_index": "3", 109 | }, 110 | ), # WINE_AND_OLIVE_OIL_ALLOWED, Sunday 111 | ( 112 | "1924-12-4", 113 | "new", 114 | { 115 | "fasting_season_index": "10", 116 | "fasting_laymen_index": "4", 117 | "fasting_monks_index": "4", 118 | }, 119 | ), # WINE_AND_OLIVE_OIL_ALLOWED, Thursday 120 | ( 121 | "1924-12-5", 122 | "new", 123 | { 124 | "fasting_season_index": "10", 125 | "fasting_laymen_index": "4", 126 | "fasting_monks_index": "4", 127 | }, 128 | ), # WINE_AND_OLIVE_OIL_ALLOWED, Friday 129 | ( 130 | "1924-12-6", 131 | "new", 132 | { 133 | "fasting_season_index": "10", 134 | "fasting_laymen_index": "3", 135 | "fasting_monks_index": "3", 136 | }, 137 | ), # WINE_AND_OLIVE_OIL_ALLOWED, Saturday 138 | ( 139 | "1924-12-7", 140 | "new", 141 | { 142 | "fasting_season_index": "10", 143 | "fasting_laymen_index": "3", 144 | "fasting_monks_index": "3", 145 | }, 146 | ), # Sunday 147 | ( 148 | "1924-12-9", 149 | "new", 150 | { 151 | "fasting_season_index": "10", 152 | "fasting_laymen_index": "4", 153 | "fasting_monks_index": "4", 154 | }, 155 | ), # WINE_AND_OLIVE_OIL_ALLOWED, Tuesday 156 | ( 157 | "1924-12-12", 158 | "new", 159 | { 160 | "fasting_season_index": "10", 161 | "fasting_laymen_index": "4", 162 | "fasting_monks_index": "4", 163 | }, 164 | ), # WINE_AND_OLIVE_OIL_ALLOWED, Friday 165 | ( 166 | "1924-12-13", 167 | "new", 168 | { 169 | "fasting_season_index": "10", 170 | "fasting_laymen_index": "4", 171 | "fasting_monks_index": "4", 172 | }, 173 | ), # Saturday 174 | ( 175 | "1924-12-14", 176 | "new", 177 | { 178 | "fasting_season_index": "10", 179 | "fasting_laymen_index": "4", 180 | "fasting_monks_index": "4", 181 | }, 182 | ), # Sunday 183 | ( 184 | "1924-12-15", 185 | "new", 186 | { 187 | "fasting_season_index": "10", 188 | "fasting_laymen_index": "4", 189 | "fasting_monks_index": "4", 190 | }, 191 | ), # WINE_AND_OLIVE_OIL_ALLOWED, Monday 192 | ( 193 | "1924-12-17", 194 | "new", 195 | { 196 | "fasting_season_index": "10", 197 | "fasting_laymen_index": "4", 198 | "fasting_monks_index": "4", 199 | }, 200 | ), # WINE_AND_OLIVE_OIL_ALLOWED, Wednesday 201 | ( 202 | "1924-12-20", 203 | "new", 204 | { 205 | "fasting_season_index": "10", 206 | "fasting_laymen_index": "4", 207 | "fasting_monks_index": "4", 208 | }, 209 | ), # WINE_AND_OLIVE_OIL_ALLOWED, Saturday 210 | ( 211 | "1924-12-21", 212 | "new", 213 | { 214 | "fasting_season_index": "10", 215 | "fasting_laymen_index": "4", 216 | "fasting_monks_index": "4", 217 | }, 218 | ), # Sunday 219 | ( 220 | "1924-12-24", 221 | "new", 222 | { 223 | "fasting_season_index": "10", 224 | "fasting_laymen_index": "5", 225 | "fasting_monks_index": "5", 226 | }, 227 | ), # STRICT_FAST, Wednesday 228 | ( 229 | "2099-11-15", 230 | "new", 231 | { 232 | "fasting_season_index": "10", 233 | "fasting_laymen_index": "4", 234 | "fasting_monks_index": "4", 235 | }, 236 | ), # Sunday 237 | ( 238 | "2099-11-16", 239 | "new", 240 | { 241 | "fasting_season_index": "10", 242 | "fasting_laymen_index": "4", 243 | "fasting_monks_index": "4", 244 | }, 245 | ), # WINE_AND_OLIVE_OIL_ALLOWED, Monday 246 | ( 247 | "2099-11-21", 248 | "new", 249 | { 250 | "fasting_season_index": "10", 251 | "fasting_laymen_index": "3", 252 | "fasting_monks_index": "3", 253 | }, 254 | ), # FISH_ALLOWED, Saturday 255 | ( 256 | "2099-11-22", 257 | "new", 258 | { 259 | "fasting_season_index": "10", 260 | "fasting_laymen_index": "3", 261 | "fasting_monks_index": "3", 262 | }, 263 | ), # Sunday 264 | ( 265 | "2099-11-25", 266 | "new", 267 | { 268 | "fasting_season_index": "10", 269 | "fasting_laymen_index": "4", 270 | "fasting_monks_index": "4", 271 | }, 272 | ), # WINE_AND_OLIVE_OIL_ALLOWED, Wednesday 273 | ( 274 | "2099-11-30", 275 | "new", 276 | { 277 | "fasting_season_index": "10", 278 | "fasting_laymen_index": "4", 279 | "fasting_monks_index": "4", 280 | }, 281 | ), # WINE_AND_OLIVE_OIL_ALLOWED, Monday 282 | ( 283 | "2099-12-4", 284 | "new", 285 | { 286 | "fasting_season_index": "10", 287 | "fasting_laymen_index": "4", 288 | "fasting_monks_index": "4", 289 | }, 290 | ), # WINE_AND_OLIVE_OIL_ALLOWED, Friday 291 | ( 292 | "2099-12-5", 293 | "new", 294 | { 295 | "fasting_season_index": "10", 296 | "fasting_laymen_index": "3", 297 | "fasting_monks_index": "3", 298 | }, 299 | ), # WINE_AND_OLIVE_OIL_ALLOWED, Saturday 300 | ( 301 | "2099-12-6", 302 | "new", 303 | { 304 | "fasting_season_index": "10", 305 | "fasting_laymen_index": "3", 306 | "fasting_monks_index": "3", 307 | }, 308 | ), # WINE_AND_OLIVE_OIL_ALLOWED, Sunday 309 | ( 310 | "2099-12-9", 311 | "new", 312 | { 313 | "fasting_season_index": "10", 314 | "fasting_laymen_index": "4", 315 | "fasting_monks_index": "4", 316 | }, 317 | ), # WINE_AND_OLIVE_OIL_ALLOWED, Wednesday 318 | ( 319 | "2099-12-11", 320 | "new", 321 | { 322 | "fasting_season_index": "10", 323 | "fasting_laymen_index": "5", 324 | "fasting_monks_index": "5", 325 | }, 326 | ), # Friday 327 | ( 328 | "2099-12-12", 329 | "new", 330 | { 331 | "fasting_season_index": "10", 332 | "fasting_laymen_index": "3", 333 | "fasting_monks_index": "3", 334 | }, 335 | ), # WINE_AND_OLIVE_OIL_ALLOWED, Saturday 336 | ( 337 | "2099-12-13", 338 | "new", 339 | { 340 | "fasting_season_index": "10", 341 | "fasting_laymen_index": "4", 342 | "fasting_monks_index": "4", 343 | }, 344 | ), # Sunday 345 | ( 346 | "2099-12-15", 347 | "new", 348 | { 349 | "fasting_season_index": "10", 350 | "fasting_laymen_index": "4", 351 | "fasting_monks_index": "4", 352 | }, 353 | ), # WINE_AND_OLIVE_OIL_ALLOWED, Tuesday 354 | ( 355 | "2099-12-17", 356 | "new", 357 | { 358 | "fasting_season_index": "10", 359 | "fasting_laymen_index": "4", 360 | "fasting_monks_index": "4", 361 | }, 362 | ), # WINE_AND_OLIVE_OIL_ALLOWED, Thursday 363 | ( 364 | "2099-12-19", 365 | "new", 366 | { 367 | "fasting_season_index": "10", 368 | "fasting_laymen_index": "4", 369 | "fasting_monks_index": "4", 370 | }, 371 | ), # Saturday 372 | ( 373 | "2099-12-20", 374 | "new", 375 | { 376 | "fasting_season_index": "10", 377 | "fasting_laymen_index": "4", 378 | "fasting_monks_index": "4", 379 | }, 380 | ), # WINE_AND_OLIVE_OIL_ALLOWED, Sunday 381 | ( 382 | "2018-12-24", 383 | "new", 384 | { 385 | "fasting_season_index": "10", 386 | "fasting_laymen_index": "5", 387 | "fasting_monks_index": "5", 388 | }, 389 | ), # STRICT_FAST, Monday 390 | ( 391 | "2019-12-24", 392 | "new", 393 | { 394 | "fasting_season_index": "10", 395 | "fasting_laymen_index": "5", 396 | "fasting_monks_index": "5", 397 | }, 398 | ), # STRICT_FAST, Tuesday 399 | ( 400 | "2099-12-24", 401 | "new", 402 | { 403 | "fasting_season_index": "10", 404 | "fasting_laymen_index": "5", 405 | "fasting_monks_index": "5", 406 | }, 407 | ), # STRICT_FAST, Thursday 408 | ( 409 | "2021-12-24", 410 | "new", 411 | { 412 | "fasting_season_index": "10", 413 | "fasting_laymen_index": "5", 414 | "fasting_monks_index": "5", 415 | }, 416 | ), # STRICT_FAST, Friday 417 | ( 418 | "2022-12-24", 419 | "new", 420 | { 421 | "fasting_season_index": "10", 422 | "fasting_laymen_index": "4", 423 | "fasting_monks_index": "4", 424 | }, 425 | ), # STRICT_FAST, Saturday 426 | ( 427 | "2023-12-24", 428 | "new", 429 | { 430 | "fasting_season_index": "10", 431 | "fasting_laymen_index": "4", 432 | "fasting_monks_index": "4", 433 | }, 434 | ), # STRICT_FAST, Sunday 435 | ] 436 | 437 | 438 | @pytest.mark.parametrize("current_date, calendar_style, expected", test_cases) 439 | def test_get_fasting(current_date: str, calendar_style: str, expected: str) -> None: 440 | """Test for get_fasting.""" 441 | assert get_fasting(string_to_date(current_date), calendar_style) == expected 442 | -------------------------------------------------------------------------------- /tests/core/test_fasting_season_10_old.py: -------------------------------------------------------------------------------- 1 | """Test for fasting subpackage.""" 2 | 3 | import pytest 4 | 5 | from ocma_data.core.fasting.logic import get_fasting 6 | from ocma_data.utils.date_utils import string_to_date 7 | 8 | test_cases = [ 9 | # Maximum range for each fasting season 10 | # Format: ["Calendar", "Start Month-Day", "End Month-Day"] 11 | # Fasting season 10: ["Old Calendar", "11-15", "12-24"] 12 | ( 13 | "1924-11-15", 14 | "old", 15 | { 16 | "fasting_season_index": "10", 17 | "fasting_laymen_index": "5", 18 | "fasting_monks_index": "5", 19 | }, 20 | ), # Friday 21 | ( 22 | "1924-11-16", 23 | "old", 24 | { 25 | "fasting_season_index": "10", 26 | "fasting_laymen_index": "4", 27 | "fasting_monks_index": "4", 28 | }, 29 | ), # WINE_AND_OLIVE_OIL_ALLOWED, Saturday 30 | ( 31 | "1924-11-17", 32 | "old", 33 | { 34 | "fasting_season_index": "10", 35 | "fasting_laymen_index": "4", 36 | "fasting_monks_index": "4", 37 | }, 38 | ), # Sunday 39 | ( 40 | "1924-11-18", 41 | "old", 42 | { 43 | "fasting_season_index": "10", 44 | "fasting_laymen_index": "5", 45 | "fasting_monks_index": "5", 46 | }, 47 | ), # Monday 48 | ( 49 | "1924-11-19", 50 | "old", 51 | { 52 | "fasting_season_index": "10", 53 | "fasting_laymen_index": "4", 54 | "fasting_monks_index": "4", 55 | }, 56 | ), # Tuesday 57 | ( 58 | "1924-11-20", 59 | "old", 60 | { 61 | "fasting_season_index": "10", 62 | "fasting_laymen_index": "5", 63 | "fasting_monks_index": "5", 64 | }, 65 | ), # Wednesday 66 | ( 67 | "1924-11-21", 68 | "old", 69 | { 70 | "fasting_season_index": "10", 71 | "fasting_laymen_index": "3", 72 | "fasting_monks_index": "3", 73 | }, 74 | ), # FISH_ALLOWED, Thursday 75 | ( 76 | "1924-11-23", 77 | "old", 78 | { 79 | "fasting_season_index": "10", 80 | "fasting_laymen_index": "3", 81 | "fasting_monks_index": "3", 82 | }, 83 | ), # Saturday 84 | ( 85 | "1924-11-24", 86 | "old", 87 | { 88 | "fasting_season_index": "10", 89 | "fasting_laymen_index": "3", 90 | "fasting_monks_index": "3", 91 | }, 92 | ), # Sunday 93 | ( 94 | "1924-11-25", 95 | "old", 96 | { 97 | "fasting_season_index": "10", 98 | "fasting_laymen_index": "4", 99 | "fasting_monks_index": "4", 100 | }, 101 | ), # WINE_AND_OLIVE_OIL_ALLOWED, Monday 102 | ( 103 | "1924-11-30", 104 | "old", 105 | { 106 | "fasting_season_index": "10", 107 | "fasting_laymen_index": "3", 108 | "fasting_monks_index": "3", 109 | }, 110 | ), # WINE_AND_OLIVE_OIL_ALLOWED, Saturday 111 | ( 112 | "1924-12-4", 113 | "old", 114 | { 115 | "fasting_season_index": "10", 116 | "fasting_laymen_index": "4", 117 | "fasting_monks_index": "4", 118 | }, 119 | ), # WINE_AND_OLIVE_OIL_ALLOWED, Wednesday 120 | ( 121 | "1924-12-5", 122 | "old", 123 | { 124 | "fasting_season_index": "10", 125 | "fasting_laymen_index": "4", 126 | "fasting_monks_index": "4", 127 | }, 128 | ), # WINE_AND_OLIVE_OIL_ALLOWED, Thursday 129 | ( 130 | "1924-12-6", 131 | "old", 132 | { 133 | "fasting_season_index": "10", 134 | "fasting_laymen_index": "4", 135 | "fasting_monks_index": "4", 136 | }, 137 | ), # WINE_AND_OLIVE_OIL_ALLOWED, Friday 138 | ( 139 | "1924-12-7", 140 | "old", 141 | { 142 | "fasting_season_index": "10", 143 | "fasting_laymen_index": "3", 144 | "fasting_monks_index": "3", 145 | }, 146 | ), # Saturday 147 | ( 148 | "1924-12-8", 149 | "old", 150 | { 151 | "fasting_season_index": "10", 152 | "fasting_laymen_index": "3", 153 | "fasting_monks_index": "3", 154 | }, 155 | ), # Sunday 156 | ( 157 | "1924-12-9", 158 | "old", 159 | { 160 | "fasting_season_index": "10", 161 | "fasting_laymen_index": "4", 162 | "fasting_monks_index": "4", 163 | }, 164 | ), # WINE_AND_OLIVE_OIL_ALLOWED, Monday 165 | ( 166 | "1924-12-12", 167 | "old", 168 | { 169 | "fasting_season_index": "10", 170 | "fasting_laymen_index": "4", 171 | "fasting_monks_index": "4", 172 | }, 173 | ), # WINE_AND_OLIVE_OIL_ALLOWED, Thursday 174 | ( 175 | "1924-12-14", 176 | "old", 177 | { 178 | "fasting_season_index": "10", 179 | "fasting_laymen_index": "4", 180 | "fasting_monks_index": "4", 181 | }, 182 | ), # Saturday 183 | ( 184 | "1924-12-15", 185 | "old", 186 | { 187 | "fasting_season_index": "10", 188 | "fasting_laymen_index": "4", 189 | "fasting_monks_index": "4", 190 | }, 191 | ), # WINE_AND_OLIVE_OIL_ALLOWED, Sunday 192 | ( 193 | "1924-12-17", 194 | "old", 195 | { 196 | "fasting_season_index": "10", 197 | "fasting_laymen_index": "4", 198 | "fasting_monks_index": "4", 199 | }, 200 | ), # WINE_AND_OLIVE_OIL_ALLOWED, Tuesday 201 | ( 202 | "1924-12-20", 203 | "old", 204 | { 205 | "fasting_season_index": "10", 206 | "fasting_laymen_index": "4", 207 | "fasting_monks_index": "4", 208 | }, 209 | ), # WINE_AND_OLIVE_OIL_ALLOWED, Friday 210 | ( 211 | "1924-12-21", 212 | "old", 213 | { 214 | "fasting_season_index": "10", 215 | "fasting_laymen_index": "4", 216 | "fasting_monks_index": "4", 217 | }, 218 | ), # Saturday 219 | ( 220 | "1924-12-22", 221 | "old", 222 | { 223 | "fasting_season_index": "10", 224 | "fasting_laymen_index": "4", 225 | "fasting_monks_index": "4", 226 | }, 227 | ), # Sunday 228 | ( 229 | "1924-12-24", 230 | "old", 231 | { 232 | "fasting_season_index": "10", 233 | "fasting_laymen_index": "5", 234 | "fasting_monks_index": "5", 235 | }, 236 | ), # STRICT_FAST, Tuesday 237 | ( 238 | "2099-11-15", 239 | "old", 240 | { 241 | "fasting_season_index": "10", 242 | "fasting_laymen_index": "4", 243 | "fasting_monks_index": "4", 244 | }, 245 | ), # Saturday 246 | ( 247 | "2099-11-16", 248 | "old", 249 | { 250 | "fasting_season_index": "10", 251 | "fasting_laymen_index": "4", 252 | "fasting_monks_index": "4", 253 | }, 254 | ), # WINE_AND_OLIVE_OIL_ALLOWED, Sunday 255 | ( 256 | "2099-11-20", 257 | "old", 258 | { 259 | "fasting_season_index": "10", 260 | "fasting_laymen_index": "4", 261 | "fasting_monks_index": "4", 262 | }, 263 | ), # Thursday 264 | ( 265 | "2099-11-21", 266 | "old", 267 | { 268 | "fasting_season_index": "10", 269 | "fasting_laymen_index": "3", 270 | "fasting_monks_index": "3", 271 | }, 272 | ), # FISH_ALLOWED, Friday 273 | ( 274 | "2099-11-22", 275 | "old", 276 | { 277 | "fasting_season_index": "10", 278 | "fasting_laymen_index": "3", 279 | "fasting_monks_index": "3", 280 | }, 281 | ), # Saturday 282 | ( 283 | "2099-11-23", 284 | "old", 285 | { 286 | "fasting_season_index": "10", 287 | "fasting_laymen_index": "3", 288 | "fasting_monks_index": "3", 289 | }, 290 | ), # Sunday 291 | ( 292 | "2099-11-25", 293 | "old", 294 | { 295 | "fasting_season_index": "10", 296 | "fasting_laymen_index": "4", 297 | "fasting_monks_index": "4", 298 | }, 299 | ), # WINE_AND_OLIVE_OIL_ALLOWED, Tuesday 300 | ( 301 | "2099-11-30", 302 | "old", 303 | { 304 | "fasting_season_index": "10", 305 | "fasting_laymen_index": "3", 306 | "fasting_monks_index": "3", 307 | }, 308 | ), # WINE_AND_OLIVE_OIL_ALLOWED, Sunday 309 | ( 310 | "2099-12-4", 311 | "old", 312 | { 313 | "fasting_season_index": "10", 314 | "fasting_laymen_index": "4", 315 | "fasting_monks_index": "4", 316 | }, 317 | ), # WINE_AND_OLIVE_OIL_ALLOWED, Thursday 318 | ( 319 | "2099-12-5", 320 | "old", 321 | { 322 | "fasting_season_index": "10", 323 | "fasting_laymen_index": "4", 324 | "fasting_monks_index": "4", 325 | }, 326 | ), # WINE_AND_OLIVE_OIL_ALLOWED, Friday 327 | ( 328 | "2099-12-6", 329 | "old", 330 | { 331 | "fasting_season_index": "10", 332 | "fasting_laymen_index": "3", 333 | "fasting_monks_index": "3", 334 | }, 335 | ), # WINE_AND_OLIVE_OIL_ALLOWED, Saturday 336 | ( 337 | "2099-12-7", 338 | "old", 339 | { 340 | "fasting_season_index": "10", 341 | "fasting_laymen_index": "3", 342 | "fasting_monks_index": "3", 343 | }, 344 | ), # Sunday 345 | ( 346 | "2099-12-9", 347 | "old", 348 | { 349 | "fasting_season_index": "10", 350 | "fasting_laymen_index": "4", 351 | "fasting_monks_index": "4", 352 | }, 353 | ), # WINE_AND_OLIVE_OIL_ALLOWED, Tuesday 354 | ( 355 | "2099-12-12", 356 | "old", 357 | { 358 | "fasting_season_index": "10", 359 | "fasting_laymen_index": "4", 360 | "fasting_monks_index": "4", 361 | }, 362 | ), # WINE_AND_OLIVE_OIL_ALLOWED, Friday 363 | ( 364 | "2099-12-13", 365 | "old", 366 | { 367 | "fasting_season_index": "10", 368 | "fasting_laymen_index": "4", 369 | "fasting_monks_index": "4", 370 | }, 371 | ), # Saturday 372 | ( 373 | "2099-12-14", 374 | "old", 375 | { 376 | "fasting_season_index": "10", 377 | "fasting_laymen_index": "4", 378 | "fasting_monks_index": "4", 379 | }, 380 | ), # Sunday 381 | ( 382 | "2099-12-15", 383 | "old", 384 | { 385 | "fasting_season_index": "10", 386 | "fasting_laymen_index": "4", 387 | "fasting_monks_index": "4", 388 | }, 389 | ), # WINE_AND_OLIVE_OIL_ALLOWED, Monday 390 | ( 391 | "2099-12-17", 392 | "old", 393 | { 394 | "fasting_season_index": "10", 395 | "fasting_laymen_index": "4", 396 | "fasting_monks_index": "4", 397 | }, 398 | ), # WINE_AND_OLIVE_OIL_ALLOWED, Wednesday 399 | ( 400 | "2099-12-20", 401 | "old", 402 | { 403 | "fasting_season_index": "10", 404 | "fasting_laymen_index": "4", 405 | "fasting_monks_index": "4", 406 | }, 407 | ), # WINE_AND_OLIVE_OIL_ALLOWED, Saturday 408 | ( 409 | "2099-12-21", 410 | "old", 411 | { 412 | "fasting_season_index": "10", 413 | "fasting_laymen_index": "4", 414 | "fasting_monks_index": "4", 415 | }, 416 | ), # Sunday 417 | ( 418 | "2024-12-24", 419 | "old", 420 | { 421 | "fasting_season_index": "10", 422 | "fasting_laymen_index": "5", 423 | "fasting_monks_index": "5", 424 | }, 425 | ), # STRICT_FAST, Monday 426 | ( 427 | "2099-12-24", 428 | "old", 429 | { 430 | "fasting_season_index": "10", 431 | "fasting_laymen_index": "5", 432 | "fasting_monks_index": "5", 433 | }, 434 | ), # STRICT_FAST, Wednesday 435 | ( 436 | "2027-12-24", 437 | "old", 438 | { 439 | "fasting_season_index": "10", 440 | "fasting_laymen_index": "5", 441 | "fasting_monks_index": "5", 442 | }, 443 | ), # STRICT_FAST, Thursday 444 | ( 445 | "2033-12-24", 446 | "old", 447 | { 448 | "fasting_season_index": "10", 449 | "fasting_laymen_index": "5", 450 | "fasting_monks_index": "5", 451 | }, 452 | ), # STRICT_FAST, Friday 453 | ( 454 | "2028-12-24", 455 | "old", 456 | { 457 | "fasting_season_index": "10", 458 | "fasting_laymen_index": "4", 459 | "fasting_monks_index": "4", 460 | }, 461 | ), # STRICT_FAST, Saturday 462 | ( 463 | "2029-12-24", 464 | "old", 465 | { 466 | "fasting_season_index": "10", 467 | "fasting_laymen_index": "4", 468 | "fasting_monks_index": "4", 469 | }, 470 | ), # STRICT_FAST, Sunday 471 | ] 472 | 473 | 474 | @pytest.mark.parametrize("current_date, calendar_style, expected", test_cases) 475 | def test_get_fasting(current_date: str, calendar_style: str, expected: str) -> None: 476 | """Test for get_fasting.""" 477 | assert get_fasting(string_to_date(current_date), calendar_style) == expected 478 | -------------------------------------------------------------------------------- /ocma_data/core/fasting/logic.py: -------------------------------------------------------------------------------- 1 | """Module for calculating fasting seasons and levels.""" 2 | 3 | from datetime import date 4 | from typing import TypedDict 5 | 6 | from ocma_data.constants import Weekdays 7 | from ocma_data.core.fasting.constants import ( 8 | FISH_ALLOWED, 9 | STRICT_FAST, 10 | WINE_AND_OLIVE_OIL_ALLOWED, 11 | DateFixed, 12 | DateMovable, 13 | FastingLevels, 14 | FastingSeasons, 15 | PaschaDistanceAfter, 16 | PaschaDistanceBefore, 17 | ) 18 | from ocma_data.core.pascha_distance.logic import pascha_distance 19 | from ocma_data.core.weekday.logic import get_weekday 20 | from ocma_data.utils.date_utils import ( 21 | is_date_before, 22 | is_date_in_range, 23 | is_date_in_tuple, 24 | ) 25 | 26 | 27 | class FastingResult(TypedDict): 28 | """Type for fasting result.""" 29 | 30 | fasting_season_index: str 31 | fasting_laymen_index: str 32 | fasting_monks_index: str 33 | 34 | 35 | class FastingContext: 36 | """Context object for fasting calculations.""" 37 | 38 | def __init__(self, current_date: date, calendar_style: str): 39 | """Initialize fasting context with date and calendar style.""" 40 | self.current_date = current_date 41 | self.calendar_style = calendar_style 42 | self.month_day = f"{current_date.month}-{current_date.day}" 43 | self.pascha_distance = int(pascha_distance(current_date, calendar_style)) 44 | self.weekday = int(get_weekday(current_date, calendar_style)) 45 | 46 | def comparison_date(self, fasting_season_date: str) -> date: 47 | """Get the comparison date for fixed date ranges.""" 48 | current_year = self.current_date.year 49 | if fasting_season_date == DateFixed.TWELVE_DAY_FAST_FREE_AFTER.value: 50 | current_year += 1 51 | 52 | parts = fasting_season_date.split("-") 53 | return date(current_year, int(parts[0]), int(parts[1])) 54 | 55 | def is_weekend(self) -> bool: 56 | """Check if current day is weekend.""" 57 | return self.weekday in (Weekdays.SAT.value, Weekdays.SUN.value) 58 | 59 | def is_mon_wed_fri(self) -> bool: 60 | """Check if current day is Monday, Wednesday or Friday.""" 61 | return self.weekday in (Weekdays.MON.value, Weekdays.WED.value, Weekdays.FRI.value) 62 | 63 | def create_result(self, season: int, laymen: int, monks: int) -> FastingResult: 64 | """Create a fasting result dictionary.""" 65 | return { 66 | "fasting_season_index": str(season), 67 | "fasting_laymen_index": str(laymen), 68 | "fasting_monks_index": str(monks), 69 | } 70 | 71 | 72 | # Strategy functions for each fasting period 73 | 74 | 75 | def check_first_week_triodion(ctx: FastingContext) -> FastingResult | None: 76 | """Check if date is in First Week of Triodion.""" 77 | if ( 78 | PaschaDistanceBefore.FIRST_WEEK_OF_THE_TRIODION.value 79 | <= ctx.pascha_distance 80 | <= PaschaDistanceAfter.FIRST_WEEK_OF_THE_TRIODION.value 81 | ): 82 | return ctx.create_result( 83 | FastingSeasons.FIRST_WEEK_OF_THE_TRIODION.value, 84 | FastingLevels.NO_FASTING.value, 85 | FastingLevels.DAIRY_PRODUCTS_ALLOWED.value, 86 | ) 87 | return None 88 | 89 | 90 | def check_cheesefare_week(ctx: FastingContext) -> FastingResult | None: 91 | """Check if date is in Cheesefare Week.""" 92 | if PaschaDistanceBefore.CHEESEFARE_WEEK.value <= ctx.pascha_distance <= PaschaDistanceAfter.CHEESEFARE_WEEK.value: 93 | return ctx.create_result( 94 | FastingSeasons.CHEESEFARE_WEEK.value, 95 | FastingLevels.DAIRY_PRODUCTS_ALLOWED.value, 96 | FastingLevels.DAIRY_PRODUCTS_ALLOWED.value, 97 | ) 98 | return None 99 | 100 | 101 | def check_three_day_fast(ctx: FastingContext) -> FastingResult | None: 102 | """Check if date is in Three-Day Fast.""" 103 | if PaschaDistanceBefore.THREE_DAY_FAST.value <= ctx.pascha_distance <= PaschaDistanceAfter.THREE_DAY_FAST.value: 104 | level = ( 105 | FastingLevels.STRICT_FAST.value 106 | if is_date_in_tuple(ctx.month_day, FISH_ALLOWED) 107 | else FastingLevels.ABSOLUTE_FAST.value 108 | ) 109 | return ctx.create_result(FastingSeasons.THREE_DAY_FAST.value, level, level) 110 | return None 111 | 112 | 113 | def check_great_lent(ctx: FastingContext) -> FastingResult | None: 114 | """Check if date is in Great Lent.""" 115 | if not (PaschaDistanceBefore.GREAT_LENT.value <= ctx.pascha_distance <= PaschaDistanceAfter.GREAT_LENT.value): 116 | return None 117 | 118 | # Fish allowed days 119 | if is_date_in_tuple(ctx.month_day, FISH_ALLOWED) or ctx.pascha_distance == DateMovable.PALM_SUNDAY.value: 120 | level = FastingLevels.FISH_ALLOWED.value 121 | # Wine and oil allowed days 122 | elif is_date_in_tuple(ctx.month_day, WINE_AND_OLIVE_OIL_ALLOWED) or ctx.is_weekend(): 123 | level = FastingLevels.WINE_AND_OLIVE_OIL_ALLOWED.value 124 | # Strict fast days 125 | else: 126 | level = FastingLevels.STRICT_FAST.value 127 | 128 | return ctx.create_result(FastingSeasons.GREAT_LENT.value, level, level) 129 | 130 | 131 | def check_holy_week(ctx: FastingContext) -> FastingResult | None: 132 | """Check if date is in Holy Week.""" 133 | if not (PaschaDistanceBefore.HOLY_WEEK.value <= ctx.pascha_distance <= PaschaDistanceAfter.HOLY_WEEK.value): 134 | return None 135 | 136 | is_saturday = ctx.weekday == Weekdays.SAT.value 137 | is_friday = ctx.weekday == Weekdays.FRI.value 138 | fish_allowed = is_date_in_tuple(ctx.month_day, FISH_ALLOWED) 139 | 140 | # Saturday - strict fast 141 | if is_saturday: 142 | level = FastingLevels.STRICT_FAST.value 143 | # Friday - absolute or strict fast 144 | elif is_friday: 145 | level = FastingLevels.STRICT_FAST.value if fish_allowed else FastingLevels.ABSOLUTE_FAST.value 146 | # Other days with fish allowed 147 | elif fish_allowed: 148 | level = FastingLevels.WINE_AND_OLIVE_OIL_ALLOWED.value 149 | # Other days 150 | else: 151 | level = FastingLevels.STRICT_FAST.value 152 | 153 | return ctx.create_result(FastingSeasons.HOLY_WEEK.value, level, level) 154 | 155 | 156 | def check_bright_week(ctx: FastingContext) -> FastingResult | None: 157 | """Check if date is in Bright Week.""" 158 | if PaschaDistanceBefore.BRIGHT_WEEK.value <= ctx.pascha_distance <= PaschaDistanceAfter.BRIGHT_WEEK.value: 159 | return ctx.create_result( 160 | FastingSeasons.BRIGHT_WEEK.value, FastingLevels.NO_FASTING.value, FastingLevels.DAIRY_PRODUCTS_ALLOWED.value 161 | ) 162 | return None 163 | 164 | 165 | def check_week_holy_spirit(ctx: FastingContext) -> FastingResult | None: 166 | """Check if date is in Week of the Holy Spirit.""" 167 | if ( 168 | PaschaDistanceBefore.WEEK_OF_THE_HOLY_SPIRIT.value 169 | <= ctx.pascha_distance 170 | <= PaschaDistanceAfter.WEEK_OF_THE_HOLY_SPIRIT.value 171 | ): 172 | return ctx.create_result( 173 | FastingSeasons.WEEK_OF_THE_HOLY_SPIRIT.value, 174 | FastingLevels.NO_FASTING.value, 175 | FastingLevels.DAIRY_PRODUCTS_ALLOWED.value, 176 | ) 177 | return None 178 | 179 | 180 | def check_apostles_fast(ctx: FastingContext) -> FastingResult | None: 181 | """Check if date is in Apostles' Fast.""" 182 | if not ( 183 | ctx.pascha_distance >= PaschaDistanceBefore.APOSTLES_FAST.value 184 | and is_date_before(ctx.current_date, ctx.comparison_date(DateFixed.APOSTLES_FAST.value)) 185 | ): 186 | return None 187 | 188 | # Fish allowed days 189 | if is_date_in_tuple(ctx.month_day, FISH_ALLOWED): 190 | level = FastingLevels.FISH_ALLOWED.value 191 | # Weekend 192 | elif ctx.is_weekend(): 193 | before_baptist = is_date_before(ctx.current_date, ctx.comparison_date(DateFixed.NATIVITY_OF_THE_BAPTIST.value)) 194 | level = FastingLevels.FISH_ALLOWED.value if before_baptist else FastingLevels.WINE_AND_OLIVE_OIL_ALLOWED.value 195 | # Monday, Wednesday, Friday 196 | elif ctx.is_mon_wed_fri(): 197 | level = ( 198 | FastingLevels.WINE_AND_OLIVE_OIL_ALLOWED.value 199 | if is_date_in_tuple(ctx.month_day, WINE_AND_OLIVE_OIL_ALLOWED) 200 | else FastingLevels.STRICT_FAST.value 201 | ) 202 | # Other days 203 | else: 204 | level = FastingLevels.WINE_AND_OLIVE_OIL_ALLOWED.value 205 | 206 | return ctx.create_result(FastingSeasons.APOSTLES_FAST.value, level, level) 207 | 208 | 209 | def check_dormition_fast(ctx: FastingContext) -> FastingResult | None: 210 | """Check if date is in Dormition Fast.""" 211 | if not is_date_in_range( 212 | ctx.current_date, 213 | ctx.comparison_date(DateFixed.DORMITION_FAST_BEFORE.value), 214 | ctx.comparison_date(DateFixed.DORMITION_FAST_AFTER.value), 215 | ): 216 | return None 217 | 218 | # Fish allowed days 219 | if is_date_in_tuple(ctx.month_day, FISH_ALLOWED): 220 | level = FastingLevels.FISH_ALLOWED.value 221 | # Weekend 222 | elif ctx.is_weekend(): 223 | level = FastingLevels.WINE_AND_OLIVE_OIL_ALLOWED.value 224 | # Other days 225 | else: 226 | level = FastingLevels.STRICT_FAST.value 227 | 228 | return ctx.create_result(FastingSeasons.DORMITION_FAST.value, level, level) 229 | 230 | 231 | def check_nativity_fast(ctx: FastingContext) -> FastingResult | None: 232 | """Check if date is in Nativity Fast.""" 233 | if not is_date_in_range( 234 | ctx.current_date, 235 | ctx.comparison_date(DateFixed.NATIVITY_FAST_BEFORE.value), 236 | ctx.comparison_date(DateFixed.NATIVITY_FAST_AFTER.value), 237 | ): 238 | return None 239 | 240 | is_strict_day = is_date_in_tuple(ctx.month_day, STRICT_FAST) 241 | 242 | # Strict fast days 243 | if is_strict_day: 244 | level = FastingLevels.WINE_AND_OLIVE_OIL_ALLOWED.value if ctx.is_weekend() else FastingLevels.STRICT_FAST.value 245 | # Fish allowed days 246 | elif is_date_in_tuple(ctx.month_day, FISH_ALLOWED): 247 | level = FastingLevels.FISH_ALLOWED.value 248 | # Weekend 249 | elif ctx.is_weekend(): 250 | in_theotokos_range = is_date_in_range( 251 | ctx.current_date, 252 | ctx.comparison_date(DateFixed.THE_ENTRANCE_OF_THE_THEOTOKOS.value), 253 | ctx.comparison_date(DateFixed.SAINT_SPYRIDON.value), 254 | ) 255 | level = ( 256 | FastingLevels.FISH_ALLOWED.value if in_theotokos_range else FastingLevels.WINE_AND_OLIVE_OIL_ALLOWED.value 257 | ) 258 | # Monday, Wednesday, Friday 259 | elif ctx.is_mon_wed_fri(): 260 | level = ( 261 | FastingLevels.WINE_AND_OLIVE_OIL_ALLOWED.value 262 | if is_date_in_tuple(ctx.month_day, WINE_AND_OLIVE_OIL_ALLOWED) 263 | else FastingLevels.STRICT_FAST.value 264 | ) 265 | # Other days 266 | else: 267 | level = FastingLevels.WINE_AND_OLIVE_OIL_ALLOWED.value 268 | 269 | return ctx.create_result(FastingSeasons.NATIVITY_FAST.value, level, level) 270 | 271 | 272 | def check_twelve_day_fast_free(ctx: FastingContext) -> FastingResult | None: 273 | """Check if date is in Twelve-Day Fast-Free period.""" 274 | if not is_date_in_range( 275 | ctx.current_date, 276 | ctx.comparison_date(DateFixed.TWELVE_DAY_FAST_FREE_BEFORE.value), 277 | ctx.comparison_date(DateFixed.TWELVE_DAY_FAST_FREE_AFTER.value), 278 | ): 279 | return None 280 | 281 | if is_date_in_tuple(ctx.month_day, STRICT_FAST): 282 | level = FastingLevels.WINE_AND_OLIVE_OIL_ALLOWED.value if ctx.is_weekend() else FastingLevels.STRICT_FAST.value 283 | return ctx.create_result(FastingSeasons.TWELVE_DAY_FAST_FREE.value, level, level) 284 | 285 | return ctx.create_result( 286 | FastingSeasons.TWELVE_DAY_FAST_FREE.value, 287 | FastingLevels.NO_FASTING.value, 288 | FastingLevels.DAIRY_PRODUCTS_ALLOWED.value, 289 | ) 290 | 291 | 292 | def check_regular_season(ctx: FastingContext) -> FastingResult: 293 | """Check regular season fasting rules.""" 294 | # Strict fast days 295 | if is_date_in_tuple(ctx.month_day, STRICT_FAST): 296 | level = FastingLevels.WINE_AND_OLIVE_OIL_ALLOWED.value if ctx.is_weekend() else FastingLevels.STRICT_FAST.value 297 | return ctx.create_result(FastingSeasons.REGULAR_SEASON.value, level, level) 298 | 299 | # Monday, Wednesday, Friday 300 | if ctx.is_mon_wed_fri(): 301 | # Special movable feasts (not on Monday) 302 | if ctx.weekday != Weekdays.MON.value and ( 303 | is_date_in_tuple(ctx.month_day, FISH_ALLOWED) 304 | or ctx.pascha_distance == DateMovable.MIDFEAST_OF_PENTECOST.value 305 | or ctx.pascha_distance == DateMovable.LEAVETAKING_OF_PASCHA.value 306 | ): 307 | level_lay = level_monk = FastingLevels.FISH_ALLOWED.value 308 | # Wine and oil allowed (not on Monday) 309 | elif ctx.weekday != Weekdays.MON.value and is_date_in_tuple(ctx.month_day, WINE_AND_OLIVE_OIL_ALLOWED): 310 | level_lay = level_monk = FastingLevels.WINE_AND_OLIVE_OIL_ALLOWED.value 311 | # Monday 312 | elif ctx.weekday == Weekdays.MON.value: 313 | level_lay = FastingLevels.NO_FASTING.value 314 | level_monk = FastingLevels.STRICT_FAST.value 315 | # Wednesday and Friday 316 | else: 317 | level_lay = level_monk = FastingLevels.STRICT_FAST.value 318 | 319 | return ctx.create_result(FastingSeasons.REGULAR_SEASON.value, level_lay, level_monk) 320 | 321 | # Other days - no fasting for laypeople 322 | return ctx.create_result( 323 | FastingSeasons.REGULAR_SEASON.value, FastingLevels.NO_FASTING.value, FastingLevels.DAIRY_PRODUCTS_ALLOWED.value 324 | ) 325 | 326 | 327 | # Main function with strategy pattern 328 | 329 | FASTING_STRATEGIES = [ 330 | check_first_week_triodion, 331 | check_cheesefare_week, 332 | check_three_day_fast, 333 | check_great_lent, 334 | check_holy_week, 335 | check_bright_week, 336 | check_week_holy_spirit, 337 | check_apostles_fast, 338 | check_dormition_fast, 339 | check_nativity_fast, 340 | check_twelve_day_fast_free, 341 | ] 342 | 343 | 344 | def get_fasting(current_date: date, calendar_style: str) -> dict[str, str]: 345 | """Get the fasting_season_index, fasting_laymen_index and fasting_monks_index. 346 | 347 | This function uses a strategy pattern to check each fasting period in order. 348 | The first matching period determines the fasting rules. 349 | """ 350 | ctx = FastingContext(current_date, calendar_style) 351 | 352 | # Check special fasting periods in order 353 | for strategy in FASTING_STRATEGIES: 354 | result = strategy(ctx) 355 | if result is not None: 356 | return result 357 | 358 | # Default to regular season 359 | return check_regular_season(ctx) 360 | --------------------------------------------------------------------------------