├── docs ├── .gitignore ├── history.rst ├── readme.rst ├── modules │ ├── holidays.rst │ ├── iso.rst │ ├── utils.rst │ ├── daycount.rst │ ├── hebrew.rst │ ├── julian.rst │ ├── coptic.rst │ ├── ordinal.rst │ ├── armenian.rst │ ├── islamic.rst │ ├── persian.rst │ ├── julianday.rst │ ├── mayan.rst │ ├── bahai.rst │ ├── gregorian.rst │ ├── positivist.rst │ ├── dublin.rst │ ├── indian_civil.rst │ └── french_republican.rst ├── index.rst ├── Makefile └── conf.py ├── src └── convertdate │ ├── data │ ├── __init__.py │ ├── french_republican_days.py │ └── positivist.py │ ├── dublin.py │ ├── daycount.py │ ├── iso.py │ ├── ordinal.py │ ├── __init__.py │ ├── julianday.py │ ├── coptic.py │ ├── islamic.py │ ├── utils.py │ ├── indian_civil.py │ ├── julian.py │ ├── persian.py │ ├── positivist.py │ ├── gregorian.py │ ├── bahai.py │ ├── hebrew.py │ ├── armenian.py │ ├── mayan.py │ └── french_republican.py ├── .readthedocs.yml ├── setup.py ├── .gitignore ├── pyproject.toml ├── Makefile ├── tests ├── test_utils.py ├── __init__.py ├── test_indian_civil.py ├── test_julian.py ├── test_ordinal.py ├── test_mayan.py ├── test_persian.py ├── test_positivist.py ├── test_armenian.py ├── test_gregorian.py ├── test_general.py ├── test_bahai.py ├── test_holidays.py └── test_french_republican.py ├── .github └── workflows │ ├── publish.yml │ └── python.yml ├── LICENSE ├── setup.cfg ├── HISTORY.rst └── README.md /docs/.gitignore: -------------------------------------------------------------------------------- 1 | _build -------------------------------------------------------------------------------- /src/convertdate/data/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /docs/history.rst: -------------------------------------------------------------------------------- 1 | .. include:: ../HISTORY.rst -------------------------------------------------------------------------------- /docs/readme.rst: -------------------------------------------------------------------------------- 1 | General Info 2 | ============ 3 | 4 | .. include:: ../README.md 5 | :parser: myst_parser.sphinx_ 6 | -------------------------------------------------------------------------------- /docs/modules/holidays.rst: -------------------------------------------------------------------------------- 1 | Holidays 2 | ========= 3 | 4 | .. automodule:: convertdate.holidays 5 | :members: 6 | :undoc-members: 7 | -------------------------------------------------------------------------------- /docs/modules/iso.rst: -------------------------------------------------------------------------------- 1 | ISO calendar 2 | ============= 3 | 4 | .. automodule:: convertdate.iso 5 | :members: 6 | :undoc-members: 7 | 8 | -------------------------------------------------------------------------------- /docs/modules/utils.rst: -------------------------------------------------------------------------------- 1 | Utilities 2 | ========= 3 | 4 | .. automodule:: convertdate.utils 5 | :members: 6 | :undoc-members: 7 | 8 | 9 | -------------------------------------------------------------------------------- /docs/modules/daycount.rst: -------------------------------------------------------------------------------- 1 | Day count class 2 | =============== 3 | 4 | .. automodule:: convertdate.daycount 5 | :members: 6 | :undoc-members: 7 | -------------------------------------------------------------------------------- /docs/modules/hebrew.rst: -------------------------------------------------------------------------------- 1 | Hebrew calendar 2 | =============== 3 | 4 | .. automodule:: convertdate.hebrew 5 | :members: 6 | :undoc-members: 7 | 8 | -------------------------------------------------------------------------------- /docs/modules/julian.rst: -------------------------------------------------------------------------------- 1 | Julian calendar 2 | =============== 3 | 4 | .. automodule:: convertdate.julian 5 | :members: 6 | :undoc-members: 7 | 8 | -------------------------------------------------------------------------------- /docs/modules/coptic.rst: -------------------------------------------------------------------------------- 1 | Coptic calendar 2 | =============== 3 | 4 | .. automodule:: convertdate.coptic 5 | :members: 6 | :undoc-members: 7 | 8 | 9 | -------------------------------------------------------------------------------- /docs/modules/ordinal.rst: -------------------------------------------------------------------------------- 1 | Ordinal dates 2 | ============== 3 | 4 | .. automodule:: convertdate.ordinal 5 | :members: 6 | :undoc-members: 7 | 8 | 9 | -------------------------------------------------------------------------------- /docs/modules/armenian.rst: -------------------------------------------------------------------------------- 1 | Armenian calendar 2 | ================= 3 | 4 | .. automodule:: convertdate.armenian 5 | :members: 6 | :undoc-members: 7 | 8 | -------------------------------------------------------------------------------- /docs/modules/islamic.rst: -------------------------------------------------------------------------------- 1 | Islamic calendar 2 | ================= 3 | 4 | .. automodule:: convertdate.islamic 5 | :members: 6 | :undoc-members: 7 | 8 | 9 | -------------------------------------------------------------------------------- /docs/modules/persian.rst: -------------------------------------------------------------------------------- 1 | Persian Calendar 2 | =================== 3 | 4 | .. automodule:: convertdate.persian 5 | :members: 6 | :undoc-members: 7 | 8 | -------------------------------------------------------------------------------- /docs/modules/julianday.rst: -------------------------------------------------------------------------------- 1 | Julian Day Count 2 | ================= 3 | 4 | .. automodule:: convertdate.julianday 5 | :members: 6 | :undoc-members: 7 | 8 | 9 | -------------------------------------------------------------------------------- /docs/modules/mayan.rst: -------------------------------------------------------------------------------- 1 | Mayan calendar 2 | ============== 3 | 4 | .. automodule:: convertdate.mayan 5 | :members: 6 | :undoc-members: 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /docs/modules/bahai.rst: -------------------------------------------------------------------------------- 1 | Bahá'í calendar 2 | ================ 3 | 4 | .. automodule:: convertdate.bahai 5 | :members: 6 | :undoc-members: 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /docs/modules/gregorian.rst: -------------------------------------------------------------------------------- 1 | Gregorian calendar 2 | ================== 3 | 4 | .. automodule:: convertdate.gregorian 5 | :members: 6 | :undoc-members: 7 | 8 | 9 | -------------------------------------------------------------------------------- /docs/modules/positivist.rst: -------------------------------------------------------------------------------- 1 | Positivist Calendar 2 | =================== 3 | 4 | .. automodule:: convertdate.positivist 5 | :members: 6 | :undoc-members: 7 | 8 | 9 | -------------------------------------------------------------------------------- /docs/modules/dublin.rst: -------------------------------------------------------------------------------- 1 | Dublin day count 2 | ================ 3 | 4 | .. automodule:: convertdate.dublin 5 | :members: 6 | :undoc-members: 7 | :imported-members: 8 | -------------------------------------------------------------------------------- /docs/modules/indian_civil.rst: -------------------------------------------------------------------------------- 1 | Indian Civil calendar 2 | ====================== 3 | 4 | .. automodule:: convertdate.indian_civil 5 | :members: 6 | :undoc-members: 7 | 8 | -------------------------------------------------------------------------------- /docs/modules/french_republican.rst: -------------------------------------------------------------------------------- 1 | French Republican calendar 2 | ========================== 3 | 4 | .. automodule:: convertdate.french_republican 5 | :members: 6 | :undoc-members: 7 | 8 | 9 | -------------------------------------------------------------------------------- /.readthedocs.yml: -------------------------------------------------------------------------------- 1 | # See https://docs.readthedocs.io/en/stable/config-file/v2.html for details 2 | version: 2 3 | 4 | python: 5 | version: 3.9 6 | install: 7 | - method: pip 8 | path: . 9 | extra_requirements: 10 | - docs 11 | -------------------------------------------------------------------------------- /docs/index.rst: -------------------------------------------------------------------------------- 1 | Documentation for convertdate 2 | ============================= 3 | 4 | .. toctree:: 5 | :maxdepth: 1 6 | 7 | readme 8 | history 9 | 10 | .. automodule:: convertdate 11 | 12 | .. toctree:: 13 | :caption: API reference 14 | :maxdepth: 2 15 | :glob: 16 | 17 | modules/* 18 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | # This file is part of convertdate. 4 | # http://github.com/fitnr/convertdate 5 | 6 | # Licensed under the MIT license: 7 | # http://opensource.org/licenses/MIT 8 | # Copyright (c) 2016-21, fitnr 9 | from setuptools import setup 10 | 11 | setup() 12 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # This file is part of convertdate. 2 | # http://github.com/fitnr/convertdate 3 | # Licensed under the MIT license: 4 | # http://opensource.org/licenses/MIT 5 | # Copyright (c) 2016, fitnr 6 | build 7 | dist 8 | *.egg-info 9 | *.egg 10 | *.pyc 11 | *.whl 12 | .tox 13 | .eggs 14 | py35 15 | __pycache__ 16 | *.sublime-project 17 | .coverage 18 | -------------------------------------------------------------------------------- /docs/Makefile: -------------------------------------------------------------------------------- 1 | # Minimal makefile for Sphinx documentation 2 | # 3 | 4 | # You can set these variables from the command line, and also 5 | # from the environment for the first two. 6 | SPHINXOPTS ?= 7 | SPHINXBUILD ?= sphinx-build 8 | SOURCEDIR = . 9 | BUILDDIR = _build 10 | 11 | # Put it first so that "make" without argument is like "make help". 12 | help: 13 | $(SPHINXBUILD) -M help "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) 14 | 15 | .PHONY: help Makefile 16 | 17 | # Catch-all target: route all unknown targets to Sphinx using the new 18 | # "make mode" option. $(O) is meant as a shortcut for $(SPHINXOPTS). 19 | %: Makefile 20 | $(SPHINXBUILD) -M $@ "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) 21 | -------------------------------------------------------------------------------- /pyproject.toml: -------------------------------------------------------------------------------- 1 | # This file is part of convertdate. 2 | # http://github.com/fitnr/convertdate 3 | # Licensed under the MIT license: 4 | # http://opensource.org/licenses/MIT 5 | # Copyright (c) 2020-2, fitnr 6 | [build-system] 7 | requires = ["setuptools>=60.5.0", "wheel"] 8 | build-backend = 'setuptools.build_meta' 9 | 10 | [tool.black] 11 | line-length = 120 12 | target-version = ["py39"] 13 | include = 'py$' 14 | skip-string-normalization = true 15 | 16 | [tool.pylint.master] 17 | fail-under = "8.77" 18 | 19 | [tool.pylint.messages_control] 20 | disable = [ 21 | "invalid-name", 22 | "duplicate-code" 23 | ] 24 | 25 | [tool.pylint.format] 26 | max-line-length = 120 27 | 28 | [tool.isort] 29 | line_length = 120 30 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | # This file is part of convertdate. 2 | # http://github.com/fitnr/convertdate 3 | 4 | # Licensed under the MIT license: 5 | # http://opensource.org/licenses/MIT 6 | # Copyright (c) 2016, fitnr 7 | 8 | .PHONY: all htmlcov test pylint cov format publish build clean 9 | 10 | all: 11 | 12 | htmlcov: | test 13 | python -m coverage html 14 | 15 | test: 16 | python -m unittest 17 | 18 | pylint: 19 | pylint convertdate/ tests 20 | 21 | cov: 22 | python -m coverage run --branch --source=convertdate -m unittest 23 | python -m coverage report 24 | 25 | format: 26 | black src/ tests/ 27 | isort src/convertdate/*.py src/convertdate/data/*.py tests/*.py 28 | 29 | publish: | build 30 | twine upload dist/* 31 | 32 | build: | clean 33 | python -m build 34 | 35 | clean: 36 | rm -rf build dist 37 | -------------------------------------------------------------------------------- /tests/test_utils.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | import math 3 | import unittest 4 | from convertdate import utils 5 | 6 | 7 | class TestConvertdate(unittest.TestCase): 8 | def test_amod(self): 9 | self.assertEqual(utils.amod(10, 5), 5) 10 | self.assertEqual(utils.amod(12, 3), 3) 11 | self.assertEqual(utils.amod(12, 4), 4) 12 | self.assertEqual(utils.amod(100, 4), 4) 13 | 14 | def test_jwday(self): 15 | self.assertEqual(utils.jwday(2459252.5), 6) 16 | self.assertEqual(utils.weekday_before(0, 2459252.5), 2459252.5 - 6) 17 | 18 | self.assertEqual(utils.jwday(2459252.5), 6) 19 | self.assertEqual(utils.weekday_before(0, 2459252.5), 2459252.5 - 6) 20 | 21 | self.assertEqual(utils.jwday(0.5), 1) 22 | self.assertEqual(utils.jwday(-0.5), 0) 23 | self.assertEqual(utils.jwday(-1.5), 6) 24 | -------------------------------------------------------------------------------- /src/convertdate/dublin.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # This file is part of convertdate. 3 | # http://github.com/fitnr/convertdate 4 | # Licensed under the MIT license: 5 | # http://opensource.org/licenses/MIT 6 | # Copyright (c) 2016, fitnr 7 | 8 | ''' 9 | The Dublin day count is a truncated `Julian day `__ with an epoch of noon on December 31, 1899. 10 | 11 | This is a convenience module, it uses :doc:`daycount` class for all functions. 12 | ''' 13 | 14 | from . import daycount 15 | 16 | EPOCH = 2415020 # Julian Day Count for Dublin Count 0 17 | 18 | _dublin = daycount.DayCount(EPOCH) 19 | 20 | to_gregorian = _dublin.to_gregorian 21 | 22 | from_gregorian = _dublin.from_gregorian 23 | 24 | to_jd = _dublin.to_jd 25 | 26 | from_jd = _dublin.from_jd 27 | 28 | from_julian = _dublin.from_julian 29 | 30 | to_julian = _dublin.to_julian 31 | 32 | to_datetime = _dublin.to_datetime 33 | 34 | from_datetime = _dublin.from_datetime 35 | -------------------------------------------------------------------------------- /.github/workflows/publish.yml: -------------------------------------------------------------------------------- 1 | # This file is part of convertdate. 2 | # http://github.com/fitnr/convertdate 3 | 4 | # Licensed under the MIT license: 5 | # http://opensource.org/licenses/MIT 6 | # Copyright (c) 2021, fitnr 7 | name: Publish 8 | 9 | on: 10 | release: 11 | types: [created] 12 | 13 | jobs: 14 | deploy: 15 | runs-on: ubuntu-latest 16 | steps: 17 | - uses: actions/checkout@v2 18 | 19 | - name: Set up Python 20 | uses: actions/setup-python@v2 21 | with: 22 | python-version: "3.10" 23 | cache: 'pip' 24 | cache-dependency-path: setup.cfg 25 | 26 | - name: Install build requirements 27 | run: | 28 | python -m pip install -U pip build twine 29 | pip install . 30 | 31 | - run: make test 32 | 33 | - name: Publish package 34 | run: make publish 35 | env: 36 | TWINE_USERNAME: __token__ 37 | TWINE_PASSWORD: ${{ secrets.PYPI_API_TOKEN }} 38 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2014-2022 Neil Freeman 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 13 | all 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 21 | THE SOFTWARE. 22 | 23 | -------------------------------------------------------------------------------- /tests/__init__.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | # Most of this code is ported from Fourmilab's javascript calendar converter 4 | # http://www.fourmilab.ch/documents/calendar/ 5 | # which was developed by John Walker 6 | # 7 | # The algorithms are believed to be derived from the following source: 8 | # Meeus, Jean. Astronomical Algorithms . Richmond: Willmann-Bell, 1991. ISBN 0-943396-35-2. 9 | # The essential reference for computational positional astronomy. 10 | # 11 | import unittest 12 | 13 | JDCS = range(2159677, 2488395, 2000) 14 | 15 | 16 | class CalTestCase(unittest.TestCase): 17 | def reflexive(self, module, dates=None): 18 | """Check that the to_func and from_func work for a range of Julian dates.""" 19 | to_func = getattr(module, 'to_jd') 20 | from_func = getattr(module, 'from_jd') 21 | dates = dates or JDCS 22 | for j in dates: 23 | j = j + 0.5 24 | self.assertEqual(j, to_func(*from_func(j)), 'checking from_jd(to_jd({0}))'.format(j)) 25 | 26 | def assertSequenceType(self, seq, cls): 27 | '''Assert that all members of `seq` are of the type `cls`.''' 28 | for x in seq: 29 | self.assertIs(type(x), cls) 30 | -------------------------------------------------------------------------------- /src/convertdate/daycount.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # This file is part of convertdate. 3 | # http://github.com/fitnr/convertdate 4 | # Licensed under the MIT license: 5 | # http://opensource.org/licenses/MIT 6 | # Copyright (c) 2016, fitnr 7 | from . import gregorian, julian, julianday 8 | 9 | 10 | class DayCount: 11 | 12 | '''A day count converter for the given epoch (in terms of Julian Day Count)''' 13 | 14 | def __init__(self, epoch): 15 | self.epoch = epoch 16 | 17 | def to_gregorian(self, dc): 18 | '''From day count to Gregorian (year, month, day) tuple''' 19 | return gregorian.from_jd(self.to_jd(dc)) 20 | 21 | def from_gregorian(self, year, month, day): 22 | return self.from_jd(gregorian.to_jd(year, month, day)) 23 | 24 | def to_jd(self, dc): 25 | return dc + self.epoch 26 | 27 | def from_jd(self, jdc): 28 | return jdc - self.epoch 29 | 30 | def from_julian(self, year, month, day): 31 | return self.from_jd(julian.to_jd(year, month, day)) 32 | 33 | def to_julian(self, dc): 34 | '''From day count to Julian (year, month, day) tuple''' 35 | return julian.from_jd(self.to_jd(dc)) 36 | 37 | def to_datetime(self, dc): 38 | return julianday.to_datetime(self.to_jd(dc)) 39 | 40 | def from_datetime(self, date): 41 | return self.from_jd(julianday.from_datetime(date)) 42 | -------------------------------------------------------------------------------- /.github/workflows/python.yml: -------------------------------------------------------------------------------- 1 | # This file is part of convertdate. 2 | # http://github.com/fitnr/convertdate 3 | 4 | # Licensed under the MIT license: 5 | # http://opensource.org/licenses/MIT 6 | # Copyright (c) 2021, fitnr 7 | name: convertdate 8 | 9 | on: 10 | push: 11 | pull_request: 12 | branches: [master] 13 | 14 | jobs: 15 | build: 16 | runs-on: ubuntu-latest 17 | strategy: 18 | matrix: 19 | python-version: [3.7, 3.8, 3.9, "3.10.0"] 20 | steps: 21 | - uses: actions/checkout@v1 22 | - name: Set up Python ${{ matrix.python-version }} 23 | uses: actions/setup-python@v2 24 | with: 25 | python-version: ${{ matrix.python-version }} 26 | cache: 'pip' 27 | cache-dependency-path: setup.cfg 28 | - name: Install 29 | run: | 30 | python -m pip install --upgrade pip wheel 31 | pip install ".[tests]" 32 | - name: Test 33 | run: make cov 34 | 35 | pylint: 36 | runs-on: ubuntu-latest 37 | steps: 38 | - uses: actions/checkout@v1 39 | - name: Set up Python ${{ matrix.python-version }} 40 | uses: actions/setup-python@v2 41 | with: 42 | python-version: 3.9 43 | cache: 'pip' 44 | cache-dependency-path: setup.cfg 45 | - name: Install 46 | run: | 47 | python -m pip install --upgrade pip pylint 48 | pip install . 49 | - name: Pylint 50 | run: make pylint 51 | -------------------------------------------------------------------------------- /src/convertdate/iso.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # This file is part of convertdate. 3 | # http://github.com/fitnr/convertdate 4 | # Licensed under the MIT license: 5 | # http://opensource.org/licenses/MIT 6 | # Copyright (c) 2016, fitnr 7 | """Conversions for the `ISO calendar system `__.""" 8 | from calendar import isleap 9 | from datetime import date 10 | 11 | from . import gregorian 12 | from .utils import jwday, n_weeks 13 | 14 | MON = 0 15 | TUE = 1 16 | WED = 2 17 | THU = 3 18 | FRI = 4 19 | SAT = 5 20 | SUN = 6 21 | 22 | 23 | def to_jd(year, week, day): 24 | '''Return Julian day count of given ISO year, week, and day''' 25 | return day + n_weeks(SUN, gregorian.to_jd(year - 1, 12, 28), week) 26 | 27 | 28 | def from_jd(jd): 29 | '''Return tuple of ISO (year, week, day) for Julian day''' 30 | return from_gregorian(*gregorian.from_jd(jd)) 31 | 32 | 33 | def weeks_per_year(year): 34 | '''Number of ISO weeks in a year''' 35 | # 53 weeks: any year starting on Thursday and any leap year starting on Wednesday 36 | jan1 = jwday(gregorian.to_jd(year, 1, 1)) 37 | 38 | if jan1 == THU or (jan1 == WED and isleap(year)): 39 | return 53 40 | 41 | return 52 42 | 43 | 44 | def from_gregorian(year, month, day): 45 | return date(year, month, day).isocalendar() 46 | 47 | 48 | def to_gregorian(year, week, day): 49 | return gregorian.from_jd(to_jd(year, week, day)) 50 | 51 | 52 | def format(year, week, day): 53 | # pylint: disable=redefined-builtin 54 | return "{}-W{:02}-{}".format(year, week, day) 55 | -------------------------------------------------------------------------------- /setup.cfg: -------------------------------------------------------------------------------- 1 | # This file is part of convertdate. 2 | # http://github.com/fitnr/convertdate 3 | # Licensed under the MIT license: 4 | # http://opensource.org/licenses/MIT 5 | # Copyright (c) 2022, fitnr 6 | [metadata] 7 | name = convertdate 8 | version = attr: convertdate.__version__ 9 | description = Converts between Gregorian dates and other calendar systems 10 | author = Neil Freeman 11 | author_email = contact@fakeisthenewreal.org 12 | url = https://github.com/fitnr/convertdate 13 | long_description = file: README.md 14 | long_description_content_type = text/markdown 15 | license = MIT 16 | license_files = LICENSE 17 | keywords: calendar, date, time 18 | classifiers = 19 | License :: OSI Approved :: MIT License 20 | Natural Language :: English 21 | Programming Language :: Python 22 | Programming Language :: Python :: 3.7 23 | Programming Language :: Python :: 3.8 24 | Programming Language :: Python :: 3.9 25 | Programming Language :: Python :: 3.10 26 | Topic :: Religion 27 | Topic :: Scientific/Engineering :: Astronomy 28 | Operating System :: OS Independent 29 | 30 | [options] 31 | zip_safe = True 32 | python_requires = >= 3.7, <4 33 | package_dir = 34 | =src 35 | packages = find: 36 | install_requires = 37 | pymeeus>=0.3.13, <=1 38 | 39 | [options.packages.find] 40 | where = src 41 | 42 | [options.extras_require] 43 | tests = 44 | coverage 45 | docs = 46 | sphinx 47 | sphinx_rtd_theme 48 | myst_parser 49 | dev = 50 | build 51 | black 52 | isort 53 | pylint 54 | 55 | [options.entry_points] 56 | console_scripts = 57 | censusgeocode = censusgeocode.__main__:main 58 | -------------------------------------------------------------------------------- /src/convertdate/ordinal.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # This file is part of convertdate. 3 | # http://github.com/fitnr/convertdate 4 | # Licensed under the MIT license: 5 | # http://opensource.org/licenses/MIT 6 | # Copyright (c) 2016, fitnr 7 | """ 8 | The `ordinal date ` specifies the day 9 | of year as a number between 1 and 366. 10 | 11 | Ordinal dates are represented by a tuple: ``(year, dayofyear)`` 12 | """ 13 | from calendar import isleap 14 | from math import trunc 15 | 16 | from . import gregorian 17 | 18 | 19 | def to_jd(year, dayofyear): 20 | '''Return Julian day count of given ordinal date.''' 21 | return gregorian.to_jd(year, 1, 1) + dayofyear - 1 22 | 23 | 24 | def from_jd(jd): 25 | '''Convert a Julian day count to an ordinal date.''' 26 | year, _, _ = gregorian.from_jd(jd) 27 | return year, round(jd - gregorian.to_jd(year, 1, 1) + 1) 28 | 29 | 30 | def from_gregorian(year, month, day): 31 | """Convert a Gregorian date to an ordinal date.""" 32 | m = month + 1 33 | 34 | if m <= 3: 35 | m = m + 12 36 | 37 | leap = isleap(year) 38 | 39 | t = trunc(30.6 * m) + day - 122 + 59 + leap 40 | 41 | if t > 365 + leap: 42 | t = t - 365 - leap 43 | 44 | return year, t 45 | 46 | 47 | def to_gregorian(year, dayofyear): 48 | """Convert an ordinal date to a Gregorian date.""" 49 | leap = isleap(year) 50 | 51 | if dayofyear < 59 + leap: 52 | leap_adj = 0 53 | elif leap: 54 | leap_adj = 1 55 | else: 56 | leap_adj = 2 57 | 58 | month = trunc((((dayofyear - 1 + leap_adj) * 12) + 373) / 367) 59 | 60 | startofmonth = from_gregorian(year, month, 1) 61 | 62 | return year, month, dayofyear - startofmonth[1] + 1 63 | -------------------------------------------------------------------------------- /src/convertdate/__init__.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # This file is part of convertdate. 3 | # http://github.com/fitnr/convertdate 4 | # Licensed under the MIT license: 5 | # http://opensource.org/licenses/MIT 6 | # Copyright (c) 2016, 2020, 2021, 2022 fitnr 7 | """ 8 | The Convertdate library contains methods and functions for converting dates between 9 | different calendar systems. 10 | 11 | It was originally developed as as `Python Date Util <(http://sourceforge.net/projects/pythondateutil/>`__ 12 | by Phil Schwartz. It has been significantly updated and expanded. 13 | 14 | Most of the original code is ported from 15 | `Fourmilab's calendar converter `__, 16 | which was developed by John Walker. 17 | 18 | The algorithms are believed to be derived from: Meeus, Jean. `Astronomical Algorithms`, 19 | Richmond: Willmann-Bell, 1991 (ISBN 0-943396-35-2) 20 | """ 21 | from . import armenian 22 | from . import bahai 23 | from . import coptic 24 | from . import daycount 25 | from . import dublin 26 | from . import french_republican 27 | from . import gregorian 28 | from . import hebrew 29 | from . import holidays 30 | from . import indian_civil 31 | from . import iso 32 | from . import islamic 33 | from . import julian 34 | from . import julianday 35 | from . import mayan 36 | from . import persian 37 | from . import positivist 38 | from . import ordinal 39 | from . import utils 40 | 41 | __version__ = '2.4.0' 42 | 43 | __all__ = [ 44 | 'holidays', 45 | 'armenian', 46 | 'bahai', 47 | 'coptic', 48 | 'dublin', 49 | 'daycount', 50 | 'french_republican', 51 | 'gregorian', 52 | 'hebrew', 53 | 'indian_civil', 54 | 'islamic', 55 | 'iso', 56 | 'julian', 57 | 'julianday', 58 | 'mayan', 59 | 'persian', 60 | 'positivist', 61 | 'mayan', 62 | 'ordinal', 63 | 'utils', 64 | ] 65 | -------------------------------------------------------------------------------- /tests/test_indian_civil.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | import time 3 | 4 | from convertdate import gregorian, indian_civil 5 | 6 | from . import CalTestCase 7 | 8 | 9 | class TestConvertdate(CalTestCase): 10 | def setUp(self): 11 | self.tm = time.localtime() 12 | self.gregoriandate = (self.tm[0], self.tm[1], self.tm[2]) 13 | 14 | self.jd = gregorian.to_jd(self.gregoriandate[0], self.gregoriandate[1], self.gregoriandate[2]) 15 | 16 | self.c_greg = (1492, 10, 21) 17 | self.c = gregorian.to_jd(*self.c_greg) 18 | self.x = gregorian.to_jd(2016, 2, 29) 19 | 20 | self.jdcs = range(2159677, 2488395, 2000) 21 | 22 | def test_reflexive(self): 23 | self.reflexive(indian_civil) 24 | 25 | def test_inverse(self): 26 | self.assertEqual(self.jd, indian_civil.to_jd(*indian_civil.from_jd(self.jd))) 27 | 28 | def test_returntype(self): 29 | '''Check that from_jd, from_gregorian return integers''' 30 | self.assertSequenceType(indian_civil.from_jd(self.jd), int) 31 | self.assertSequenceType(indian_civil.from_gregorian(*self.c_greg), int) 32 | self.assertSequenceType(indian_civil.from_gregorian(2020, 6, 4), int) 33 | 34 | def test_negative_jd(self): 35 | self.assertSequenceEqual(indian_civil.from_jd(1.5), (-4791, 9, 5)) 36 | self.assertSequenceEqual(indian_civil.from_jd(0.5), (-4791, 9, 4)) 37 | self.assertSequenceEqual(indian_civil.from_jd(-0.5), (-4791, 9, 3)) 38 | self.assertSequenceEqual(indian_civil.from_jd(-1.5), (-4791, 9, 2)) 39 | 40 | def test_month_length_indian_civil(self): 41 | self.assertEqual(indian_civil.month_length(1922, 1), 31) 42 | self.assertEqual(indian_civil.month_length(1923, 1), 30) 43 | 44 | def test_monthcalendar_indian_civil(self): 45 | self.assertEqual(indian_civil.monthcalendar(1936, 8).pop(0).pop(4), 1) 46 | self.assertEqual(indian_civil.monthcalendar(1927, 2).pop(0).pop(4), 1) 47 | self.assertEqual(indian_civil.monthcalendar(1922, 1).pop().pop(4), 31) 48 | -------------------------------------------------------------------------------- /docs/conf.py: -------------------------------------------------------------------------------- 1 | # Configuration file for the Sphinx documentation builder. 2 | # 3 | # This file only contains a selection of the most common options. For a full 4 | # list see the documentation: 5 | # https://www.sphinx-doc.org/en/master/usage/configuration.html 6 | 7 | # -- Path setup -------------------------------------------------------------- 8 | 9 | # If extensions (or modules to document with autodoc) are in another directory, 10 | # add these directories to sys.path here. If the directory is relative to the 11 | # documentation root, use os.path.abspath to make it absolute, like shown here. 12 | # 13 | import os 14 | import sys 15 | sys.path.insert(0, os.path.abspath('../src')) 16 | 17 | 18 | # -- Project information ----------------------------------------------------- 19 | 20 | project = 'convertdate' 21 | copyright = '2020, Neil Freeman' 22 | author = 'Neil Freeman' 23 | 24 | # The full version, including alpha/beta/rc tags 25 | with open('../src/convertdate/__init__.py') as i: 26 | release = next(r for r in i.readlines() if '__version__' in r).split('=')[1].strip('"\' \n') 27 | 28 | # -- General configuration --------------------------------------------------- 29 | 30 | master_doc = 'index' 31 | 32 | # Add any Sphinx extension module names here, as strings. They can be 33 | # extensions coming with Sphinx (named 'sphinx.ext.*') or your custom 34 | # ones. 35 | extensions = [ 36 | 'sphinx.ext.autodoc', 37 | 'myst_parser' 38 | ] 39 | 40 | # Add any paths that contain templates here, relative to this directory. 41 | templates_path = ['_templates'] 42 | 43 | # List of patterns, relative to source directory, that match files and 44 | # directories to ignore when looking for source files. 45 | # This pattern also affects html_static_path and html_extra_path. 46 | exclude_patterns = ['_build', 'Thumbs.db', '.DS_Store'] 47 | 48 | html_sidebars = {'*': ['globaltoc.html', 'sourcelink.html']} 49 | 50 | # -- Options for HTML output ------------------------------------------------- 51 | 52 | # The theme to use for HTML and HTML Help pages. See the documentation for 53 | # a list of builtin themes. 54 | # 55 | html_theme = "sphinx_rtd_theme" 56 | html_theme_options = { 57 | 'display_version': True, 58 | } 59 | 60 | -------------------------------------------------------------------------------- /tests/test_julian.py: -------------------------------------------------------------------------------- 1 | from convertdate import julian 2 | 3 | from . import CalTestCase 4 | 5 | # 1492, 10, 12 6 | C = 2266295.5 7 | 8 | 9 | class TestJulian(CalTestCase): 10 | def test_julian_legal_date(self): 11 | try: 12 | julian.to_jd(1900, 2, 29) 13 | except ValueError: 14 | self.fail('Unexpected ValueError: "julian.to_jd(1900, 2, 29)"') 15 | 16 | self.assertRaises(ValueError, julian.to_jd, 2014, 2, 29) 17 | self.assertRaises(ValueError, julian.to_jd, 2014, 3, 32) 18 | self.assertRaises(ValueError, julian.to_jd, 2014, 4, 31) 19 | self.assertRaises(ValueError, julian.to_jd, 2014, 5, -1) 20 | 21 | def test_reflexive_julian(self): 22 | self.reflexive(julian) 23 | self.reflexive(julian, range(113957, 1574957, 365)) 24 | self.assertEqual(julian.from_jd(julian.to_jd(-4718, 3, 5)), (-4718, 3, 5)) 25 | 26 | def test_from_julian(self): 27 | jd = 2457447.5 28 | self.assertEqual(jd, julian.to_jd(*julian.from_jd(jd))) 29 | self.assertEqual(julian.from_jd(C), (1492, 10, 12)) 30 | self.assertEqual(julian.from_jd(2400000.5), (1858, 11, 5)) 31 | self.assertEqual(julian.from_jd(2399830.5), (1858, 5, 19)) 32 | self.assertEqual(julian.from_jd(0), (-4712, 1, 1)) 33 | self.assertEqual(julian.from_jd(-1763), (-4717, 3, 5)) 34 | 35 | def test_julian_inverse(self): 36 | self.reflexive(julian) 37 | 38 | def test_to_julian(self): 39 | self.assertEqual(julian.to_jd(1858, 11, 5), 2400000.5) 40 | self.assertEqual(julian.to_jd(1492, 10, 12), C) 41 | 42 | def test_month_length_julian(self): 43 | self.assertEqual(julian.month_length(1582, 10), 31) 44 | self.assertEqual(julian.month_length(1977, 2), 28) 45 | self.assertEqual(julian.month_length(1900, 2), 29) 46 | self.assertEqual(julian.month_length(1904, 2), 29) 47 | 48 | def test_monthcalendar_julian(self): 49 | self.assertEqual(julian.monthcalendar(1582, 10).pop(0).pop(1), 1) 50 | self.assertEqual(julian.monthcalendar(1582, 10).pop().pop(3), 31) 51 | 52 | def test_returntype(self): 53 | self.assertSequenceType(julian.from_gregorian(2020, 6, 4), int) 54 | -------------------------------------------------------------------------------- /src/convertdate/julianday.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # This file is part of convertdate. 3 | # http://github.com/fitnr/convertdate 4 | # Licensed under the MIT license: 5 | # http://opensource.org/licenses/MIT 6 | # Copyright (c) 2016, fitnr 7 | """ 8 | The `Julian day `__ 9 | is a continuous count of days since the beginning of the Julian era on January 1, 4713 BC. 10 | """ 11 | from datetime import datetime, timezone 12 | 13 | from . import gregorian, julian 14 | 15 | 16 | def to_datetime(jdc): 17 | '''Return a datetime for the input floating point julian day count''' 18 | year, month, day = gregorian.from_jd(jdc) 19 | 20 | # in jdc: 0.0 = noon, 0.5 = midnight 21 | # the 0.5 changes it to 0.0 = midnight, 0.5 = noon 22 | frac = (jdc + 0.5) % 1 23 | 24 | hours = int(24 * frac) 25 | 26 | mfrac = frac * 24 - hours 27 | mins = int(60 * round(mfrac, 6)) 28 | 29 | sfrac = mfrac * 60 - mins 30 | secs = int(60 * round(sfrac, 6)) 31 | 32 | msfrac = sfrac * 60 - secs 33 | 34 | # down to ms, which are 1/1000 of a second 35 | ms = int(1000 * round(msfrac, 6)) 36 | 37 | return datetime( 38 | year, 39 | month, 40 | day, 41 | int(hours), 42 | int(mins), 43 | int(secs), 44 | int(ms), 45 | tzinfo=timezone.utc 46 | ) 47 | 48 | 49 | def from_datetime(dt): 50 | '''Convert from ``datetime`` to julian day count.''' 51 | # take account of offset (if there isn't one, act like it's utc) 52 | try: 53 | dt = dt + dt.utcoffset() 54 | except TypeError: 55 | # Assuming UTC 56 | pass 57 | 58 | jdc = gregorian.to_jd(dt.year, dt.month, dt.day) 59 | 60 | hfrac = dt.hour / 24.0 61 | mfrac = round(dt.minute / (24.0 * 60), 5) 62 | sfrac = round(dt.second / (24.0 * 60 * 60), 5) 63 | msfrac = dt.microsecond / (24.0 * 60 * 60 * 1000) 64 | 65 | return jdc + hfrac + mfrac + sfrac + msfrac 66 | 67 | 68 | def to_gregorian(jdc): 69 | '''Convert from julian day count to Gregorian date.''' 70 | return gregorian.from_jd(jdc) 71 | 72 | 73 | def from_gregorian(year, month, day): 74 | '''Convert from Gregorian ``year``, ``month`` and ``day`` to julian day count.''' 75 | return gregorian.to_jd(year, month, day) 76 | 77 | 78 | def to_julian(jdc): 79 | '''Convert from julian day count to Julian date.''' 80 | return julian.from_jd(jdc) 81 | 82 | 83 | def from_julian(year, month, day): 84 | '''Convert from Julian ``year``, ``month`` and ``day`` to julian day count.''' 85 | return julian.to_jd(year, month, day) 86 | -------------------------------------------------------------------------------- /src/convertdate/coptic.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # This file is part of convertdate. 3 | # http:#github.com/fitnr/convertdate 4 | # Licensed under the MIT license: 5 | # http:#opensource.org/licenses/MIT 6 | # Copyright (c) 2017, fitnr 7 | """ 8 | The `Coptic calendar `__, 9 | also called the Alexandrian calendar, is a liturgical calendar used by the 10 | Coptic Orthodox Church and some communities in Egypt. It is a reformed version 11 | of the ancient Egyptian calendar. 12 | 13 | It consists of twelve months of 30 days, followed by a "little month" of five days 14 | (six in leap years). 15 | """ 16 | from math import floor 17 | 18 | from . import gregorian 19 | from .utils import jwday, monthcalendarhelper 20 | 21 | EPOCH = 1825029.5 22 | MONTHS = [ 23 | "Thout", 24 | "Paopi", 25 | "Hathor", 26 | "Koiak", 27 | "Tobi", 28 | "Meshir", 29 | "Paremhat", 30 | "Paremoude", 31 | "Pashons", 32 | "Paoni", 33 | "Epip", 34 | "Mesori", 35 | "Pi Kogi Enavot", 36 | ] 37 | WEEKDAYS = ["Tkyriaka", "Pesnau", "Pshoment", "Peftoou", "Ptiou", "Psoou", "Psabbaton"] 38 | 39 | 40 | def is_leap(year): 41 | "Determine whether this is a leap year." 42 | return year % 4 == 3 or year % 4 == -1 43 | 44 | 45 | def to_jd(year, month, day): 46 | "Retrieve the Julian date equivalent for this date" 47 | return day + (month - 1) * 30 + (year - 1) * 365 + floor(year / 4) + EPOCH - 1 48 | 49 | 50 | def from_jd(jdc): 51 | "Create a new date from a Julian date." 52 | cdc = floor(jdc) + 0.5 - EPOCH 53 | year = floor((cdc - floor((cdc + 366) / 1461)) / 365) + 1 54 | 55 | yday = jdc - to_jd(year, 1, 1) 56 | 57 | month = floor(yday / 30) + 1 58 | day = yday - (month - 1) * 30 + 1 59 | return int(year), int(month), int(day) 60 | 61 | 62 | def to_gregorian(year, month, day): 63 | return gregorian.from_jd(to_jd(year, month, day)) 64 | 65 | 66 | def from_gregorian(year, month, day): 67 | return from_jd(gregorian.to_jd(year, month, day)) 68 | 69 | 70 | def month_length(year, month): 71 | if month <= 12: 72 | return 30 73 | 74 | if month == 13: 75 | if is_leap(year): 76 | return 6 77 | 78 | return 5 79 | 80 | raise ValueError("Invalid month") 81 | 82 | 83 | def monthcalendar(year, month): 84 | start_weekday = jwday(to_jd(year, month, 1)) 85 | monthlen = month_length(year, month) 86 | return monthcalendarhelper(start_weekday, monthlen) 87 | 88 | 89 | def format(year, month, day): 90 | """Convert a Coptic date into a string with the format DD MONTH YYYY.""" 91 | # pylint: disable=redefined-builtin 92 | return "{0:d} {1:} {2:d}".format(day, MONTHS[month - 1], year) 93 | -------------------------------------------------------------------------------- /tests/test_ordinal.py: -------------------------------------------------------------------------------- 1 | from calendar import isleap 2 | 3 | from convertdate import ordinal 4 | 5 | from . import CalTestCase 6 | 7 | 8 | class TestOrdinal(CalTestCase): 9 | def test_reflexive(self): 10 | self.reflexive(ordinal, range(2458849, 2458849 + 7289)) 11 | 12 | def test_to_jd(self): 13 | self.assertEqual(ordinal.to_jd(1900, 1), 2415020.5) 14 | 15 | def test_from_jd(self): 16 | self.assertEqual(ordinal.from_jd(2415020.5), (1900, 1)) 17 | 18 | def test_ordinal_to_gregorian(self): 19 | self.assertEqual(ordinal.to_gregorian(2013, 1), (2013, 1, 1)) 20 | self.assertEqual(ordinal.to_gregorian(2013, 105), (2013, 4, 15)) 21 | self.assertEqual(ordinal.to_gregorian(2013, 32), (2013, 2, 1)) 22 | self.assertEqual(ordinal.to_gregorian(2012, 1), (2012, 1, 1)) 23 | self.assertEqual(ordinal.to_gregorian(2012, 31), (2012, 1, 31)) 24 | self.assertEqual(ordinal.to_gregorian(2012, 32), (2012, 2, 1)) 25 | self.assertEqual(ordinal.to_gregorian(2012, 52), (2012, 2, 21)) 26 | self.assertEqual(ordinal.to_gregorian(2012, 59), (2012, 2, 28)) 27 | self.assertEqual(ordinal.to_gregorian(2012, 60), (2012, 2, 29)) 28 | self.assertEqual(ordinal.to_gregorian(2012, 61), (2012, 3, 1)) 29 | self.assertEqual(ordinal.from_gregorian(2013, 1, 1), (2013, 1)) 30 | self.assertEqual(ordinal.from_gregorian(2013, 2, 1), (2013, 32)) 31 | self.assertEqual(ordinal.from_gregorian(2013, 3, 1), (2013, 60)) 32 | self.assertEqual(ordinal.from_gregorian(2013, 4, 15), (2013, 105)) 33 | 34 | def test_jan_1(self): 35 | self.assertEqual(ordinal.from_gregorian(2000, 1, 1), (2000, 1)) 36 | self.assertEqual(ordinal.from_gregorian(2004, 1, 1), (2004, 1)) 37 | self.assertEqual(ordinal.from_gregorian(1, 1, 1), (1, 1)) 38 | 39 | def test_dec_31(self): 40 | self.assertEqual(ordinal.to_gregorian(2001, 364), (2001, 12, 30), 'December 30, 2001') 41 | self.assertEqual(ordinal.to_gregorian(2004, 365), (2004, 12, 30)) 42 | self.assertEqual(ordinal.to_gregorian(2001, 365), (2001, 12, 31)) 43 | self.assertEqual(ordinal.to_gregorian(2004, 366), (2004, 12, 31)) 44 | 45 | self.assertEqual(ordinal.from_gregorian(2001, 12, 30), (2001, 364)) 46 | self.assertEqual(ordinal.from_gregorian(2004, 12, 30), (2004, 365)) 47 | self.assertEqual(ordinal.from_gregorian(2001, 12, 31), (2001, 365)) 48 | self.assertEqual(ordinal.from_gregorian(2004, 12, 31), (2004, 366)) 49 | 50 | def test_leap(self): 51 | for year in range(1995, 2005): 52 | self.assertEqual(ordinal.from_gregorian(year, 2, 28), (year, 59)) 53 | 54 | leap = 0 55 | if isleap(year): 56 | leap = 1 57 | self.assertEqual(ordinal.from_gregorian(year, 2, 29), (year, 60)) 58 | 59 | self.assertEqual(ordinal.from_gregorian(year, 3, 1), (year, 60 + leap)) 60 | -------------------------------------------------------------------------------- /src/convertdate/islamic.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # This file is part of convertdate. 3 | # http://github.com/fitnr/convertdate 4 | # Licensed under the MIT license: 5 | # http://opensource.org/licenses/MIT 6 | # Copyright (c) 2016, fitnr 7 | """Convert dates between the Hijri calendar and the Gregorian and Julian calendars.""" 8 | from math import ceil, floor 9 | 10 | from . import gregorian 11 | from .utils import jwday, monthcalendarhelper 12 | 13 | EPOCH = 1948439.5 14 | WEEKDAYS = ("al-'ahad", "al-'ithnayn", "ath-thalatha'", "al-'arb`a'", "al-khamis", "al-jum`a", "as-sabt") 15 | MONTHS = [ 16 | "al-Muḥarram", 17 | "Ṣafar", 18 | "Rabīʿ al-ʾAwwal", 19 | "Rabīʿ ath-Thānī,", 20 | "Jumādā al-ʾAwwal,", 21 | "Jumādā ath-Thāniyah,", 22 | "Rajab", 23 | "Shaʿbān", 24 | "Ramaḍān", 25 | "Shawwāl", 26 | "Zū al-Qaʿdah", 27 | "Zū al-Ḥijjah", 28 | ] 29 | HAS_29_DAYS = (2, 4, 6, 8, 10) 30 | HAS_30_DAYS = (1, 3, 5, 7, 9, 11) 31 | 32 | 33 | def leap(year): 34 | '''Is a given year a leap year in the Islamic calendar''' 35 | return (((year * 11) + 14) % 30) < 11 36 | 37 | 38 | def to_jd(year, month, day): 39 | '''Determine Julian day count from Islamic date''' 40 | return (day + ceil(29.5 * (month - 1)) + (year - 1) * 354 + floor((3 + (11 * year)) / 30) + EPOCH) - 1 41 | 42 | 43 | def from_jd(jd): 44 | '''Calculate Islamic date from Julian day''' 45 | jd = floor(jd) + 0.5 46 | year = floor(((30 * (jd - EPOCH)) + 10646) / 10631) 47 | month = min(12, ceil((jd - (29 + to_jd(year, 1, 1))) / 29.5) + 1) 48 | day = int(jd - to_jd(year, month, 1)) + 1 49 | return (year, month, day) 50 | 51 | 52 | def to_jd_gregorianyear(gregorianyear, islamic_month, islamic_day): 53 | # Gregorian year is either 578 or 623 years greater than Islamic year 54 | # we'll first try 622 if conversion to gregorian isn't the same 55 | # year that was passed to this method, then it must be 623. 56 | jan1 = gregorian.to_jd(gregorianyear, 1, 1) 57 | yi, mi, _ = from_jd(jan1) 58 | 59 | if mi > islamic_month: 60 | yi = yi + 1 61 | 62 | return to_jd(yi, islamic_month, islamic_day) 63 | 64 | 65 | def from_gregorian(year, month, day): 66 | return from_jd(gregorian.to_jd(year, month, day)) 67 | 68 | 69 | def to_gregorian(year, month, day): 70 | return gregorian.from_jd(to_jd(year, month, day)) 71 | 72 | 73 | def month_length(year, month): 74 | if month in HAS_30_DAYS or (month == 12 and leap(year)): 75 | return 30 76 | 77 | return 29 78 | 79 | 80 | def monthcalendar(year, month): 81 | start_weekday = jwday(to_jd(year, month, 1)) 82 | monthlen = month_length(year, month) 83 | return monthcalendarhelper(start_weekday, monthlen) 84 | 85 | 86 | def format(year, month, day): 87 | """Convert an Islamic date into a string with the format DD MONTH YYYY.""" 88 | # pylint: disable=redefined-builtin 89 | return "{0:d} {1:} {2:d}".format(day, MONTHS[month - 1], year) 90 | -------------------------------------------------------------------------------- /tests/test_mayan.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | import time 3 | 4 | from convertdate import gregorian, mayan 5 | 6 | from . import CalTestCase 7 | 8 | 9 | class TestMayan(CalTestCase): 10 | def setUp(self): 11 | self.gdate = 2021, 2, 5 12 | self.c_greg = (1492, 10, 21) 13 | self.c = gregorian.to_jd(*self.c_greg) 14 | self.jd = gregorian.to_jd(*self.gdate) 15 | self.jdcs = range(2159677, 2488395, 2000) 16 | 17 | def test_mayan_reflexive(self): 18 | assert self.jd == mayan.to_jd(*mayan.from_jd(self.jd)) 19 | 20 | self.reflexive(mayan) 21 | 22 | def test_mayan_count(self): 23 | assert mayan.to_jd(13, 0, 0, 0, 0) == 2456282.5 24 | assert mayan.from_gregorian(2012, 12, 21) == (13, 0, 0, 0, 0) 25 | assert mayan.to_gregorian(13, 0, 0, 0, 0) == (2012, 12, 21) 26 | assert mayan.from_jd(self.c) == (11, 13, 12, 4, 13) 27 | 28 | def test_mayan_haab(self): 29 | # haab 30 | assert mayan.HAAB[2] == 'Zip' 31 | assert mayan.HAAB.index("Xul") == 5 32 | assert mayan.to_haab(self.c) == (16, "Sotz'") 33 | assert mayan.to_haab(2456282.5) == (3, "K'ank'in") 34 | 35 | def test_mayan_tzolkin(self): 36 | # tzolkin 37 | assert mayan.TZOLKIN[0] == "Imix'" 38 | assert mayan.to_tzolkin(self.c) == (12, "B'en") 39 | assert mayan.to_tzolkin(2456282.5) == (4, 'Ajaw') 40 | assert mayan.to_tzolkin(2456850.5) == (13, 'Lamat') 41 | 42 | def test_mayan_convenience(self): 43 | 44 | self.assertEqual(mayan.lc_to_haab(0, 0, 0, 0, 0), (8, "Kumk'u")) 45 | assert mayan.lc_to_tzolkin(0, 0, 0, 0, 0) == (4, "Ajaw") 46 | 47 | assert mayan.lc_to_tzolkin(9, 16, 12, 5, 17) == (6, "Kab'an") 48 | assert mayan.lc_to_haab(9, 16, 12, 5, 17) == (10, "Mol") 49 | 50 | assert mayan.lc_to_haab_tzolkin(9, 16, 12, 5, 17) == "6 Kab'an 10 Mol" 51 | 52 | assert mayan.translate_haab("Wayeb'") == 'Nameless' 53 | 54 | def test_mayan_predictions(self): 55 | assert mayan.next_haab("Sotz'", self.c) == 2266280.5 56 | 57 | for h in mayan.HAAB: 58 | assert mayan.to_haab(mayan.next_haab(h, self.c)) == (1, h) 59 | 60 | assert mayan.next_tzolkin_haab((13, "Ajaw"), (3, "Kumk'u"), 2456849.5) == 2463662.5 61 | 62 | def test_mayan_monthcalendar(self): 63 | calendar = mayan.haab_monthcalendar(13, 0, 2, 11, 13) 64 | row = calendar[0] 65 | square = row[-1] 66 | assert isinstance(row, list) 67 | assert isinstance(square, tuple) 68 | assert row[7][0] == 1 69 | 70 | assert mayan.to_jd(*calendar[-1][-1][-1]) == 19 + mayan.to_jd(13, 0, 2, 11, 13) 71 | self.assertEqual(square, (6, (13, "Etz'nab'"), (13, 0, 2, 11, 18))) 72 | 73 | def test_mayan_generators(self): 74 | lcg = mayan.longcount_generator(13, 0, 2, 11, 13) 75 | assert next(lcg) == (13, 0, 2, 11, 13) 76 | assert next(lcg) == (13, 0, 2, 11, 14) 77 | assert next(lcg) == (13, 0, 2, 11, 15) 78 | 79 | tzg = mayan.tzolkin_generator(9, "Ix") 80 | self.assertEqual(next(tzg), (9, "Ix")) 81 | assert next(tzg) == (10, "Men") 82 | assert next(tzg) == (11, "K'ib'") 83 | 84 | def test_start_epoch(self): 85 | self.assertSequenceEqual(mayan.from_jd(mayan.EPOCH + 1), (0, 0, 0, 0, 1)) 86 | self.assertSequenceEqual(mayan.from_jd(mayan.EPOCH), (0, 0, 0, 0, 0)) 87 | 88 | with self.assertRaises(ValueError): 89 | mayan.from_jd(mayan.EPOCH - 1) 90 | -------------------------------------------------------------------------------- /src/convertdate/utils.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # This file is part of convertdate. 3 | # http://github.com/fitnr/convertdate 4 | # Licensed under the MIT license: 5 | # http://opensource.org/licenses/MIT 6 | # Copyright (c) 2016, fitnr 7 | """Helper utilities useful in several converters.""" 8 | import calendar 9 | import math 10 | 11 | TROPICALYEAR = 365.24219878 # Mean solar tropical year 12 | 13 | 14 | def amod(a, b): 15 | '''Modulus function which returns numerator if modulus is zero''' 16 | modded = int(a % b) 17 | return b if modded == 0 else modded 18 | 19 | 20 | def jwday(j): 21 | '''Calculate day of week from Julian day. Consider using ``calendar.weekday``!''' 22 | return math.trunc((j + 0.5)) % 7 23 | 24 | 25 | def weekday_before(weekday, jd): 26 | return jd - jwday(jd - weekday) 27 | 28 | 29 | def search_weekday(weekday, jd, direction, offset): 30 | ''' 31 | Determine the Julian date for the next or previous weekday 32 | 33 | Arguments: 34 | weekday (int): Day of week desired, 0 = Monday 35 | jd (float): Julian date to begin search 36 | direction(int): 1 = next weekday, -1 = last weekday 37 | offset(int): Offset from jd to begin search. 38 | ''' 39 | return weekday_before(weekday, jd + (direction * offset)) 40 | 41 | 42 | # Utility weekday functions, just wrappers for search_weekday 43 | 44 | 45 | def nearest_weekday(weekday, jd): 46 | return search_weekday(weekday, jd, 1, 3) 47 | 48 | 49 | def next_weekday(weekday, jd): 50 | return search_weekday(weekday, jd, 1, 7) 51 | 52 | 53 | def next_or_current_weekday(weekday, jd): 54 | return search_weekday(weekday, jd, 1, 6) 55 | 56 | 57 | def previous_weekday(weekday, jd): 58 | return search_weekday(weekday, jd, -1, 1) 59 | 60 | 61 | def previous_or_current_weekday(weekday, jd): 62 | return search_weekday(weekday, jd, 1, 0) 63 | 64 | 65 | def n_weeks(weekday, jd, nthweek): 66 | j = 7 * nthweek 67 | 68 | if nthweek > 0: 69 | j += previous_weekday(weekday, jd) 70 | else: 71 | j += next_weekday(weekday, jd) 72 | 73 | return j 74 | 75 | 76 | def monthcalendarhelper(start_weekday, month_length): 77 | end_weekday = start_weekday + (month_length - 1) % 7 78 | 79 | lpad = (start_weekday + 1) % 7 80 | rpad = (5 - end_weekday % 7) % 6 81 | 82 | days = [None] * lpad + list(range(1, 1 + month_length)) + rpad * [None] 83 | 84 | return [days[i : i + 7] for i in range(0, len(days), 7)] 85 | 86 | 87 | def nth_day_of_month(n, weekday, month, year): 88 | """ 89 | Return (year, month, day) tuple that represents nth weekday of month in year. 90 | If n==0, returns last weekday of month. Weekdays: Monday=0 91 | """ 92 | if not 0 <= n <= 5: 93 | raise IndexError("Nth day of month must be 0-5. Received: {}".format(n)) 94 | 95 | if not 0 <= weekday <= 6: 96 | raise IndexError("Weekday must be 0-6") 97 | 98 | firstday, daysinmonth = calendar.monthrange(year, month) 99 | 100 | # Get first WEEKDAY of month 101 | first_weekday_of_kind = 1 + (weekday - firstday) % 7 102 | 103 | if n == 0: 104 | # find last weekday of kind, which is 5 if these conditions are met, else 4 105 | if first_weekday_of_kind in [1, 2, 3] and first_weekday_of_kind + 28 <= daysinmonth: 106 | n = 5 107 | else: 108 | n = 4 109 | 110 | day = first_weekday_of_kind + ((n - 1) * 7) 111 | 112 | if day > daysinmonth: 113 | raise IndexError("No {}th day of month {}".format(n, month)) 114 | 115 | return (year, month, day) 116 | -------------------------------------------------------------------------------- /src/convertdate/indian_civil.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # This file is part of convertdate. 3 | # http://github.com/fitnr/convertdate 4 | # Licensed under the MIT license: 5 | # http://opensource.org/licenses/MIT 6 | # Copyright (c) 2016, fitnr 7 | """ 8 | The Indian Civil calendar, also called the Indian national calendar, or the Shalivahana Shaka calendar, 9 | was instituted following independence. It consists of twelve months of 31 or 30 days, with a 10 | leap day every four years. 11 | """ 12 | from calendar import isleap 13 | from math import floor 14 | 15 | from . import gregorian 16 | from .utils import jwday, monthcalendarhelper 17 | 18 | # 0 = Sunday 19 | WEEKDAYS = ( 20 | "Ravivāra", 21 | "Somavāra", 22 | "Maṅgalavāra", 23 | "Budhavāra", 24 | "Guruvāra", 25 | "Śukravāra", 26 | "Śanivāra", 27 | ) 28 | 29 | MONTHS = ( 30 | "Chaitra", 31 | "Vaishākha", 32 | "Jyēshtha", 33 | "Āshādha", 34 | "Shrāvana", 35 | "Bhādrapada", 36 | "Āshwin", 37 | "Kārtika", 38 | "Mārgashīrsha", 39 | "Pausha", 40 | "Māgha", 41 | "Phālguna", 42 | ) 43 | 44 | HAVE_31_DAYS = (2, 3, 4, 5, 6) 45 | HAVE_30_DAYS = (7, 8, 9, 10, 11, 12) 46 | 47 | SAKA_EPOCH = 78 48 | 49 | 50 | def to_jd(year, month, day): 51 | '''Obtain Julian day for Indian Civil date''' 52 | 53 | gyear = year + 78 54 | leap = isleap(gyear) 55 | # // Is this a leap year ? 56 | 57 | # 22 - leap = 21 if leap, 22 non-leap 58 | start = gregorian.to_jd(gyear, 3, 22 - leap) 59 | if leap: 60 | caitra = 31 61 | else: 62 | caitra = 30 63 | 64 | if month == 1: 65 | jd = start + (day - 1) 66 | else: 67 | jd = start + caitra 68 | m = month - 2 69 | m = min(m, 5) 70 | jd += m * 31 71 | if month >= 8: 72 | m = month - 7 73 | jd += m * 30 74 | 75 | jd += day - 1 76 | 77 | return jd 78 | 79 | 80 | def from_jd(jd): 81 | """Calculate Indian Civil date from Julian day 82 | Offset in years from Saka era to Gregorian epoch""" 83 | start = 80 84 | # Day offset between Saka and Gregorian 85 | 86 | jd = floor(jd) + 0.5 87 | gyear, _, _ = gregorian.from_jd(jd) # Gregorian date for Julian day 88 | leap = isleap(gyear) # Is this a leap year? 89 | # Tentative year in Saka era 90 | year = gyear - SAKA_EPOCH 91 | # JD at start of Gregorian year 92 | greg0 = gregorian.to_jd(gyear, 1, 1) 93 | yday = jd - greg0 # Day number (0 based) in Gregorian year 94 | 95 | if leap: 96 | caitra = 31 # Days in Caitra this year. 97 | else: 98 | caitra = 30 99 | 100 | if yday < start: 101 | # Day is at the end of the preceding Saka year 102 | year -= 1 103 | yday += caitra + (31 * 5) + (30 * 3) + 10 + start 104 | 105 | yday -= start 106 | if yday < caitra: 107 | month = 1 108 | day = yday + 1 109 | else: 110 | mday = yday - caitra 111 | if mday < 31 * 5: 112 | month = floor(mday / 31) + 2 113 | day = (mday % 31) + 1 114 | else: 115 | mday -= 31 * 5 116 | month = floor(mday / 30) + 7 117 | day = (mday % 30) + 1 118 | 119 | return year, month, int(day) 120 | 121 | 122 | def from_gregorian(year, month, day): 123 | return from_jd(gregorian.to_jd(year, month, day)) 124 | 125 | 126 | def to_gregorian(year, month, day): 127 | return gregorian.from_jd(to_jd(year, month, day)) 128 | 129 | 130 | def month_length(year, month): 131 | if month in HAVE_31_DAYS or (month == 1 and isleap(year - SAKA_EPOCH)): 132 | return 31 133 | return 30 134 | 135 | 136 | def monthcalendar(year, month): 137 | start_weekday = jwday(to_jd(year, month, 1)) 138 | monthlen = month_length(year, month) 139 | return monthcalendarhelper(start_weekday, monthlen) 140 | 141 | 142 | def format(year, month, day): 143 | """Convert a Indian Civil date into a string with the format DD MONTH YYYY.""" 144 | # pylint: disable=redefined-builtin 145 | return "{0:d} {1:} {2:d}".format(day, MONTHS[month - 1], year) 146 | -------------------------------------------------------------------------------- /tests/test_persian.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | from convertdate import gregorian, persian 3 | 4 | from . import CalTestCase 5 | 6 | # fmt: off 7 | JDS = ( 8 | 2130575, 2131306, 2132036, 2132767, 2133497, 2134228, 2134958, 2135689, 9 | 2136419, 2137150, 2137880, 2138611, 2139341, 2140072, 2140802, 2141533, 10 | 2142263, 2142994, 2143724, 2144455, 2145185, 2145916, 2146646, 2147377, 11 | 2148107, 2148838, 2149568, 2150299, 2151029, 2151759, 2152490, 2153220, 12 | 2153951, 2154681, 2155412, 2156142, 2156873, 2157603, 2158334, 2159064, 13 | 2159795, 2160525, 2161256, 2161986, 2162717, 2163447, 2164178, 2164908, 14 | 2165639, 2166369 15 | ) 16 | # fmt: on 17 | 18 | 19 | class testPersian(CalTestCase): 20 | def setUp(self): 21 | self.gdate = 2021, 2, 5 22 | self.jd = gregorian.to_jd(*self.gdate) 23 | self.jdcs = range(2159677, 2488395, 2000) 24 | 25 | def test_equinox_jd(self): 26 | data = [ 27 | (1000, 2086381), 28 | (1100, 2122905), 29 | (1199, 2159064), 30 | (1200, 2159430), 31 | (1201, 2159795), 32 | (1300, 2195954), 33 | (1400, 2232478), 34 | (1500, 2269002), 35 | (1600, 2305527), 36 | (1700, 2342051), 37 | (1800, 2378575), 38 | (1900, 2415099), 39 | (2000, 2451623), 40 | ] 41 | for gyear, jd in data: 42 | with self.subTest(y=gyear, jd=jd): 43 | self.assertEqual(persian.equinox_jd(gyear), jd) 44 | 45 | self.assertAlmostEqual(persian.equinox_jd(1620), 2312831, places=0) 46 | self.assertAlmostEqual(persian.equinox_jd(2021), 2459294, places=0) 47 | 48 | def test_inverse(self): 49 | self.assertEqual(self.jd, persian.to_jd(*persian.from_jd(self.jd))) 50 | 51 | def test_reflexive(self): 52 | self.reflexive(persian) 53 | 54 | def test_reverse_reflexive(self): 55 | date = 579, 9, 2 56 | self.assertEqual(persian.from_jd(persian.to_jd(*date)), date, 'from_jd(to_jd(*x)) == x') 57 | 58 | def test_to_jd_579(self): 59 | date = 579, 9, 2 60 | self.assertEqual(persian.to_jd(*date), 2159677.5) 61 | 62 | def test_from_jd_579(self): 63 | date = 579, 9, 2 64 | self.assertEqual(persian.from_jd(2159677.5), date) 65 | 66 | def test_leap(self): 67 | self.assertEqual(persian.leap(-101), False) 68 | 69 | def test_leapconvert(self): 70 | jd = 2121444.5 71 | self.assertSequenceEqual(persian.from_jd(jd), (475, 1, 1)) 72 | self.assertSequenceEqual(persian.from_jd(jd - 2), (474, 12, 28)) 73 | self.assertSequenceEqual(persian.from_jd(jd - 1), (474, 12, 29)) 74 | jd = 2121809.5 75 | self.assertSequenceEqual(persian.from_jd(jd), (475, 12, 30)) 76 | self.assertSequenceEqual(persian.from_jd(jd + 1), (476, 1, 1)) 77 | 78 | def test_newyears(self): 79 | years = (1, 750, 625, 600, 580, 1000, 1400) 80 | jds = (1948320, 2221886, 2176231, 2167100, 2159795, 2313197, 2459294) 81 | 82 | for y, jd in zip(years, jds): 83 | jd = jd + 0.5 84 | with self.subTest(y=y, jd=jd): 85 | self.assertEqual(persian.to_jd(y, 1, 1), jd) 86 | with self.subTest(jd=jd, y=y): 87 | self.assertEqual(persian.from_jd(jd), (y, 1, 1)) 88 | 89 | def test_examples(self): 90 | pairs = zip(range(500, 600, 2), JDS) 91 | 92 | for y, jd in pairs: 93 | jd = jd + 0.5 94 | with self.subTest(y=y, jd=jd): 95 | self.assertEqual(persian.to_jd(y, 1, 1), jd) 96 | with self.subTest(jd=jd, y=y): 97 | self.assertEqual(persian.from_jd(jd), (y, 1, 1)) 98 | 99 | self.assertEqual(persian.to_jd(2021, 3, 21), 2686191.5) 100 | self.assertEqual(persian.to_jd(2021, 3, 20), 2686190.5) 101 | 102 | def test_month_length_persian(self): 103 | self.assertEqual(persian.month_length(1354, 12), 30) 104 | self.assertEqual(persian.month_length(1355, 12), 29) 105 | 106 | def test_monthcalendar_persian(self): 107 | self.assertEqual(persian.monthcalendar(1393, 8).pop(0).pop(4), 1) 108 | self.assertEqual(persian.monthcalendar(1393, 8).pop().pop(0), 25) 109 | -------------------------------------------------------------------------------- /HISTORY.rst: -------------------------------------------------------------------------------- 1 | History 2 | ======= 3 | 4 | 2.4.0 5 | ----- 6 | * Drop official support for Python version 3.5 and 3.6, which have reached end-of-life. 7 | * Remove pytz dependency (#49) 8 | 9 | 2.3.2 10 | ----- 11 | * Switch Persian calendar to astronomical computation (was non-canonical algorithmic method) 12 | * Fix inappropriate uses of trunc, which caused several errors before the Julian Day epoch (#39, #40, #42) 13 | * Fix bug in `julian.leap` (#43) 14 | * Add official suppport and tests for Python 3.9 15 | * Raise ValueError for attempted conversion of dates before the Mayan epoch 16 | 17 | 2.3.1 18 | ----- 19 | * Adjust requirements to avoid a broken release (pymeeus 0.3.8) 20 | 21 | 2.3.0 22 | ----- 23 | * Fix bug in Bahai calculations during Ayyám-i-Há (#32). Thanks @chiuczek 24 | * Regularize variable names: 25 | 26 | - coptic: rename `MONTH_NAMES` to `MONTHS`, rename `DAY_NAMES` to `WEEKDAYS` 27 | - french_republican: add `MONTHS` 28 | - mayan: rename `HAAB_MONTHS` to `HAAB`, rename `TZOLKIN_NAMES` to `TZOLKIN` 29 | 30 | * Add some Jewish and Islamic holidays to `holidays`: `shemini_azeret`, `lag_baomer`, `tu_beshvat`, `tisha_bav`, `ramadan`, `ashura`, `eid_alfitr`, `eid_aladha` 31 | * Add docs. See https://convertdate.readthedocs.io/ 32 | * Fix December 31st bug in `ordinal` (#34) 33 | * Add Indigenous Peoples' Day and Juneteenth to `holidays`, deprecate Columbus day 34 | * Additional `format` methods for expressing dates as strings (#37, thanks @philosp) 35 | 36 | 2.2.2 37 | ----- 38 | * Add `observed` argument to the functions for several American holidays (#30) 39 | 40 | 2.2.1 41 | ----- 42 | * Add orthodox/eastern Easter calculations and docs 43 | * Add module for Armenian and Sarkawag regularisation 44 | * Bump pytz requirement 45 | 46 | 2.2.0 47 | ----- 48 | * Repair Bahai intercalary days bug (#13, thanks @bchurchill) 49 | * Replace pyephem, which is now in maintenance mode, with pymeeus. 50 | * Remove shebangs and regularize licenses (thanks @fabaff) 51 | * Convert readme to ascii (#16) 52 | 53 | 2.1.3 54 | ----- 55 | * Bump requirements 56 | 57 | 2.1.2 58 | ----- 59 | * Additional Jewish holidays (thanks, @ohadeytan) 60 | * Upload source distributions to Pypi (#10) 61 | 62 | 2.1.1 63 | ----- 64 | * Add Coptic (Alexandrian) calendar converter. 65 | * Add explicit support for Python 3.6. 66 | 67 | 2.1.0 68 | ----- 69 | * Change Exception thrown on illegal dates to ValueError. 70 | * Add Comte's Positivist calendar. 71 | * Bump requirement versions. 72 | 73 | 2.0.8 74 | ----- 75 | * Fix Persian weekday order (thanks, @meyt) 76 | 77 | 2.0.7 78 | ----- 79 | * Better Python 2/3 compatibility 80 | * Improve tests 81 | * bump pytz requirement 82 | 83 | 2.0.6 84 | ----- 85 | * Executing holidays module returns class with current year, not '2014'. 86 | * Expand tests for French Republican, Bahai, Persian, holidays 87 | * Add Travis CI testing 88 | 89 | Bug fixes: 90 | 91 | * Fix edge case when detecting the day of fall equinox in French Republican calendar 92 | * Fix minor methods of calculating French Republican leap years. 93 | * Change holidays.holidays.fathers_day from a method to a property 94 | * Add pulaski_day to holidays.holidays 95 | * Add pytz is dependency list 96 | 97 | 2.0.5 98 | ----- 99 | * Fix Yom Kippur error in holidays.py (issue #3) 100 | 101 | 2.0.4 102 | ----- 103 | * Typo in name of holidays.independence_day method 104 | * Fix major bug in ordinal.from_gregorian 105 | * Expand and organized tests 106 | 107 | 2.0.3.1 108 | ------- 109 | Features: 110 | 111 | * Add `ordinal` module, for counting the day of year 112 | * Added Mexican national holidays 113 | * Add `monthcalendar` functions 114 | 115 | Other changes: 116 | 117 | * Simplified logic in `ISO` module 118 | 119 | 2.0.3 120 | ----- 121 | Features: 122 | 123 | - Add list of day names and `day_name` function to French Republican converter 124 | - Add multiple conversion methods to the French Republican calendar 125 | - Add Dublin day count and Julian day count converters 126 | - Add month names to Bahai and Hebrew calendars. 127 | 128 | Other changes: 129 | 130 | - Clarify that weekdays run Monday=0 to Sunday=6 (#2) 131 | - Change Julian converter to use astronomical notation (0 = 1 BCE, -1 = 1 BCE) 132 | - Expanded tests 133 | 134 | 2.0.2 135 | ----- 136 | Features: 137 | 138 | * Add support for Python 3 (#1) 139 | -------------------------------------------------------------------------------- /src/convertdate/julian.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # This file is part of convertdate. 3 | # http://github.com/fitnr/convertdate 4 | # Licensed under the MIT license: 5 | # http://opensource.org/licenses/MIT 6 | # Copyright (c) 2016, fitnr 7 | """ 8 | The Julian calendar was implemented by Julius Caesar in 45 BC as a reformation of 9 | the Roman calendar. It is designed to follow the solar year, with a standard year 10 | of 365 days and a quadrennial leap year with an intercalary day (29 February). 11 | 12 | For the first several centuries of its use, the Julian calendar did not have a 13 | single year-numbering system. The Romans initially specific years with the names of political 14 | leaders. Later on, different areas employed different era with various epochs. 15 | Between the sixth and eighth centuries, western Europe adopted the Anno Domini convention. 16 | 17 | This numbering system does not include a year 0. However, for dates before 1, 18 | this module uses the astronomical convention of including a year 0 to simplify 19 | mathematical comparisons across epochs. To present a date in the standard 20 | convention, use the :meth:`julian.format` function. 21 | """ 22 | from datetime import date 23 | from math import floor 24 | 25 | from .gregorian import from_jd as gregorian_from_jd 26 | from .gregorian import to_jd as gregorian_to_jd 27 | from .utils import jwday, monthcalendarhelper 28 | 29 | J0000 = 1721424.5 # Julian date of Gregorian epoch: 0000-01-01 30 | J1970 = 2440587.5 # Julian date at Unix epoch: 1970-01-01 31 | JMJD = 2400000.5 # Epoch of Modified Julian Date system 32 | 33 | JULIAN_EPOCH = 1721423.5 34 | J2000 = 2451545.0 # Julian day of J2000 epoch 35 | JULIANCENTURY = 36525.0 # Days in Julian century 36 | 37 | HAVE_30_DAYS = (4, 6, 9, 11) 38 | HAVE_31_DAYS = (1, 3, 5, 7, 8, 10, 12) 39 | 40 | 41 | def leap(year): 42 | return year % 4 == 0 43 | 44 | 45 | def month_length(year, month): 46 | if month == 2: 47 | daysinmonth = 29 if leap(year) else 28 48 | else: 49 | daysinmonth = 30 if month in HAVE_30_DAYS else 31 50 | 51 | return daysinmonth 52 | 53 | 54 | def legal_date(year, month, day): 55 | '''Check if this is a legal date in the Julian calendar''' 56 | daysinmonth = month_length(year, month) 57 | 58 | if not 0 < day <= daysinmonth: 59 | raise ValueError("Month {} doesn't have a day {}".format(month, day)) 60 | 61 | return True 62 | 63 | 64 | def from_jd(jd): 65 | '''Calculate Julian calendar date from Julian day''' 66 | jd += 0.5 67 | a = floor(jd) 68 | b = a + 1524 69 | c = floor((b - 122.1) / 365.25) 70 | d = floor(365.25 * c) 71 | e = floor((b - d) / 30.6001) 72 | if e < 14: 73 | month = floor(e - 1) 74 | else: 75 | month = floor(e - 13) 76 | if month > 2: 77 | year = floor(c - 4716) 78 | else: 79 | year = floor(c - 4715) 80 | day = b - d - floor(30.6001 * e) 81 | return (year, month, day) 82 | 83 | 84 | def to_jd(year, month, day): 85 | '''Convert to Julian day using astronomical years (0 = 1 BC, -1 = 2 BC)''' 86 | legal_date(year, month, day) 87 | 88 | # Algorithm as given in Meeus, Astronomical Algorithms, Chapter 7, page 61 89 | if month <= 2: 90 | year -= 1 91 | month += 12 92 | 93 | return (floor((365.25 * (year + 4716))) + floor((30.6001 * (month + 1))) + day) - 1524.5 94 | 95 | 96 | def from_gregorian(year, month, day): 97 | '''Convert a Gregorian date to a Julian date.''' 98 | return from_jd(gregorian_to_jd(year, month, day)) 99 | 100 | 101 | def to_gregorian(year, month, day): 102 | '''Convert a Julian date to a Gregorian date.''' 103 | return gregorian_from_jd(to_jd(year, month, day)) 104 | 105 | 106 | def monthcalendar(year, month): 107 | ''' 108 | Returns a matrix representing a month’s calendar. Each row represents a week; 109 | days outside of the month are represented by zeros. Each week begins with Sunday. 110 | ''' 111 | start_weekday = jwday(to_jd(year, month, 1)) 112 | monthlen = month_length(year, month) 113 | return monthcalendarhelper(start_weekday, monthlen) 114 | 115 | 116 | def format(year, month, day, format_string="%-d %B %y"): 117 | # pylint: disable=redefined-builtin 118 | epoch = '' 119 | if year <= 0: 120 | year = (year - 1) * -1 121 | epoch = ' BCE' 122 | d = date(year, month, day) 123 | return d.strftime(format_string) + epoch 124 | -------------------------------------------------------------------------------- /tests/test_positivist.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | import unittest 3 | 4 | from convertdate.data import positivist as data 5 | from convertdate.positivist import EPOCH, dayname, festival, from_gregorian, from_jd, legal_date, to_gregorian, to_jd 6 | 7 | 8 | class TestGregorian(unittest.TestCase): 9 | def setUp(self): 10 | pass 11 | 12 | def test_epoch(self): 13 | self.assertEqual(to_jd(1, 1, 1), EPOCH) 14 | self.assertEqual(to_jd(1, 1, 2), EPOCH + 1) 15 | self.assertEqual(to_jd(2, 1, 1), EPOCH + 365.0) 16 | self.assertEqual(from_jd(EPOCH), (1, 1, 1)) 17 | 18 | def test_to_gregorian(self): 19 | self.assertEqual(to_gregorian(228, 13, 25), (2016, 12, 26)) 20 | self.assertEqual(to_gregorian(228, 13, 25), (2016, 12, 26)) 21 | 22 | def test_legaldate(self): 23 | self.assertTrue(legal_date(1, 1, 1)) 24 | with self.assertRaises(ValueError): 25 | legal_date(0, 1, 1) 26 | 27 | with self.assertRaises(ValueError): 28 | legal_date(1, -1, 1) 29 | 30 | with self.assertRaises(ValueError): 31 | legal_date(1, 14, 3) 32 | 33 | with self.assertRaises(ValueError): 34 | legal_date(1, 16, 3) 35 | 36 | def test_from_jd(self): 37 | self.assertTrue(legal_date(*from_jd(2375479.5))) 38 | assert legal_date(*from_jd(2376479.5)) 39 | assert legal_date(*from_jd(2378479.5)) 40 | assert legal_date(*from_jd(2379479.5)) 41 | 42 | with self.assertRaises(ValueError): 43 | from_jd(EPOCH - 0.5) 44 | 45 | def test_reflexive_jd(self): 46 | self.assertEqual(from_jd(to_jd(1, 1, 1)), (1, 1, 1)) 47 | self.assertEqual(from_jd(to_jd(4, 1, 1)), (4, 1, 1)) 48 | self.assertEqual(from_jd(to_jd(4, 14, 1)), (4, 14, 1)) 49 | self.assertEqual(from_jd(to_jd(4, 14, 2)), (4, 14, 2)) 50 | self.assertEqual(from_jd(to_jd(10, 1, 1)), (10, 1, 1)) 51 | self.assertEqual(from_jd(to_jd(12, 1, 1)), (12, 1, 1)) 52 | self.assertEqual(from_jd(to_jd(13, 1, 1)), (13, 1, 1)) 53 | self.assertEqual(from_jd(to_jd(13, 1, 2)), (13, 1, 2)) 54 | self.assertEqual(from_jd(to_jd(13, 1, 3)), (13, 1, 3)) 55 | self.assertEqual(from_jd(to_jd(13, 1, 5)), (13, 1, 5)) 56 | self.assertEqual(from_jd(to_jd(13, 1, 7)), (13, 1, 7)) 57 | self.assertEqual(from_jd(to_jd(13, 1, 14)), (13, 1, 14)) 58 | self.assertEqual(from_jd(to_jd(13, 1, 28)), (13, 1, 28)) 59 | self.assertEqual(from_jd(to_jd(13, 2, 28)), (13, 2, 28)) 60 | self.assertEqual(from_jd(to_jd(13, 6, 1)), (13, 6, 1)) 61 | self.assertEqual(from_jd(to_jd(14, 1, 1)), (14, 1, 1)) 62 | self.assertEqual(from_jd(to_jd(16, 1, 1)), (16, 1, 1)) 63 | self.assertEqual(from_jd(to_jd(50, 1, 1)), (50, 1, 1)) 64 | self.assertEqual(from_jd(to_jd(99, 1, 1)), (99, 1, 1)) 65 | self.assertEqual(from_jd(to_jd(100, 1, 1)), (100, 1, 1)) 66 | self.assertEqual(from_jd(to_jd(100, 13, 25)), (100, 13, 25)) 67 | self.assertEqual(from_jd(to_jd(120, 13, 25)), (120, 13, 25)) 68 | self.assertEqual(from_jd(to_jd(50, 13, 25)), (50, 13, 25)) 69 | self.assertEqual(from_jd(to_jd(200, 1, 5)), (200, 1, 5)) 70 | self.assertEqual(from_jd(to_jd(250, 14, 1)), (250, 14, 1)) 71 | 72 | def test_reflexive_jd2(self): 73 | assert len(from_jd(2375479.5)) == 3 74 | self.assertEqual(to_jd(*from_jd(2375479.5)), 2375479.5) 75 | self.assertEqual(to_jd(*from_jd(2376479.5)), 2376479.5) 76 | self.assertEqual(to_jd(*from_jd(2378479.5)), 2378479.5) 77 | self.assertEqual(to_jd(*from_jd(2379479.5)), 2379479.5) 78 | 79 | def test_reflexive_gregorian(self): 80 | self.assertEqual(from_gregorian(*to_gregorian(100, 13, 25)), (100, 13, 25)) 81 | self.assertEqual(from_gregorian(*to_gregorian(120, 13, 25)), (120, 13, 25)) 82 | self.assertEqual(from_gregorian(*to_gregorian(50, 13, 25)), (50, 13, 25)) 83 | self.assertEqual(from_gregorian(*to_gregorian(200, 1, 5)), (200, 1, 5)) 84 | self.assertEqual(from_gregorian(*to_gregorian(250, 14, 1)), (250, 14, 1)) 85 | 86 | def test_named_day(self): 87 | self.assertEqual(dayname(228, 13, 25), ('Bichat', "Félix Vicq-d'Azyr")) 88 | self.assertEqual(dayname(228, 1, 1), ('Moses', "Cadmus")) 89 | self.assertEqual(dayname(227, 1, 1), ('Moses', "Prometheus")) 90 | self.assertEqual(dayname(1, 1, 1), ('Moses', "Prometheus")) 91 | 92 | def test_festival(self): 93 | self.assertIsNone(festival(1, 2)) 94 | self.assertEqual(festival(1, 1), data.FESTIVALS.get((1, 1))) 95 | -------------------------------------------------------------------------------- /tests/test_armenian.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | import time 3 | import unittest 4 | 5 | from convertdate import julianday 6 | from convertdate.armenian import ( 7 | _valid_date, 8 | from_gregorian, 9 | from_jd, 10 | from_julian, 11 | leap, 12 | month_length, 13 | to_gregorian, 14 | to_jd, 15 | to_julian, 16 | tostring, 17 | ) 18 | 19 | from . import CalTestCase 20 | 21 | 22 | class TestArmenian(CalTestCase): 23 | def setUp(self): 24 | self.now = time.localtime() 25 | self.today = julianday.from_gregorian(self.now[0], self.now[1], self.now[2]) 26 | 27 | def testValidDate(self): 28 | self.assertTrue(_valid_date(1, 1, 1)) 29 | 30 | self.assertTrue(_valid_date(533, 1, 1, method="sarkawag")) 31 | 32 | with self.assertRaises(ValueError): 33 | _valid_date(401, 1, 1, method="sarkawag") 34 | 35 | with self.assertRaises(ValueError): 36 | _valid_date(30, 4, 31) 37 | 38 | with self.assertRaises(ValueError): 39 | _valid_date(536, 13, 6) 40 | 41 | with self.assertRaises(ValueError): 42 | from_jd(1.5) 43 | 44 | self.assertTrue(_valid_date(536, 13, 6, method="sarkawag")) 45 | 46 | def testReflexive(self): 47 | self.assertEqual(self.today, to_jd(*from_jd(self.today))) 48 | self.assertEqual(self.today, to_jd(*from_jd(self.today, "sarkawag"), method="sarkawag")) 49 | for jd in range(2159677, 2488395, 2000): 50 | jd = jd + 0.5 51 | self.assertEqual(jd, to_jd(*from_jd(jd))) 52 | self.assertEqual(jd, to_jd(*from_jd(jd, "sarkawag"), method="sarkawag")) 53 | 54 | def testLeap(self): 55 | self.assertEqual(True, leap(600)) 56 | self.assertEqual(False, leap(601)) 57 | 58 | def testGregorian(self): 59 | self.assertEqual((2019, 11, 3), to_gregorian(1469, 4, 14)) 60 | self.assertEqual((1469, 4, 14), from_gregorian(2019, 11, 3)) 61 | 62 | def testMonthLength(self): 63 | self.assertEqual(30, month_length(600, 1)) 64 | self.assertEqual(5, month_length(600, 13)) 65 | self.assertEqual(6, month_length(600, 13, "sarkawag")) 66 | 67 | def testJulian(self): 68 | cases = [ 69 | # first date of the calendar 70 | [(1, 1, 1), (552, 7, 11)], 71 | # last day of the year 72 | [(1, 13, 5), (553, 7, 10)], 73 | # leap year moves the calendar 74 | [(4, 13, 5), (556, 7, 9)], 75 | [(5, 1, 1), (556, 7, 10)], 76 | # check month boundaries for an entire year 77 | [(420, 1, 1), (971, 3, 29)], 78 | [(420, 1, 30), (971, 4, 27)], 79 | [(420, 2, 1), (971, 4, 28)], 80 | [(420, 2, 30), (971, 5, 27)], 81 | [(420, 3, 1), (971, 5, 28)], 82 | [(420, 3, 30), (971, 6, 26)], 83 | [(420, 4, 1), (971, 6, 27)], 84 | [(420, 4, 30), (971, 7, 26)], 85 | [(420, 5, 1), (971, 7, 27)], 86 | [(420, 5, 30), (971, 8, 25)], 87 | [(420, 6, 1), (971, 8, 26)], 88 | [(420, 6, 30), (971, 9, 24)], 89 | [(420, 7, 1), (971, 9, 25)], 90 | [(420, 7, 30), (971, 10, 24)], 91 | [(420, 8, 1), (971, 10, 25)], 92 | [(420, 8, 30), (971, 11, 23)], 93 | [(420, 9, 1), (971, 11, 24)], 94 | [(420, 9, 30), (971, 12, 23)], 95 | [(420, 10, 1), (971, 12, 24)], 96 | [(420, 10, 30), (972, 1, 22)], 97 | [(420, 11, 1), (972, 1, 23)], 98 | [(420, 11, 30), (972, 2, 21)], 99 | [(420, 12, 1), (972, 2, 22)], 100 | [(420, 12, 30), (972, 3, 22)], 101 | [(420, 13, 1), (972, 3, 23)], 102 | [(420, 13, 5), (972, 3, 27)], 103 | # check month boundaries around Julian leap year 104 | [(512, 13, 1), (1064, 2, 29)], 105 | [(512, 13, 2), (1064, 3, 1)], 106 | [(513, 1, 1), (1064, 3, 5)], 107 | # check the two calendars in 1084 108 | [(533, 6, 15), (1084, 8, 11)], 109 | ] 110 | 111 | for a, j in cases: 112 | self.assertEqual(a, from_julian(*j)) 113 | 114 | for a, j in cases: 115 | self.assertEqual(j, to_julian(*a)) 116 | 117 | self.assertEqual((533, 1, 1), from_julian(1084, 8, 11, method="sarkawag")) 118 | self.assertEqual((533, 13, 5), from_julian(1085, 8, 10, method="sarkawag")) 119 | self.assertEqual((536, 13, 6), from_julian(1088, 8, 10, method="sarkawag")) 120 | self.assertEqual((537, 1, 1), from_julian(1088, 8, 11, method="sarkawag")) 121 | 122 | def testTostring(self): 123 | self.assertEqual('14 trē 1469', tostring(1469, 4, 14)) 124 | -------------------------------------------------------------------------------- /tests/test_gregorian.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | import time 3 | 4 | from convertdate import gregorian, julian 5 | 6 | from . import CalTestCase 7 | 8 | 9 | class TestGregorian(CalTestCase): 10 | def setUp(self): 11 | self.tm = time.localtime() 12 | self.gregoriandate = (self.tm[0], self.tm[1], self.tm[2]) 13 | 14 | self.jd = gregorian.to_jd(self.gregoriandate[0], self.gregoriandate[1], self.gregoriandate[2]) 15 | 16 | self.c_greg = (1492, 10, 21) 17 | self.c = gregorian.to_jd(*self.c_greg) 18 | 19 | self.jdcs = range(2159677, 2488395, 2000) 20 | 21 | def test_gregorian(self): 22 | assert gregorian.to_jd(*self.gregoriandate) == self.jd 23 | assert gregorian.to_jd2(*self.gregoriandate) == self.jd 24 | 25 | self.assertEqual(self.c, 2266295.5) 26 | assert gregorian.to_jd(2000, 1, 1) == 2451544.5 27 | 28 | assert gregorian.to_jd2(2000, 1, 1) == 2451544.5 29 | 30 | self.reflexive(gregorian) 31 | self.reflexive(gregorian, range(113957, 1574957, 365)) 32 | 33 | def test_gregorian_proleptic(self): 34 | self.assertEqual(gregorian.to_jd(72, 6, 27), 1747535.5) 35 | assert gregorian.to_jd2(72, 6, 27) == 1747535.5 36 | 37 | for y in range(int(gregorian.EPOCH), int(gregorian.EPOCH) - 10000, -250): 38 | assert gregorian.to_jd(*gregorian.from_jd(y)) == y - 0.5 39 | 40 | assert gregorian.from_jd(gregorian.to_jd(-1, 3, 1)) == (-1, 3, 1) 41 | assert gregorian.from_jd(gregorian.to_jd(-100, 7, 1)) == (-100, 7, 1) 42 | assert gregorian.from_jd(gregorian.to_jd(-500, 12, 31)) == (-500, 12, 31) 43 | assert gregorian.from_jd(gregorian.to_jd(-1000, 1, 1)) == (-1000, 1, 1) 44 | 45 | def test_gregorian_pre_epoch(self): 46 | j = julian.to_jd(-4716, 3, 1) 47 | g = gregorian.to_jd(-4716, 1, 23) 48 | self.assertEqual(g, j) 49 | self.assertEqual(g - 1, j - 1) 50 | self.assertEqual(g - 1, gregorian.to_jd(-4716, 1, 22)) 51 | self.assertEqual(gregorian.to_jd(-4716, 1, 22), j - 1) 52 | self.assertEqual(gregorian.to_jd(-4716, 1, 22), julian.to_jd(-4716, 2, 29)) 53 | 54 | def test_from_gregorian_20thc(self): 55 | self.assertEqual(gregorian.from_jd(2418934.0), (1910, 9, 19)) 56 | self.assertEqual(gregorian.from_jd(2433360.0), (1950, 3, 19)) 57 | self.assertEqual(gregorian.from_jd(2437970.0), (1962, 11, 1)) 58 | self.assertEqual(gregorian.from_jd(2447970.0), (1990, 3, 19)) 59 | self.assertEqual(gregorian.from_jd(2456967.5), (2014, 11, 6)) 60 | 61 | def test_to_gregorian(self): 62 | self.assertEqual(gregorian.to_jd(2014, 11, 5), 2456966.5) 63 | 64 | assert gregorian.to_jd(2012, 3, 1) == 1 + gregorian.to_jd(2012, 2, 29) 65 | 66 | assert gregorian.from_jd(gregorian.to_jd(2012, 2, 29) + 1) == (2012, 3, 1) 67 | assert gregorian.from_jd(gregorian.to_jd(2011, 2, 28) + 1) == (2011, 3, 1) 68 | 69 | assert gregorian.from_jd(gregorian.to_jd(2012, 3, 2) - 2) == (2012, 2, 29) 70 | assert gregorian.from_jd(gregorian.to_jd(2011, 3, 2) - 2) == (2011, 2, 28) 71 | 72 | def test_gregorian_1_ma(self): 73 | assert gregorian.to_jd(*self.c_greg) == 2266295.5 74 | 75 | def test_gregorian_2_ma(self): 76 | assert gregorian.to_jd2(*self.c_greg) == 2266295.5 77 | 78 | def test_gregorian_julian_dif_proleptic(self): 79 | self.assertEqual(julian.to_jd(1500, 5, 10), gregorian.to_jd(1500, 5, 20)) 80 | assert julian.to_jd(1300, 5, 10) == gregorian.to_jd(1300, 5, 18) 81 | assert julian.to_jd(1000, 5, 10) == gregorian.to_jd(1000, 5, 16) 82 | assert julian.to_jd(900, 5, 10) == gregorian.to_jd(900, 5, 15) 83 | assert julian.to_jd(300, 5, 10) == gregorian.to_jd(300, 5, 11) 84 | assert julian.to_jd(200, 5, 10) == gregorian.to_jd(200, 5, 10) 85 | assert julian.to_jd(100, 5, 10) == gregorian.to_jd(100, 5, 9) 86 | assert julian.to_jd(-1, 5, 10) == gregorian.to_jd(-1, 5, 8) 87 | 88 | def test_year_zero(self): 89 | assert gregorian.to_jd(1, 1, 1) == 1.0 + gregorian.to_jd(0, 12, 31) 90 | assert julian.to_jd(1, 1, 1) == 1.0 + julian.to_jd(0, 12, 31) 91 | 92 | assert julian.from_jd(julian.to_jd(1, 1, 1) - 1) == (0, 12, 31) 93 | self.assertEqual(gregorian.from_jd(gregorian.to_jd(1, 1, 1) - 1), (0, 12, 31)) 94 | 95 | def test_legal_date(self): 96 | self.assertRaises(ValueError, gregorian.to_jd, 1900, 2, 29) 97 | self.assertRaises(ValueError, gregorian.to_jd, 2014, 2, 29) 98 | self.assertRaises(ValueError, gregorian.to_jd, 2014, 3, 32) 99 | self.assertRaises(ValueError, gregorian.to_jd, 2014, 4, 31) 100 | self.assertRaises(ValueError, gregorian.to_jd, 2014, 5, -1) 101 | -------------------------------------------------------------------------------- /src/convertdate/persian.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # This file is part of convertdate. 3 | # http://github.com/fitnr/convertdate 4 | # Licensed under the MIT license: 5 | # http://opensource.org/licenses/MIT 6 | # Copyright (c) 2016, fitnr 7 | """ 8 | The modern Persian calendar, or the Solar Hijri calendar, was adopted in 1911. 9 | It consists of twelve months of 30 or 31 days. The new year always falls on the 10 | March equinox. 11 | """ 12 | from math import ceil, floor 13 | 14 | from pymeeus.Epoch import Epoch 15 | from pymeeus.Sun import Sun 16 | 17 | from . import gregorian 18 | from .utils import TROPICALYEAR, jwday, monthcalendarhelper 19 | 20 | EPOCH = 1948320.5 21 | WEEKDAYS = ("Doshanbeh", "Seshhanbeh", "Chaharshanbeh", "Panjshanbeh", "Jomeh", "Shanbeh", "Yekshanbeh") 22 | 23 | MONTHS = [ 24 | "Farvardin", 25 | "Ordibehesht", 26 | "Khordad", 27 | "Tir", 28 | "Mordad", 29 | "Shahrivar", 30 | "Mehr", 31 | "Aban", 32 | "Azar", 33 | "Dey", 34 | "Bahman", 35 | "Esfand", 36 | ] 37 | 38 | HAS_31_DAYS = (1, 2, 3, 4, 5, 6) 39 | HAS_30_DAYS = (7, 8, 9, 10, 11) 40 | 41 | 42 | def leap(year): 43 | '''Is a given year a leap year in the Persian calendar ?''' 44 | return (to_jd(year + 1, 1, 1) - to_jd(year, 1, 1)) > 365 45 | 46 | 47 | def equinox_jd(gyear): 48 | """Calculate Julian day during which the March equinox, reckoned from the 49 | Tehran meridian, occurred for a given Gregorian year.""" 50 | mean_jd = Sun.get_equinox_solstice(gyear, target='spring') 51 | deltat_jd = mean_jd - Epoch.tt2ut(gyear, 3) / (24 * 60 * 60.) 52 | # Apparent JD in universal time 53 | apparent_jd = deltat_jd + (Sun.equation_of_time(deltat_jd)[0] / (24 * 60.)) 54 | # Correct for meridian of Tehran + 52.5 degrees 55 | return floor(apparent_jd.jde() + (52.5 / 360)) 56 | 57 | 58 | def last_equinox_jd(jd): 59 | """Return the Julian date of spring equinox immediately preceeding the 60 | given Julian date.""" 61 | guessyear = gregorian.from_jd(jd)[0] 62 | last_equinox = equinox_jd(guessyear) 63 | 64 | while last_equinox > jd: 65 | guessyear = guessyear - 1 66 | last_equinox = equinox_jd(guessyear) 67 | 68 | next_equinox = last_equinox - 1 69 | 70 | while not last_equinox <= jd < next_equinox: 71 | last_equinox = next_equinox 72 | guessyear = guessyear + 1 73 | next_equinox = equinox_jd(guessyear) 74 | 75 | return last_equinox 76 | 77 | 78 | def jd_to_pyear(jd): 79 | """ 80 | Determine the year in the Persian astronomical calendar in which a given 81 | Julian day falls. 82 | 83 | Returns: 84 | tuple - (Persian year, Julian day number containing equinox for this year) 85 | """ 86 | lasteq = last_equinox_jd(jd) 87 | return round((lasteq - EPOCH) / TROPICALYEAR) + 1, lasteq 88 | 89 | 90 | def to_jd(year, month, day): 91 | '''Determine Julian day from Persian date''' 92 | guess = (EPOCH - 1) + (TROPICALYEAR * ((year - 1) - 1)) 93 | y0, equinox = year - 1, 0 94 | 95 | while y0 < year: 96 | y0, equinox = jd_to_pyear(guess) 97 | guess = equinox + TROPICALYEAR + 2 98 | 99 | if month <= 7: 100 | m = (month - 1) * 31 101 | else: 102 | m = ((month - 1) * 30) + 6 103 | 104 | return equinox + m + day + 0.5 105 | 106 | 107 | def from_jd(jd): 108 | '''Calculate Persian date from Julian day''' 109 | jd = floor(jd) + 0.5 110 | equinox = last_equinox_jd(jd) 111 | year = round((equinox - EPOCH) / TROPICALYEAR) + 1 112 | yday = jd - (equinox + 0.5) 113 | 114 | if yday <= 186: 115 | month = ceil(yday / 31) 116 | day = yday - ((month - 1) * 31) 117 | else: 118 | month = ceil((yday - 6) / 30) 119 | day = yday - ((month - 1) * 30) - 6 120 | 121 | return int(year), int(month), int(day) 122 | 123 | 124 | def from_gregorian(year, month, day): 125 | return from_jd(gregorian.to_jd(year, month, day)) 126 | 127 | 128 | def to_gregorian(year, month, day): 129 | return gregorian.from_jd(to_jd(year, month, day)) 130 | 131 | 132 | def month_length(year, month): 133 | if month in HAS_30_DAYS or (month == 12 and leap(year)): 134 | return 30 135 | if month in HAS_31_DAYS: 136 | return 31 137 | 138 | return 29 139 | 140 | 141 | def monthcalendar(year, month): 142 | start_weekday = jwday(to_jd(year, month, 1)) 143 | monthlen = month_length(year, month) 144 | return monthcalendarhelper(start_weekday, monthlen) 145 | 146 | 147 | def format(year, month, day): 148 | """Convert a Persian date into a string with the format DD MONTH YYYY.""" 149 | # pylint: disable=redefined-builtin 150 | return "{0:d} {1:} {2:d}".format(day, MONTHS[month - 1], year) 151 | -------------------------------------------------------------------------------- /src/convertdate/positivist.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | ''' 3 | Convert between Gregorian/Julian Day and Comte's Positivist calendar. 4 | The Positivist calendar has 13 months and one or two festival days. 5 | Festival days are given as the fourteenth month. 6 | The Gregorian date 1789-01-01 is Positivist 0001-01-01. 7 | ''' 8 | # This file is part of convertdate. 9 | # http://github.com/fitnr/convertdate 10 | # Licensed under the MIT license: 11 | # http://opensource.org/licenses/MIT 12 | # Copyright (c) 2016, fitnr 13 | from calendar import isleap 14 | from math import floor 15 | 16 | from . import gregorian 17 | from .data import positivist as data 18 | 19 | # Positivist calendar has 13 28-day months and one festival day 20 | 21 | EPOCH = 2374479.5 22 | 23 | YEAR_EPOCH = 1789 24 | 25 | DAYS_IN_YEAR = 365 26 | 27 | MONTHS = ( 28 | 'Moses', 29 | 'Homer', 30 | 'Aristotle', 31 | 'Archimedes', 32 | 'Caesar', 33 | 'Saint Paul', 34 | 'Charlemagne', 35 | 'Dante', 36 | 'Gutenberg', 37 | 'Shakespeare', 38 | 'Descartes', 39 | 'Frederic', 40 | 'Bichat', 41 | '', 42 | ) 43 | 44 | 45 | def legal_date(year, month, day): 46 | '''Checks if a given date is a legal positivist date''' 47 | try: 48 | assert year >= 1 49 | assert 0 < month <= 14 50 | assert 0 < day <= 28 51 | if month == 14: 52 | if isleap(year + YEAR_EPOCH - 1): 53 | assert day <= 2 54 | else: 55 | assert day == 1 56 | 57 | except AssertionError as err: 58 | raise ValueError("Invalid Positivist date: ({}, {}, {})".format(year, month, day)) from err 59 | 60 | return True 61 | 62 | 63 | def to_jd(year, month, day): 64 | '''Convert a Positivist date to Julian day count.''' 65 | legal_date(year, month, day) 66 | gyear = year + YEAR_EPOCH - 1 67 | 68 | return ( 69 | gregorian.EPOCH 70 | - 1 71 | + (365 * (gyear - 1)) 72 | + floor((gyear - 1) / 4) 73 | + (-floor((gyear - 1) / 100)) 74 | + floor((gyear - 1) / 400) 75 | + (month - 1) * 28 76 | + day 77 | ) 78 | 79 | 80 | def from_jd(jd): 81 | '''Convert a Julian day count to Positivist date.''' 82 | try: 83 | assert jd >= EPOCH 84 | except AssertionError as err: 85 | raise ValueError('Invalid Julian day') from err 86 | 87 | depoch = floor(jd - 0.5) + 0.5 - gregorian.EPOCH 88 | 89 | quadricent = floor(depoch / gregorian.INTERCALATION_CYCLE_DAYS) 90 | dqc = depoch % gregorian.INTERCALATION_CYCLE_DAYS 91 | 92 | cent = floor(dqc / gregorian.LEAP_SUPPRESSION_DAYS) 93 | dcent = dqc % gregorian.LEAP_SUPPRESSION_DAYS 94 | 95 | quad = floor(dcent / gregorian.LEAP_CYCLE_DAYS) 96 | dquad = dcent % gregorian.LEAP_CYCLE_DAYS 97 | 98 | yindex = floor(dquad / gregorian.YEAR_DAYS) 99 | year = ( 100 | quadricent * gregorian.INTERCALATION_CYCLE_YEARS 101 | + cent * gregorian.LEAP_SUPPRESSION_YEARS 102 | + quad * gregorian.LEAP_CYCLE_YEARS 103 | + yindex 104 | ) 105 | 106 | if yindex == 4: 107 | yearday = 365 108 | year = year - 1 109 | 110 | else: 111 | yearday = ( 112 | depoch 113 | - quadricent * gregorian.INTERCALATION_CYCLE_DAYS 114 | - cent * gregorian.LEAP_SUPPRESSION_DAYS 115 | - quad * gregorian.LEAP_CYCLE_DAYS 116 | - yindex * gregorian.YEAR_DAYS 117 | ) 118 | 119 | month = floor(yearday / 28) 120 | 121 | return (year - YEAR_EPOCH + 2, month + 1, int(yearday - (month * 28)) + 1) 122 | 123 | 124 | def from_gregorian(year, month, day): 125 | return from_jd(gregorian.to_jd(year, month, day)) 126 | 127 | 128 | def to_gregorian(year, month, day): 129 | return gregorian.from_jd(to_jd(year, month, day)) 130 | 131 | 132 | def dayname(year, month, day): 133 | """ 134 | Give the name of the month and day for a given date. 135 | 136 | Returns: 137 | tuple month_name, day_name 138 | """ 139 | legal_date(year, month, day) 140 | 141 | yearday = (month - 1) * 28 + day 142 | 143 | if isleap(year + YEAR_EPOCH - 1): 144 | dname = data.DAY_NAMES_LEAP[yearday - 1] 145 | else: 146 | dname = data.DAY_NAMES[yearday - 1] 147 | 148 | return MONTHS[month - 1], dname 149 | 150 | 151 | def weekday(day): 152 | """ 153 | Gives the weekday (0=Monday) of a positivist month and day. 154 | Note that the festival month does not have a day. 155 | """ 156 | return (day % 7) - 1 157 | 158 | 159 | def festival(month, day): 160 | """ 161 | Gives the festival day for a month and day. 162 | Returns None if inapplicable. 163 | """ 164 | return data.FESTIVALS.get((month, day)) 165 | -------------------------------------------------------------------------------- /src/convertdate/gregorian.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # This file is part of convertdate. 3 | # http://github.com/fitnr/convertdate 4 | # Licensed under the MIT license: 5 | # http://opensource.org/licenses/MIT 6 | # Copyright (c) 2016, fitnr 7 | """ 8 | The Gregorian calendar was introduced by Pope Gregory XII in October 1582. It reforms 9 | the Julian calendar by adjusting leap year rules to reduce the drift versus solar 10 | year. 11 | 12 | The Gregorian calendar, like the Julian, does not include a year 0. However, for dates before 1, 13 | this module uses the astronomical convention of including a year 0 to simplify 14 | mathematical comparisons across epochs. To present a date in the standard convention, 15 | use the :meth:`gregorian.format` function. 16 | """ 17 | from calendar import isleap, monthrange 18 | from datetime import date 19 | from math import floor 20 | 21 | from .utils import jwday, monthcalendarhelper 22 | 23 | EPOCH = 1721425.5 24 | 25 | INTERCALATION_CYCLE_YEARS = 400 26 | INTERCALATION_CYCLE_DAYS = 146097 27 | 28 | LEAP_SUPPRESSION_YEARS = 100 29 | LEAP_SUPPRESSION_DAYS = 36524 30 | 31 | LEAP_CYCLE_YEARS = 4 32 | LEAP_CYCLE_DAYS = 1461 33 | 34 | YEAR_DAYS = 365 35 | 36 | HAVE_30_DAYS = (4, 6, 9, 11) 37 | HAVE_31_DAYS = (1, 3, 5, 7, 8, 10, 12) 38 | 39 | 40 | def legal_date(year, month, day): 41 | '''Check if this is a legal date in the Gregorian calendar''' 42 | if month == 2: 43 | daysinmonth = 29 if isleap(year) else 28 44 | else: 45 | daysinmonth = 30 if month in HAVE_30_DAYS else 31 46 | 47 | if not 0 < day <= daysinmonth: 48 | raise ValueError("Month {} doesn't have a day {}".format(month, day)) 49 | 50 | return True 51 | 52 | 53 | def to_jd2(year, month, day): 54 | '''Gregorian to Julian Day Count for years between 1801-2099''' 55 | # http://quasar.as.utexas.edu/BillInfo/JulianDatesG.html 56 | legal_date(year, month, day) 57 | 58 | if month <= 2: 59 | year = year - 1 60 | month = month + 12 61 | 62 | a = floor(year / 100) 63 | b = floor(a / 4) 64 | c = 2 - a + b 65 | e = floor(365.25 * (year + 4716)) 66 | f = floor(30.6001 * (month + 1)) 67 | return c + day + e + f - 1524.5 68 | 69 | 70 | def to_jd(year, month, day): 71 | '''Convert gregorian date to julian day count.''' 72 | legal_date(year, month, day) 73 | 74 | if month <= 2: 75 | leap_adj = 0 76 | elif isleap(year): 77 | leap_adj = -1 78 | else: 79 | leap_adj = -2 80 | 81 | return ( 82 | EPOCH 83 | - 1 84 | + (YEAR_DAYS * (year - 1)) 85 | + floor((year - 1) / LEAP_CYCLE_YEARS) 86 | + (-floor((year - 1) / LEAP_SUPPRESSION_YEARS)) 87 | + floor((year - 1) / INTERCALATION_CYCLE_YEARS) 88 | + floor((((367 * month) - 362) / 12) + leap_adj + day) 89 | ) 90 | 91 | 92 | def from_jd(jd): 93 | '''Return Gregorian date in a (Y, M, D) tuple''' 94 | wjd = floor(jd - 0.5) + 0.5 95 | depoch = wjd - EPOCH 96 | 97 | quadricent = floor(depoch / INTERCALATION_CYCLE_DAYS) 98 | dqc = depoch % INTERCALATION_CYCLE_DAYS 99 | 100 | cent = floor(dqc / LEAP_SUPPRESSION_DAYS) 101 | dcent = dqc % LEAP_SUPPRESSION_DAYS 102 | 103 | quad = floor(dcent / LEAP_CYCLE_DAYS) 104 | dquad = dcent % LEAP_CYCLE_DAYS 105 | 106 | yindex = floor(dquad / YEAR_DAYS) 107 | year = quadricent * INTERCALATION_CYCLE_YEARS + cent * LEAP_SUPPRESSION_YEARS + quad * LEAP_CYCLE_YEARS + yindex 108 | 109 | if not (cent == 4 or yindex == 4): 110 | year += 1 111 | 112 | yearday = wjd - to_jd(year, 1, 1) 113 | 114 | leap = isleap(year) 115 | 116 | if yearday < 58 + leap: 117 | leap_adj = 0 118 | elif leap: 119 | leap_adj = 1 120 | else: 121 | leap_adj = 2 122 | 123 | month = floor((((yearday + leap_adj) * 12) + 373) / 367) 124 | day = int(wjd - to_jd(year, month, 1)) + 1 125 | 126 | return (year, month, day) 127 | 128 | 129 | def month_length(year, month): 130 | '''Calculate the length of a month in the Gregorian calendar''' 131 | return monthrange(year, month)[1] 132 | 133 | 134 | def monthcalendar(year, month): 135 | ''' 136 | Return a list of lists that describe the calender for one month. Each inner 137 | list have 7 items, one for each weekday, starting with Sunday. These items 138 | are either ``None`` or an integer, counting from 1 to the number of days in 139 | the month. 140 | For Gregorian, this is very similiar to the built-in :meth:``calendar.monthcalendar``. 141 | ''' 142 | start_weekday = jwday(to_jd(year, month, 1)) 143 | monthlen = month_length(year, month) 144 | 145 | return monthcalendarhelper(start_weekday, monthlen) 146 | 147 | 148 | def format(year, month, day, format_string="%-d %B %y"): 149 | # pylint: disable=redefined-builtin 150 | epoch = '' 151 | if year <= 0: 152 | year = (year - 1) * -1 153 | epoch = ' BCE' 154 | d = date(year, month, day) 155 | return d.strftime(format_string) + epoch 156 | -------------------------------------------------------------------------------- /src/convertdate/bahai.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # This file is part of convertdate. 3 | # http://github.com/fitnr/convertdate 4 | # Licensed under the MIT license: 5 | # http://opensource.org/licenses/MIT 6 | # Copyright (c) 2016, fitnr 7 | 8 | """ 9 | The Bahá'í (Badí) calendar is a solar calendar with 19 months of 19 days. 10 | 11 | Every four years, an intercalary period, Ayyam-i-Há, occurs between the 18th and 19th 12 | months. Dates in this period are returned as month 19, and the month of ‘Alá is always 13 | reported as month 20. 14 | 15 | .. code-block:: python 16 | 17 | from convertdate import bahai 18 | # the first day of Ayyam-i-Ha: 19 | bahai.to_gregorian(175, 19, 1) 20 | # (2019, 2, 26) 21 | # The first day of 'Ala: 22 | bahai.to_gregorian(175, 20, 1) 23 | # (2019, 3, 2) 24 | 25 | """ 26 | from calendar import isleap 27 | from math import ceil, trunc 28 | 29 | from pymeeus.Angle import Angle 30 | from pymeeus.Epoch import Epoch 31 | from pymeeus.Sun import Sun 32 | 33 | from . import gregorian 34 | from .utils import jwday, monthcalendarhelper 35 | 36 | EPOCH = 2394646.5 37 | EPOCH_GREGORIAN_YEAR = 1844 38 | 39 | TEHRAN = 51.4215, 35.6944 40 | 41 | WEEKDAYS = ("Jamál", "Kamál", "Fidál", "Idál", "Istijlál", "Istiqlál", "Jalál") 42 | 43 | MONTHS = ( 44 | "Bahá", 45 | "Jalál", 46 | "Jamál", 47 | "‘Aẓamat", 48 | "Núr", 49 | "Raḥmat", 50 | "Kalimát", 51 | "Kamál", 52 | "Asmá’", 53 | "‘Izzat", 54 | "Mashíyyat", 55 | "‘Ilm", 56 | "Qudrat", 57 | "Qawl", 58 | "Masá’il", 59 | "Sharaf", 60 | "Sulṭán", 61 | "Mulk", 62 | "Ayyám-i-Há", 63 | "‘Alá", 64 | ) 65 | 66 | ENGLISH_MONTHS = ( 67 | "Splendor", 68 | "Glory", 69 | "Beauty", 70 | "Grandeur", 71 | "Light", 72 | "Mercy", 73 | "Words", 74 | "Perfection", 75 | "Names", 76 | "Might", 77 | "Will", 78 | "Knowledge", 79 | "Power", 80 | "Speech", 81 | "Questions", 82 | "Honour", 83 | "Sovereignty", 84 | "Dominion", 85 | "Days of Há", 86 | "Loftiness", 87 | ) 88 | 89 | BAHA = 1 90 | JALAL = 2 91 | JAMAL = 3 92 | AZAMAT = 4 93 | NUR = 5 94 | RAHMAT = 6 95 | KALIMAT = 7 96 | KAMAL = 8 97 | ASMA = 9 98 | IZZAT = 10 99 | MASHIYYAT = 11 100 | ILM = 12 101 | QUDRAT = 13 102 | QAWL = 14 103 | MASAIL = 15 104 | SHARAF = 16 105 | SULTAN = 17 106 | MULK = 18 107 | AYYAMIHA = 19 108 | ALA = 20 109 | 110 | 111 | def gregorian_nawruz(year): 112 | """ 113 | Return Nawruz in the Gregorian calendar. 114 | Returns a tuple (month, day), where month is always 3 115 | """ 116 | if year == 2059: 117 | return 3, 20 118 | 119 | # Timestamp of spring equinox. 120 | equinox = Sun.get_equinox_solstice(year, "spring") 121 | 122 | # Get times of sunsets in Tehran near vernal equinox. 123 | x, y = Angle(TEHRAN[0]), Angle(TEHRAN[1]) 124 | days = trunc(equinox.get_date()[2]), ceil(equinox.get_date()[2]) 125 | 126 | for day in days: 127 | sunset = Epoch(year, 3, day).rise_set(y, x)[1] 128 | if sunset > equinox: 129 | return 3, day 130 | 131 | raise ValueError("Couldn't find date of Nawruz.") 132 | 133 | 134 | def to_jd(year, month, day): 135 | '''Determine Julian day from Bahai date''' 136 | if month <= 18: 137 | gy = year - 1 + EPOCH_GREGORIAN_YEAR 138 | n_month, n_day = gregorian_nawruz(gy) 139 | return gregorian.to_jd(gy, n_month, n_day - 1) + day + (month - 1) * 19 140 | if month == 19: 141 | # Count Ayyám-i-Há from the last day of Mulk 142 | return to_jd(year, month - 1, 19) + day 143 | # For the month of ‘Alá we will count _backwards_ from the next Naw Rúz 144 | gy = year + EPOCH_GREGORIAN_YEAR 145 | n_month, n_day = gregorian_nawruz(gy) 146 | return gregorian.to_jd(gy, n_month, n_day) - 20 + day 147 | 148 | 149 | def from_jd(jd): 150 | '''Calculate Bahai date from Julian day''' 151 | jd = trunc(jd) + 0.5 152 | g = gregorian.from_jd(jd) 153 | gy = g[0] 154 | n_month, n_day = gregorian_nawruz(gy) 155 | 156 | bstarty = EPOCH_GREGORIAN_YEAR 157 | 158 | if jd <= gregorian.to_jd(gy, n_month, 20): 159 | x = 1 160 | else: 161 | x = 0 162 | # verify this next line... 163 | bys = gy - (bstarty + (((gregorian.to_jd(gy, 1, 1) <= jd) and x))) 164 | 165 | year = bys + 1 166 | days = jd - to_jd(year, 1, 1) 167 | bld = to_jd(year, n_day - 1, 1) 168 | 169 | if jd >= bld: 170 | month = 20 171 | else: 172 | month = trunc(days / 19) + 1 173 | day = int((jd + 1) - to_jd(year, month, 1)) 174 | 175 | return year, month, day 176 | 177 | 178 | def from_gregorian(year, month, day): 179 | return from_jd(gregorian.to_jd(year, month, day)) 180 | 181 | 182 | def to_gregorian(year, month, day): 183 | return gregorian.from_jd(to_jd(year, month, day)) 184 | 185 | 186 | def month_length(year, month): 187 | gy = year + EPOCH_GREGORIAN_YEAR - 1 188 | 189 | if month == 19: 190 | _, nawruz_future = gregorian_nawruz(gy + 1) 191 | _, nawruz_past = gregorian_nawruz(gy) 192 | length_of_year = nawruz_future + 365 - nawruz_past 193 | 194 | if isleap(gy + 1): 195 | length_of_year = length_of_year + 1 196 | 197 | return length_of_year - 19 * 19 198 | 199 | return 19 200 | 201 | 202 | def monthcalendar(year, month): 203 | start_weekday = jwday(to_jd(year, month, 1)) 204 | monthlen = month_length(year, month) 205 | return monthcalendarhelper(start_weekday, monthlen) 206 | 207 | 208 | def format(year, month, day, lang=None): 209 | """Convert a Baha'i date into a string with the format DD MONTH YYYY.""" 210 | # pylint: disable=redefined-builtin 211 | lang = lang or "en" 212 | if lang[0:2] == 'ar' or lang[0:2] == 'fa': 213 | month_name = MONTHS[month - 1] 214 | else: 215 | month_name = ENGLISH_MONTHS[month - 1] 216 | 217 | return "{0:d} {1:} {2:d}".format(day, month_name, year) 218 | -------------------------------------------------------------------------------- /src/convertdate/hebrew.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # This file is part of convertdate. 3 | # http://github.com/fitnr/convertdate 4 | # Licensed under the MIT license: 5 | # http://opensource.org/licenses/MIT 6 | # Copyright (c) 2016, fitnr 7 | from math import floor 8 | 9 | from . import gregorian 10 | from .utils import jwday, monthcalendarhelper 11 | 12 | EPOCH = 347995.5 13 | HEBREW_YEAR_OFFSET = 3760 14 | 15 | # Hebrew months 16 | NISAN = 1 17 | IYYAR = 2 18 | SIVAN = 3 19 | TAMMUZ = 4 20 | AV = 5 21 | ELUL = 6 22 | TISHRI = 7 23 | HESHVAN = 8 24 | KISLEV = 9 25 | TEVETH = 10 26 | SHEVAT = 11 27 | ADAR = 12 28 | VEADAR = 13 29 | 30 | MONTHS = [ 31 | 'Nisan', 32 | 'Iyyar', 33 | 'Sivan', 34 | 'Tammuz', 35 | 'Av', 36 | 'Elul', 37 | 'Tishri', 38 | 'Heshvan', 39 | 'Kislev', 40 | 'Teveth', 41 | 'Shevat', 42 | 'Adar', 43 | 'Adar Bet', 44 | ] 45 | 46 | MONTHS_HEB = [ 47 | 'ניסן', 48 | 'אייר', 49 | 'סיוון', 50 | 'תמוז', 51 | 'אב', 52 | 'אלול', 53 | 'תשרי', 54 | 'חשוון', 55 | 'כסלו', 56 | 'טבת', 57 | 'שבט', 58 | 'אדר', 59 | 'אדר ב' 60 | ] 61 | 62 | 63 | def leap(year): 64 | # Is a given Hebrew year a leap year ? 65 | return (((year * 7) + 1) % 19) < 7 66 | 67 | 68 | def year_months(year): 69 | '''How many months are there in a Hebrew year (12 = normal, 13 = leap)''' 70 | if leap(year): 71 | return VEADAR 72 | 73 | return ADAR 74 | 75 | 76 | def delay_1(year): 77 | '''Test for delay of start of the ecclesiastical new year to 78 | avoid improper weekdays for holidays.''' 79 | # Sunday, Wednesday, and Friday as start of the new year. 80 | months = floor(((235 * year) - 234) / 19) 81 | parts = 12084 + (13753 * months) 82 | day = (months * 29) + floor(parts / 25920) 83 | 84 | if ((3 * (day + 1)) % 7) < 3: 85 | day += 1 86 | 87 | return day 88 | 89 | 90 | def delay_2(year): 91 | '''Check for delay in start of the ecclesiastical new year due to length 92 | of adjacent years''' 93 | last = delay_1(year - 1) 94 | present = delay_1(year) 95 | next_ = delay_1(year + 1) 96 | 97 | if next_ - present == 356: 98 | return 2 99 | 100 | if present - last == 382: 101 | return 1 102 | 103 | return 0 104 | 105 | 106 | def year_days(year): 107 | '''How many days are in a Hebrew year ?''' 108 | return to_jd(year + 1, 7, 1) - to_jd(year, 7, 1) 109 | 110 | 111 | def month_days(year, month): 112 | '''How many days are in a given month of a given year''' 113 | if month > VEADAR: 114 | raise ValueError("Incorrect month index") 115 | 116 | # First of all, dispose of fixed-length 29 day months 117 | if month in (IYYAR, TAMMUZ, ELUL, TEVETH, VEADAR): 118 | return 29 119 | 120 | # If it's not a leap year, Adar has 29 days 121 | if month == ADAR and not leap(year): 122 | return 29 123 | 124 | # If it's Heshvan, days depend on length of year 125 | if month == HESHVAN and (year_days(year) % 10) != 5: 126 | return 29 127 | 128 | # Similarly, Kislev varies with the length of year 129 | if month == KISLEV and (year_days(year) % 10) == 3: 130 | return 29 131 | 132 | # Nope, it's a 30 day month 133 | return 30 134 | 135 | 136 | def to_jd(year, month, day): 137 | months = year_months(year) 138 | jd = EPOCH + delay_1(year) + delay_2(year) + day + 1 139 | 140 | if month < TISHRI: 141 | for m in range(TISHRI, months + 1): 142 | jd += month_days(year, m) 143 | 144 | for m in range(NISAN, month): 145 | jd += month_days(year, m) 146 | else: 147 | for m in range(TISHRI, month): 148 | jd += month_days(year, m) 149 | 150 | return int(jd) + 0.5 151 | 152 | 153 | def from_jd(jd): 154 | jd = floor(jd) + 0.5 155 | count = floor(((jd - EPOCH) * 98496.0) / 35975351.0) 156 | year = count - 1 157 | i = count 158 | while jd >= to_jd(i, TISHRI, 1): 159 | i += 1 160 | year += 1 161 | 162 | if jd < to_jd(year, NISAN, 1): 163 | first = 7 164 | else: 165 | first = 1 166 | 167 | month = i = first 168 | while jd > to_jd(year, i, month_days(year, i)): 169 | i += 1 170 | month += 1 171 | 172 | day = int(jd - to_jd(year, month, 1)) + 1 173 | return (year, month, day) 174 | 175 | 176 | def to_civil(year, month, day): 177 | """Convert a date in the ecclestical calendar (year starts in Nisan) to 178 | the civil calendar (year starts in Tishrei).""" 179 | if month >= TISHRI: 180 | year = year + 1 181 | return year, month, day 182 | 183 | 184 | def to_jd_gregorianyear(gregorianyear, hebrew_month, hebrew_day): 185 | '''Returns the Gregorian date when a given Hebrew month and year within a given Gregorian year.''' 186 | # gregorian year is either 3760 or 3761 years less than hebrew year 187 | # we'll first try 3760 if conversion to gregorian isn't the same 188 | # year that was passed to this method, then it must be 3761. 189 | for y in (gregorianyear + HEBREW_YEAR_OFFSET, gregorianyear + HEBREW_YEAR_OFFSET + 1): 190 | jd = to_jd(y, hebrew_month, hebrew_day) 191 | gd = gregorian.from_jd(jd) 192 | if gd[0] == gregorianyear: 193 | break 194 | 195 | jd = None 196 | 197 | if not jd: # should never occur, but just incase... 198 | raise ValueError("Could not determine gregorian year") 199 | 200 | return gregorian.to_jd(gd[0], gd[1], gd[2]) 201 | 202 | 203 | def from_gregorian(year, month, day): 204 | return from_jd(gregorian.to_jd(year, month, day)) 205 | 206 | 207 | def to_gregorian(year, month, day): 208 | return gregorian.from_jd(to_jd(year, month, day)) 209 | 210 | 211 | def monthcalendar(year, month): 212 | start_weekday = jwday(to_jd(year, month, 1)) 213 | monthlen = month_days(year, month) 214 | return monthcalendarhelper(start_weekday, monthlen) 215 | 216 | 217 | def format(year, month, day, lang=None): 218 | """Convert a Hebrew date into a string with the format DD MONTH YYYY.""" 219 | # pylint: disable=redefined-builtin 220 | lang = lang or "en" 221 | if lang[0:2] == "he": 222 | month_name = MONTHS_HEB[month - 1] 223 | else: 224 | month_name = MONTHS[month - 1] 225 | return "{0} {1} {2}".format(day, month_name, year) 226 | -------------------------------------------------------------------------------- /src/convertdate/armenian.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # This file is part of convertdate. 3 | # http://github.com/fitnr/convertdate 4 | # Licensed under the MIT license: 5 | # http://opensource.org/licenses/MIT 6 | # Copyright (c) 2016, fitnr 7 | 8 | """ 9 | The Armenian calendar begins on 11 July 552 (Julian) and has two modes of 10 | reckoning. The first is the invariant-length version consisting of 12 months 11 | of 30 days each and five epagomenal days; the second is the version 12 | established by Yovhannes Sarkawag in 1084, which fixed the first day of the 13 | year with respect to the Julian calendar and added a sixth epagomenal day 14 | every four years. 15 | 16 | By default the invariant calendar is used, but the Sarkawag calendar can be 17 | used beginning with the Armenian year 533 (11 August 1084) by passing the 18 | parameter ``method='sarkawag'`` to the relevant functions. 19 | """ 20 | 21 | from math import trunc 22 | 23 | from . import gregorian, julian 24 | from .utils import jwday, monthcalendarhelper 25 | 26 | EPOCH = 1922501.5 # beginning of proleptic year 0, day 0 of the moveable calendar 27 | EPOCH_SARKAWAG = 2117210.5 # last day of Sarkawag's first cycle 28 | MONTHS = [ 29 | "nawasard", 30 | "hoṙi", 31 | "sahmi", 32 | "trē", 33 | "kʿałocʿ", 34 | "aracʿ", 35 | "mehekan", 36 | "areg", 37 | "ahekan", 38 | "mareri", 39 | "margacʿ", 40 | "hroticʿ", 41 | "aweleacʿ", 42 | ] 43 | MONTHS_ARM = [ 44 | "նաւասարդ", 45 | "հոռի", 46 | "սահմի", 47 | "տրէ", 48 | "քաղոց", 49 | "արաց", 50 | "մեհեկան", 51 | "արեգ", 52 | "ահեկան", 53 | "մարերի", 54 | "մարգաց", 55 | "հրոտից", 56 | "աւելեաց", 57 | ] 58 | 59 | 60 | def _valid_date(year, month, day, method=None): 61 | try: 62 | assert 1 <= month <= 13, "Month out of range" 63 | except AssertionError as error: 64 | raise ValueError from error 65 | 66 | day_max = 30 67 | method = method or "moveable" 68 | 69 | if method.lower() == "sarkawag": 70 | year_min = 533 71 | if month == 13: 72 | day_max = 6 if leap(year) else 5 73 | 74 | else: 75 | year_min = 1 76 | if month == 13: 77 | day_max = 5 78 | 79 | try: 80 | assert year >= year_min, "Year out of range for Armenian calendar ({} method)".format(method) 81 | except AssertionError as err: 82 | raise ValueError from err 83 | 84 | try: 85 | assert 1 <= day <= day_max, "Day out of range" 86 | except AssertionError as err: 87 | raise ValueError from err 88 | 89 | return True 90 | 91 | 92 | def leap(year): 93 | """Return true if the year was a leap year under the system of Sarkawag""" 94 | if year < 533: 95 | return False 96 | return year % 4 == 0 97 | 98 | 99 | def to_jd(year, month, day, method=None): 100 | """Convert Armenian date to Julian day count. Use the method of Sarkawag if requested.""" 101 | _valid_date(year, month, day, method) 102 | yeardays = (month - 1) * 30 + day 103 | if method == "sarkawag": 104 | yeardelta = year - 533 105 | leapdays = trunc(yeardelta / 4) 106 | return EPOCH_SARKAWAG + (365 * yeardelta) + leapdays + yeardays 107 | 108 | return EPOCH + (365 * year) + yeardays 109 | 110 | 111 | def from_jd(jd, method=None): 112 | """Convert a Julian day count to an Armenian date. Use the method of Sarkawag if requested.""" 113 | if method == "sarkawag": 114 | dc = jd - EPOCH_SARKAWAG 115 | if dc < 0: 116 | raise ValueError("Day count out of range for method") 117 | years = trunc(dc / 365.25) 118 | yeardays = dc - (365 * years + trunc(years / 4)) 119 | if yeardays == 0: 120 | yeardays = 366 if years % 4 == 0 else 365 121 | years -= 1 122 | months = trunc((yeardays - 1) / 30) 123 | days = yeardays - (30 * months) 124 | return years + 533, months + 1, trunc(days) 125 | 126 | dc = jd - EPOCH 127 | 128 | if dc < 0: 129 | raise ValueError("Day count out of range") 130 | 131 | years = trunc((dc - 1) / 365) 132 | months = trunc(((dc - 1) % 365) / 30) 133 | days = dc - (365 * years) - (30 * months) 134 | 135 | return years, months + 1, trunc(days) 136 | 137 | 138 | def to_julian(year, month, day, method=None): 139 | """Convert an Armenian date to a Julian date""" 140 | return julian.from_jd(to_jd(year, month, day, method)) 141 | 142 | 143 | def from_julian(year, month, day, method=None): 144 | """Convert a Julian date to an Armenian date""" 145 | return from_jd(julian.to_jd(year, month, day), method) 146 | 147 | 148 | def to_gregorian(year, month, day, method=None): 149 | """Convert an Armenian date to a Gregorian date""" 150 | return gregorian.from_jd(to_jd(year, month, day, method)) 151 | 152 | 153 | def from_gregorian(year, month, day, method=None): 154 | """Convert a Gregorian date to an Armenian date""" 155 | return from_jd(gregorian.to_jd(year, month, day), method) 156 | 157 | 158 | def month_length(year, month, method=None): 159 | if month > 13: 160 | raise ValueError("Requested month %d doesn't exist" % month) 161 | 162 | if month == 13: 163 | return 6 if (method == "sarkawag" and leap(year)) else 5 164 | 165 | return 30 166 | 167 | 168 | def monthcalendar(year, month, method=None): 169 | """Returns a matrix representing a month’s calendar. 170 | Each row represents a week; days outside of the month are represented by zeros.""" 171 | start_weekday = jwday(to_jd(year, month, 1, method)) 172 | monthlen = month_length(year, month, method) 173 | return monthcalendarhelper(start_weekday, monthlen) 174 | 175 | 176 | def format(year, month, day, lang=None): 177 | """Convert an Armenian date into a string with the format DD MONTH YYYY.""" 178 | # pylint: disable=redefined-builtin 179 | lang = lang or "en" 180 | if lang[0:2] == 'hy' or lang[0:2] == 'am' or lang == 'arm': 181 | month_name = MONTHS_ARM[month - 1] 182 | else: 183 | month = month_name = MONTHS[month - 1] 184 | 185 | return "{0:d} {1:} {2:d}".format(day, month_name, year) 186 | 187 | 188 | def tostring(year, month, day, lang=None): 189 | """Kept for backwards compatibility, the format function name will be standard across the library""" 190 | return format(year, month, day, lang) 191 | -------------------------------------------------------------------------------- /tests/test_general.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | import time 3 | from datetime import datetime, timezone 4 | 5 | from convertdate import coptic, dublin, gregorian, hebrew, islamic, iso, julian, julianday, ordinal, persian, utils 6 | 7 | from . import CalTestCase 8 | 9 | 10 | class TestConvertdate(CalTestCase): 11 | def setUp(self): 12 | self.tm = time.localtime() 13 | self.gregoriandate = (self.tm[0], self.tm[1], self.tm[2]) 14 | 15 | self.jd = gregorian.to_jd(self.gregoriandate[0], self.gregoriandate[1], self.gregoriandate[2]) 16 | 17 | self.c_greg = (1492, 10, 21) 18 | self.c = gregorian.to_jd(*self.c_greg) 19 | self.x = gregorian.to_jd(2016, 2, 29) 20 | 21 | self.jdcs = range(2159677, 2488395, 2000) 22 | 23 | def test_julian_legal_date(self): 24 | try: 25 | julian.to_jd(1900, 2, 29) 26 | except ValueError: 27 | self.fail('Unexpected ValueError: "julian.to_jd(1900, 2, 29)"') 28 | 29 | self.assertRaises(ValueError, julian.to_jd, 2014, 2, 29) 30 | self.assertRaises(ValueError, julian.to_jd, 2014, 3, 32) 31 | self.assertRaises(ValueError, julian.to_jd, 2014, 4, 31) 32 | self.assertRaises(ValueError, julian.to_jd, 2014, 5, -1) 33 | 34 | def test_hebrew(self): 35 | self.assertEqual(self.jd, hebrew.to_jd(*hebrew.from_jd(self.jd))) 36 | self.reflexive(hebrew) 37 | 38 | # Anno Mundi 39 | am = hebrew.to_jd(1, hebrew.TISHRI, 1) 40 | self.assertEqual(julian.from_jd(am), (-3760, 10, 7)) 41 | 42 | def test_islamic(self): 43 | self.assertEqual(self.jd, islamic.to_jd(*islamic.from_jd(self.jd))) 44 | self.reflexive(islamic) 45 | 46 | new_years = [ 47 | (2012, 11, 15), 48 | (2015, 10, 15), 49 | (2019, 9, 1), 50 | (2020, 8, 20), 51 | (2021, 8, 10), 52 | (2022, 7, 30), 53 | ] 54 | 55 | for date in new_years: 56 | jd = islamic.to_jd_gregorianyear(date[0], 1, 1) 57 | with self.subTest(date[0]): 58 | self.assertEqual(date, gregorian.from_jd(jd)) 59 | 60 | self.assertEqual(islamic.from_jd(1948085.5), (0, 1, 1)) 61 | self.assertEqual(islamic.from_jd(1948084.5), (-1, 12, 30)) 62 | self.assertEqual(islamic.from_jd(1912648.5), (-100, 1, 1)) 63 | self.assertEqual(islamic.to_jd(-100, 1, 1), 1912648.5) 64 | 65 | def test_iso(self): 66 | self.assertEqual(iso.from_gregorian(2005, 1, 1), (2004, 53, 6)) 67 | self.assertEqual(iso.to_gregorian(2004, 53, 6), (2005, 1, 1)) 68 | 69 | self.assertEqual(self.jd, iso.to_jd(*iso.from_jd(self.jd))) 70 | self.reflexive(iso) 71 | 72 | def test_from_julian(self): 73 | self.assertEqual(self.jd, julian.to_jd(*julian.from_jd(self.jd))) 74 | self.assertEqual(julian.from_jd(self.c), (1492, 10, 12)) 75 | self.assertEqual(julian.from_jd(2400000.5), (1858, 11, 5)) 76 | self.assertEqual(julian.from_jd(2399830.5), (1858, 5, 19)) 77 | 78 | def test_julian_inverse(self): 79 | self.reflexive(julian) 80 | 81 | def test_to_julian(self): 82 | self.assertEqual(julian.to_jd(1858, 11, 5), 2400000.5) 83 | self.assertEqual(julian.to_jd(1492, 10, 12), self.c) 84 | 85 | def test_coptic(self): 86 | self.assertEqual(coptic.to_jd(1000, 1, 1), 2189914.5) 87 | self.assertEqual(coptic.to_jd(1666, 6, 1), 2433320.5) 88 | self.assertEqual(coptic.to_jd(1, 1, 1), 1825029.5) 89 | self.assertEqual(coptic.from_jd(2437970.5), (1679, 2, 23)) 90 | self.assertEqual(coptic.to_jd(1259, 13, 6), 2284878.5) 91 | self.assertEqual(coptic.from_jd(2284878.5), (1259, 13, 6)) 92 | self.reflexive(coptic) 93 | self.assertEqual(coptic.from_gregorian(2017, 1, 7), (1733, 4, 29)) 94 | self.assertEqual(coptic.to_gregorian(1727, 11, 11), (2011, 7, 18)) 95 | 96 | def test_dublin_dc(self): 97 | self.assertEqual(dublin.from_gregorian(1900, 1, 1), 0.5) 98 | self.assertEqual(dublin.to_gregorian(1), (1900, 1, 1)) 99 | self.assertEqual(dublin.to_jd(0), 2415020.0) 100 | 101 | self.assertEqual( 102 | dublin.to_jd(dublin.from_jd(self.c)), 103 | gregorian.to_jd(*dublin.to_gregorian(dublin.from_gregorian(*self.c_greg))), 104 | ) 105 | 106 | self.assertEqual(dublin.to_gregorian(dublin.from_jd(1737936)), gregorian.from_jd(1737936)) 107 | self.assertEqual(dublin.to_julian(dublin.from_jd(1737936)), julian.from_jd(1737936)) 108 | 109 | def test_julian_day(self): 110 | self.assertEqual(julianday.from_gregorian(*self.c_greg), self.c) 111 | self.assertEqual(julianday.to_datetime(self.c), datetime(1492, 10, 21, tzinfo=timezone.utc)) 112 | self.assertEqual(julianday.to_datetime(self.x), datetime(2016, 2, 29, tzinfo=timezone.utc)) 113 | 114 | self.assertEqual(julianday.to_datetime(self.c + 0.25), datetime(1492, 10, 21, 6, tzinfo=timezone.utc)) 115 | self.assertEqual(julianday.to_datetime(self.x + 0.525), datetime(2016, 2, 29, 12, 36, tzinfo=timezone.utc)) 116 | 117 | dt = datetime(2014, 11, 8, 3, 37, tzinfo=timezone.utc) 118 | self.assertEqual(julianday.from_datetime(dt), 2456969.65069) 119 | 120 | self.assertEqual(julianday.to_datetime(self.x + 0.525), datetime(2016, 2, 29, 12, 36, tzinfo=timezone.utc)) 121 | 122 | def test_month_length_julian(self): 123 | self.assertEqual(julian.month_length(1582, 10), 31) 124 | self.assertEqual(julian.month_length(1977, 2), 28) 125 | self.assertEqual(julian.month_length(1900, 2), 29) 126 | self.assertEqual(julian.month_length(1904, 2), 29) 127 | 128 | def test_month_length_islamic(self): 129 | self.assertEqual(islamic.month_length(1436, 1), 30) 130 | self.assertEqual(islamic.month_length(1436, 2), 29) 131 | self.assertEqual(islamic.month_length(1436, 12), 30) 132 | 133 | def test_monthcalendar_julian(self): 134 | self.assertEqual(julian.monthcalendar(1582, 10).pop(0).pop(1), 1) 135 | self.assertEqual(julian.monthcalendar(1582, 10).pop().pop(3), 31) 136 | 137 | def test_monthcalendar_islamic(self): 138 | self.assertEqual(islamic.monthcalendar(1436, 10).pop(0).pop(6), 1) 139 | self.assertEqual(islamic.monthcalendar(1436, 11).pop().pop(1), 30) 140 | 141 | def test_monthcalendar_hebrew(self): 142 | self.assertEqual(hebrew.monthcalendar(5775, 7).pop(0).pop(4), 1) 143 | self.assertEqual(hebrew.monthcalendar(5775, 7).pop().pop(0), 25) 144 | 145 | def test_returntype(self): 146 | self.assertSequenceType(coptic.from_gregorian(2020, 6, 4), int) 147 | self.assertSequenceType(hebrew.from_gregorian(2020, 6, 4), int) 148 | self.assertSequenceType(islamic.from_gregorian(2020, 6, 4), int) 149 | self.assertSequenceType(iso.from_gregorian(2020, 6, 4), int) 150 | self.assertSequenceType(julian.from_gregorian(2020, 6, 4), int) 151 | self.assertSequenceType(persian.from_gregorian(2020, 6, 4), int) 152 | -------------------------------------------------------------------------------- /tests/test_bahai.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | """Test the Bahá’í calendar""" 3 | import time 4 | import unittest 5 | 6 | from convertdate import bahai, gregorian 7 | 8 | from . import CalTestCase 9 | 10 | 11 | class TestBahai(CalTestCase): 12 | 13 | pairs = { 14 | (2041, 11, 27): (198, bahai.QAWL, 6), # ascension of Abdu'l-Bahá 2041 15 | (2043, 11, 28): (200, bahai.QAWL, 6), # ascension of Abdu'l-Bahá 2043 16 | (2038, 3, 1): (194, bahai.ALA, 1), # beginning of fast 2038 17 | (2039, 3, 2): (195, bahai.ALA, 1), # beginning of fast 2039 18 | (2040, 3, 1): (196, bahai.ALA, 1), # beginning of fast 2040 19 | (2041, 3, 1): (197, bahai.ALA, 1), # beginning of fast 2041 20 | (2042, 3, 1): (198, bahai.ALA, 1), # beginning of fast 2042 21 | (2043, 3, 2): (199, bahai.ALA, 1), # beginning of fast 2043 22 | (2031, 10, 17): (188, bahai.ILM, 2), # twin holy days, 2031 23 | (2031, 10, 18): (188, bahai.ILM, 3), 24 | (2051, 11, 5): (208, bahai.QUDRAT, 2), 25 | (2052, 10, 24): (209, bahai.ILM, 10), 26 | (2053, 11, 11): (210, bahai.QUDRAT, 9), 27 | (2054, 11, 1): (211, bahai.ILM, 18), 28 | (2055, 10, 21): (212, bahai.ILM, 6), 29 | (2056, 11, 8): (213, bahai.QUDRAT, 6), 30 | (2057, 10, 29): (214, bahai.ILM, 15), 31 | (2058, 10, 18): (215, bahai.ILM, 4), 32 | (2059, 11, 6): (216, bahai.QUDRAT, 4), 33 | (2060, 10, 25): (217, bahai.ILM, 11), 34 | (2061, 10, 14): (218, bahai.MASHIYYAT, 19), 35 | (2062, 11, 2): (219, bahai.ILM, 19), 36 | (2063, 10, 23): (220, bahai.ILM, 9), 37 | (2064, 11, 10): (221, bahai.QUDRAT, 8), 38 | } 39 | 40 | def setUp(self): 41 | self.tm = time.localtime() 42 | self.gregoriandate = (self.tm[0], self.tm[1], self.tm[2]) 43 | 44 | def test_reflexive(self): 45 | self.reflexive(bahai) 46 | 47 | def test_monthlength(self): 48 | self.assertEqual(bahai.month_length(1, 3), 19) 49 | self.assertEqual(bahai.month_length(1, 1), 19) 50 | 51 | def test_gregorian_nawruz(self): 52 | nawruz_official = { 53 | 20: [ 54 | 2016, 55 | 2017, 56 | 2020, 57 | 2021, 58 | 2024, 59 | 2025, 60 | 2028, 61 | 2029, 62 | 2030, 63 | 2032, 64 | 2033, 65 | 2034, 66 | 2036, 67 | 2037, 68 | 2038, 69 | 2040, 70 | 2041, 71 | 2042, 72 | 2044, 73 | 2045, 74 | 2046, 75 | 2048, 76 | 2049, 77 | 2050, 78 | 2052, 79 | 2053, 80 | 2054, 81 | 2056, 82 | 2057, 83 | 2058, 84 | 2059, 85 | 2060, 86 | 2061, 87 | 2062, 88 | 2063, 89 | 2064, 90 | ], 91 | 21: [ 92 | 2015, 93 | 2018, 94 | 2019, 95 | 2022, 96 | 2023, 97 | 2026, 98 | 2027, 99 | 2031, 100 | 2035, 101 | 2039, 102 | 2043, 103 | 2047, 104 | 2051, 105 | 2055, 106 | ], 107 | } 108 | 109 | for date, gyears in nawruz_official.items(): 110 | for gyear in gyears: 111 | self.assertEqual((3, date), bahai.gregorian_nawruz(gyear)) 112 | 113 | def test_ayyam_i_ha(self): 114 | # source: https://www.bahai.us/events/holy-days/ 115 | # years with four days in Ayyám-i-Há 116 | # bahai_year: gregorian start of Ayyám-i-Há 117 | ayyamiha = [ 118 | {"byear": 208, "gdate": (2052, 2, 26), "days": 4}, 119 | {"byear": 209, "gdate": (2053, 2, 25), "days": 4}, 120 | {"byear": 210, "gdate": (2054, 2, 25), "days": 4}, 121 | {"byear": 211, "gdate": (2055, 2, 25), "days": 5}, 122 | {"byear": 212, "gdate": (2056, 2, 26), "days": 4}, 123 | {"byear": 213, "gdate": (2057, 2, 25), "days": 4}, 124 | {"byear": 214, "gdate": (2058, 2, 25), "days": 4}, 125 | {"byear": 215, "gdate": (2059, 2, 25), "days": 4}, 126 | {"byear": 216, "gdate": (2060, 2, 25), "days": 5}, 127 | {"byear": 217, "gdate": (2061, 2, 25), "days": 4}, 128 | {"byear": 218, "gdate": (2062, 2, 25), "days": 4}, 129 | {"byear": 219, "gdate": (2063, 2, 25), "days": 4}, 130 | {"byear": 220, "gdate": (2064, 2, 25), "days": 5}, 131 | {"byear": 221, "gdate": (2065, 2, 25), "days": 4}, 132 | ] 133 | with self.subTest(): 134 | for case in ayyamiha: 135 | start_jd = gregorian.to_jd(*case["gdate"]) 136 | for i in range(case["days"]): 137 | b = bahai.to_jd(case["byear"], bahai.AYYAMIHA, i + 1) 138 | self.assertEqual(start_jd + i, b, "%s Ayyám-i-Há %s" % (i + 1, case["byear"])) 139 | 140 | def test_reflexive(self): 141 | for jd in range(2159677, 2488395, 1867): 142 | self.assertEqual(jd + 0.5, bahai.to_jd(*bahai.from_jd(jd + 0.5))) 143 | 144 | def test_to_gregorian(self): 145 | for g, b in self.pairs.items(): 146 | self.assertEqual(g, bahai.to_gregorian(*b)) 147 | 148 | def test_from_gregorian(self): 149 | for g, b in self.pairs.items(): 150 | self.assertEqual(b, bahai.from_gregorian(*g)) 151 | 152 | def test_month_length(self): 153 | for x in range(1, 19): 154 | self.assertEqual(bahai.month_length(2019, x), 19) 155 | 156 | def test_month_length_ha(self): 157 | official = { 158 | 4: [ 159 | 2016, 160 | 2017, 161 | 2019, 162 | 2020, 163 | 2021, 164 | 2023, 165 | 2024, 166 | 2025, 167 | 2027, 168 | 2028, 169 | 2029, 170 | 2030, 171 | 2032, 172 | 2033, 173 | 2034, 174 | 2036, 175 | 2037, 176 | 2038, 177 | 2040, 178 | 2041, 179 | 2042, 180 | 2044, 181 | 2045, 182 | 2046, 183 | 2048, 184 | 2049, 185 | 2050, 186 | 2052, 187 | 2053, 188 | 2054, 189 | 2056, 190 | 2057, 191 | 2058, 192 | 2059, 193 | 2061, 194 | 2062, 195 | 2063, 196 | 2065, 197 | ], 198 | 5: [2018, 2022, 2026, 2031, 2035, 2039, 2043, 2047, 2051, 2055, 2060, 2064], 199 | } 200 | 201 | for length, gyears in official.items(): 202 | for gyear in gyears: 203 | byear = gyear - 1844 204 | self.assertEqual(length, bahai.month_length(byear, 19)) 205 | 206 | def test_returntype(self): 207 | self.assertSequenceType(bahai.from_gregorian(2020, 6, 4), int) 208 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | convertdate 2 | =========== 3 | 4 | The convertdate package was originally developed as "[Python Date 5 | Utils](http://sourceforge.net/projects/pythondateutil/)" by Phil 6 | Schwartz. It has been significantly updated and expanded. 7 | 8 | [Consult the complete docs for detailed usage info](https://convertdate.readthedocs.io/). 9 | 10 | Available calendars: 11 | 12 | - Armenian 13 | - Bahai 14 | - Coptic (Alexandrian) 15 | - French Republican 16 | - Gregorian 17 | - Hebrew 18 | - Indian Civil 19 | - Islamic 20 | - Julian 21 | - Mayan 22 | - Persian 23 | - Positivist 24 | - Mayan 25 | - ISO 26 | - Ordinal (day of year) 27 | - Dublin day count 28 | - Julian day count 29 | 30 | The `holidays` module also provides some useful holiday-calculation, 31 | with a focus on North American and Jewish holidays. 32 | 33 | Installing 34 | ---------- 35 | 36 | `pip install convertdate` 37 | 38 | Or download the package and run `python setup.py install`. 39 | 40 | Examples 41 | -------- 42 | 43 | >>> from convertdate import french_republican 44 | >>> from convertdate import hebrew 45 | >>> french_republican.from_gregorian(2014, 10, 31) 46 | (223, 2, 9) 47 | >>> hebrew.from_gregorian(2014, 10, 31) 48 | (5775, 8, 7) 49 | 50 | Note that in some calendar systems, the day begins at sundown. 51 | Convertdate gives the conversion for noon of the day in question. 52 | 53 | Each module includes a `monthcalendar` function, which will generate a 54 | calender-like nested list for a year and month (each list of dates runs 55 | from Sunday to Saturday) 56 | 57 | >>> hebrew.monthcalendar(5775, 8) 58 | [ 59 | [None, None, None, None, None, None, 1], 60 | [2, 3, 4, 5, 6, 7, 8], 61 | [9, 10, 11, 12, 13, 14, 15], 62 | [16, 17, 18, 19, 20, 21, 22], 63 | [23, 24, 25, 26, 27, 28, 29] 64 | ] 65 | 66 | >>> julian.monthcalendar(2015, 1) 67 | [ 68 | [None, None, None, 1, 2, 3, 4], 69 | [5, 6, 7, 8, 9, 10, 11], 70 | [12, 13, 14, 15, 16, 17, 18], 71 | [19, 20, 21, 22, 23, 24, 25], 72 | [26, 27, 28, 29, 30, 31, None] 73 | ] 74 | 75 | Special Options 76 | --------------- 77 | 78 | ### Armenian 79 | 80 | The Armenian calendar begins on 11 July 552 (Julian) and has two modes of 81 | reckoning. The first is the invariant-length version consisting of 12 months 82 | of 30 days each and five epagomenal days; the second is the version 83 | established by Yovhannes Sarkawag in 1084, which fixed the first day of the 84 | year with respect to the Julian calendar and added a sixth epagomenal day 85 | every four years. 86 | 87 | By default the invariant calendar is used, but the Sarkawag calendar can be 88 | used beginning with the Armenian year 533 (11 August 1084) by passing the 89 | parameter `method='sarkawag'` to the relevant functions. 90 | 91 | 92 | ### French Republican 93 | 94 | Leap year calculations in the French Republican calendar are a matter of 95 | dispute. By default, `convertdate` calculates leap years using the 96 | autumnal equinox. You can also use one of three more systematic methods 97 | proposed over the years. 98 | 99 | - Romme, a co-creator of the calendar, proposed leap years in years 100 | divisible by four, except for years divisible by 100. 101 | - Some concordances were drawn up in the 19th century that gave leap 102 | years every 4 years, in years that give a remainder of three when 103 | divided by four (19, 23, 27, etc...). 104 | - Von Mädler proposed leap years in years divisible by four, except 105 | for years divisible by 128. 106 | 107 | You can specify any of these three methods with the method keyword 108 | argument in `french_republican` conversion functions. 109 | 110 | from convertdate import french_republican 111 | 112 | # Romme's method 113 | french_republican.to_gregorian(20, 1, 1), method='romme') 114 | # (1811, 9, 23) 115 | 116 | # continuous method 117 | french_republican.to_gregorian(20, 1, 1), method='continuous') 118 | # (1811, 9, 24) 119 | 120 | # von Madler's method 121 | french_republican.to_gregorian(20, 1, 1), method='madler') 122 | # (1811, 9, 23) 123 | 124 | All the conversion methods correctly assign the leap years implemented 125 | while calendar was in use (3, 7, 11). 126 | 127 | Baha'i 128 | ------ 129 | 130 | The Bahá'í (Badí) calendar has an intercalary period, Ayyam-i-Há, which occurs between the 18th and 19th months. 131 | Dates in this period are returned as month 19, and the month of ‘Alá is reported as month 20. 132 | 133 | ```python 134 | from convertdate import bahai 135 | # the first day of Ayyam-i-Ha: 136 | bahai.to_gregorian(175, 19, 1) 137 | # (2019, 2, 26) 138 | # The first day of 'Ala: 139 | bahai.to_gregorian(175, 20, 1) 140 | # (2019, 3, 2) 141 | ``` 142 | 143 | Before the Common Era 144 | --------------------- 145 | 146 | For dates before the Common Era (year 1), `convertdate` uses 147 | astronomical notation: 1 BC is recorded as 0, 2 BC is -1, etc. This 148 | makes arithmatic much easier at the expense of ignoring custom. 149 | 150 | Note that for dates before 4 CE, `convertdate` uses the [proleptic 151 | Julian 152 | calendar](https://en.wikipedia.org/wiki/Proleptic_Julian_calendar). The 153 | Julian Calendar was in use from 45 BC, but before 4 CE the leap year 154 | leap year pattern was irregular. 155 | 156 | The [proleptic Gregorian 157 | calendar](https://en.wikipedia.org/wiki/Proleptic_Gregorian_calendar) is 158 | used for dates before 1582 CE, the year of the Gregorian calendar 159 | reform. 160 | 161 | Holidays 162 | -------- 163 | 164 | North American holidays are the current focus of the `holidays` module, 165 | but pull requests are welcome. 166 | 167 | from convertdate import holidays 168 | 169 | # For simplicity, functions in the holidays module return a tuple 170 | # In the format (year, month, day) 171 | 172 | holidays.new_years(2014) 173 | # (2014, 1, 1) 174 | 175 | holidays.memorial_day(2014) 176 | # (2014, 5, 26) 177 | 178 | # USA is default 179 | holidays.thanksgiving(2014) 180 | # (2014, 11, 27) 181 | 182 | # But there is a Canadian option for some holidays 183 | holidays.thanksgiving(2014, 'canada') 184 | # (2014, 10, 13) 185 | 186 | # Mexican national holidays 187 | holidays.natalicio_benito_juarez(2016) 188 | # (2016, 3, 21) 189 | 190 | holidays.dia_revolucion(2016) 191 | # (2016, 11, 21) 192 | 193 | # Some Jewish holidays are included 194 | holidays.rosh_hashanah(2014) 195 | 196 | # Easter can be calculated according to different churches 197 | # ('western', 'orthodox', 'eastern') 198 | # The eastern Christian computation differs from the Orthodox one 199 | # 4 times in each 532-year cycle. 200 | 201 | holidays.easter(2019) 202 | # (2019, 4, 21) 203 | holidays.easter(2019, church="orthodox") 204 | # (2019, 4, 28) 205 | holidays.easter(2019, church="orthodox") 206 | # (2019, 4, 28) 207 | 208 | Utils 209 | ----- 210 | 211 | Convertdate includes some utilities for manipulating and calculating 212 | dates. 213 | 214 | from convertdate import utils 215 | 216 | # Calculate an arbitrary day of the week 217 | THUR = 3 218 | APRIL = 4 219 | 220 | # 3rd Thursday in April 221 | utils.nth_day_of_month(3, THUR, APRIL, 2014) 222 | # (2014, 4, 17) 223 | 224 | utils.nth_day_of_month(5, THUR, APRIL, 2014) 225 | # IndexError: No 5th day of month 4 226 | 227 | # Use 0 for the first argument to get the last weekday of a month 228 | utils.nth_day_of_month(0, THUR, APRIL, 2014) 229 | # (2014, 4, 24) 230 | 231 | Note that when calculating weekdays, convertdate uses the convention of 232 | the calendar and time modules: Monday is 0, Sunday is 6. 233 | 234 | from convertdate import gregorian 235 | 236 | SUN = 6 237 | 238 | day = gregorian.to_jd(2014, 4, 17) 239 | nextsunday = utils.next_weekday(SUN, day) 240 | 241 | gregorian.from_jd(nextsunday) 242 | # (2014, 4, 20) 243 | 244 | Other utility functions: 245 | 246 | - nearest\_weekday 247 | - next\_or\_current\_weekday 248 | - previous\_weekday 249 | - previous\_or\_current\_weekday 250 | 251 | -------------------------------------------------------------------------------- /src/convertdate/data/french_republican_days.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | # This file is part of convertdate. 4 | # http://github.com/fitnr/convertdate 5 | 6 | # Licensed under the MIT license: 7 | # http://opensource.org/licenses/MIT 8 | # Copyright (c) 2016, fitnr 9 | 10 | # source: Wikipedia 11 | # month 13 is the Sansculottides 12 | french_republican_days = { 13 | 1: [ 14 | "Raisin", 15 | "Safran", 16 | "Châtaigne", 17 | "Colchique", 18 | "Cheval", 19 | "Balsamine", 20 | "Carotte", 21 | "Amaranthe", 22 | "Panais", 23 | "Cuve", 24 | "Pomme de terre", 25 | "Immortelle", 26 | "Potiron", 27 | "Réséda", 28 | "Âne", 29 | "Belle de nuit", 30 | "Citrouille", 31 | "Sarrasin", 32 | "Tournesol", 33 | "Pressoir", 34 | "Chanvre", 35 | "Pêche", 36 | "Navet", 37 | "Amaryllis", 38 | "Bœuf", 39 | "Aubergine", 40 | "Piment", 41 | "Tomate", 42 | "Orge", 43 | "Tonneau", 44 | ], 45 | 2: [ 46 | "Pomme", 47 | "Céleri", 48 | "Poire", 49 | "Betterave", 50 | "Oie", 51 | "Héliotrope", 52 | "Figue", 53 | "Scorsonère", 54 | "Alisier", 55 | "Charrue", 56 | "Salsifis", 57 | "Mâcre", 58 | "Topinambour", 59 | "Endive", 60 | "Dindon", 61 | "Chervis", 62 | "Cresson", 63 | "Dentelaire", 64 | "Grenade", 65 | "Herse", 66 | "Bacchante", 67 | "Azerole", 68 | "Garance", 69 | "Orange", 70 | "Faisan", 71 | "Pistache", 72 | "Macjonc", 73 | "Coing", 74 | "Cormier", 75 | "Rouleau", 76 | ], 77 | 3: [ 78 | "Raiponce", 79 | "Turneps", 80 | "Chicorée", 81 | "Nèfle", 82 | "Cochon", 83 | "Mâche", 84 | "Chou-fleur", 85 | "Miel", 86 | "Genièvre", 87 | "Pioche", 88 | "Cire", 89 | "Raifort", 90 | "Cèdre", 91 | "Sapin", 92 | "Chevreuil", 93 | "Ajonc", 94 | "Cyprès", 95 | "Lierre", 96 | "Sabine", 97 | "Hoyau", 98 | "Érable à sucre", 99 | "Bruyère", 100 | "Roseau", 101 | "Oseille", 102 | "Grillon", 103 | "Pignon", 104 | "Liège", 105 | "Truffe", 106 | "Olive", 107 | "Pelle", 108 | ], 109 | 4: [ 110 | "Tourbe", 111 | "Houille", 112 | "Bitume", 113 | "Soufre", 114 | "Chien", 115 | "Lave", 116 | "Terre végétale", 117 | "Fumier", 118 | "Salpêtre", 119 | "Fléau", 120 | "Granit", 121 | "Argile", 122 | "Ardoise", 123 | "Grès", 124 | "Lapin", 125 | "Silex", 126 | "Marne", 127 | "Pierre à chaux", 128 | "Marbre", 129 | "Van", 130 | "Pierre à plâtre", 131 | "Sel", 132 | "Fer", 133 | "Cuivre", 134 | "Chat", 135 | "Étain", 136 | "Plomb", 137 | "Zinc", 138 | "Mercure", 139 | "Crible", 140 | ], 141 | 5: [ 142 | "Lauréole", 143 | "Mousse", 144 | "Fragon", 145 | "Perce-neige", 146 | "Taureau", 147 | "Laurier-thym", 148 | "Amadouvier", 149 | "Mézéréon", 150 | "Peuplier", 151 | "Coignée", 152 | "Ellébore", 153 | "Brocoli", 154 | "Laurier", 155 | "Avelinier", 156 | "Vache", 157 | "Buis", 158 | "Lichen", 159 | "If", 160 | "Pulmonaire", 161 | "Serpette", 162 | "Thlaspi", 163 | "Thimelé", 164 | "Chiendent", 165 | "Trainasse", 166 | "Lièvre", 167 | "Guède", 168 | "Noisetier", 169 | "Cyclamen", 170 | "Chélidoine", 171 | "Traîneau", 172 | ], 173 | 6: [ 174 | "Tussilage", 175 | "Cornouiller", 176 | "Violier", 177 | "Troène", 178 | "Bouc", 179 | "Asaret", 180 | "Alaterne", 181 | "Violette", 182 | "Marceau", 183 | "Bêche", 184 | "Narcisse", 185 | "Orme", 186 | "Fumeterre", 187 | "Vélar", 188 | "Chèvre", 189 | "Épinard", 190 | "Doronic", 191 | "Mouron", 192 | "Cerfeuil", 193 | "Cordeau", 194 | "Mandragore", 195 | "Persil", 196 | "Cochléaria", 197 | "Pâquerette", 198 | "Thon", 199 | "Pissenlit", 200 | "Sylvie", 201 | "Capillaire", 202 | "Frêne", 203 | "Plantoir", 204 | ], 205 | 7: [ 206 | "Primevère", 207 | "Platane", 208 | "Asperge", 209 | "Tulipe", 210 | "Poule", 211 | "Bette", 212 | "Bouleau", 213 | "Jonquille", 214 | "Aulne", 215 | "Couvoir", 216 | "Pervenche", 217 | "Charme", 218 | "Morille", 219 | "Hêtre", 220 | "Abeille", 221 | "Laitue", 222 | "Mélèze", 223 | "Ciguë", 224 | "Radis", 225 | "Ruche", 226 | "Gainier", 227 | "Romaine", 228 | "Marronnier", 229 | "Roquette", 230 | "Pigeon", 231 | "Lilas", 232 | "Anémone", 233 | "Pensée", 234 | "Myrtille", 235 | "Greffoir", 236 | ], 237 | 8: [ 238 | "Rose", 239 | "Chêne", 240 | "Fougère", 241 | "Aubépine", 242 | "Rossignol", 243 | "Ancolie", 244 | "Muguet", 245 | "Champignon", 246 | "Hyacinthe", 247 | "Râteau", 248 | "Rhubarbe", 249 | "Sainfoin", 250 | "Bâton d'or", 251 | "Chamerisier", 252 | "Ver à soie", 253 | "Consoude", 254 | "Pimprenelle", 255 | "Corbeille d'or", 256 | "Arroche", 257 | "Sarcloir", 258 | "Statice", 259 | "Fritillaire", 260 | "Bourrache", 261 | "Valériane", 262 | "Carpe", 263 | "Fusain", 264 | "Civette", 265 | "Buglosse", 266 | "Sénevé", 267 | "Houlette", 268 | ], 269 | 9: [ 270 | "Luzerne", 271 | "Hémérocalle", 272 | "Trèfle", 273 | "Angélique", 274 | "Canard", 275 | "Mélisse", 276 | "Fromental", 277 | "Martagon", 278 | "Serpolet", 279 | "Faux", 280 | "Fraise", 281 | "Bétoine", 282 | "Pois", 283 | "Acacia", 284 | "Caille", 285 | "Œillet", 286 | "Sureau", 287 | "Pavot", 288 | "Tilleul", 289 | "Fourche", 290 | "Barbeau", 291 | "Camomille", 292 | "Chèvrefeuille", 293 | "Caille-lait", 294 | "Tanche", 295 | "Jasmin", 296 | "Verveine", 297 | "Thym", 298 | "Pivoine", 299 | "Chariot", 300 | ], 301 | 10: [ 302 | "Seigle", 303 | "Avoine", 304 | "Oignon", 305 | "Véronique", 306 | "Mulet", 307 | "Romarin", 308 | "Concombre", 309 | "Échalote", 310 | "Absinthe", 311 | "Faucille", 312 | "Coriandre", 313 | "Artichaut", 314 | "Girofle", 315 | "Lavande", 316 | "Chamois", 317 | "Tabac", 318 | "Groseille", 319 | "Gesse", 320 | "Cerise", 321 | "Parc", 322 | "Menthe", 323 | "Cumin", 324 | "Haricot", 325 | "Orcanète", 326 | "Pintade", 327 | "Sauge", 328 | "Ail", 329 | "Vesce", 330 | "Blé", 331 | "Chalémie", 332 | ], 333 | 11: [ 334 | "Épeautre", 335 | "Bouillon blanc", 336 | "Melon", 337 | "Ivraie", 338 | "Bélier", 339 | "Prêle", 340 | "Armoise", 341 | "Carthame", 342 | "Mûre", 343 | "Arrosoir", 344 | "Panic", 345 | "Salicorne", 346 | "Abricot", 347 | "Basilic", 348 | "Brebis", 349 | "Guimauve", 350 | "Lin", 351 | "Amande", 352 | "Gentiane", 353 | "Écluse", 354 | "Carline", 355 | "Câprier", 356 | "Lentille", 357 | "Aunée", 358 | "Loutre", 359 | "Myrte", 360 | "Colza", 361 | "Lupin", 362 | "Coton", 363 | "Moulin", 364 | ], 365 | 12: [ 366 | "Prune", 367 | "Millet", 368 | "Lycoperdon", 369 | "Escourgeon", 370 | "Saumon", 371 | "Tubéreuse", 372 | "Sucrion", 373 | "Apocyn", 374 | "Réglisse", 375 | "Échelle", 376 | "Pastèque", 377 | "Fenouil", 378 | "Épine vinette", 379 | "Noix", 380 | "Truite", 381 | "Citron", 382 | "Cardère", 383 | "Nerprun", 384 | "Tagette", 385 | "Hotte", 386 | "Églantier", 387 | "Noisette", 388 | "Houblon", 389 | "Sorgho", 390 | "Écrevisse", 391 | "Bigarade", 392 | "Verge d'or", 393 | "Maïs", 394 | "Marron", 395 | "Panier", 396 | ], 397 | 13: [ 398 | "La Fête de la Vertu", 399 | "La Fête du Génie", 400 | "La Fête du Travail", 401 | "La Fête de l'Opinion", 402 | "La Fête des Récompenses", 403 | "La Fête de la Révolution", 404 | ], 405 | } 406 | -------------------------------------------------------------------------------- /src/convertdate/mayan.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # This file is part of convertdate. 3 | # http://github.com/fitnr/convertdate 4 | # Licensed under the MIT license: 5 | # http://opensource.org/licenses/MIT 6 | # Copyright (c) 2016, fitnr 7 | """ 8 | The Mayan calendar was developed in Mesoamerica. It includes two interlocking 9 | cycles: the 260-day _Tzolkin_ cycle and the 365-day _Haab'_ cycle. In the _Tzolkin_ cycle, 10 | each day is numbered 1-13 and has one of 20 day names. The _Haab'_ cycle comprises 18 months 11 | of 20 days, along with five additional days (_Wayebʼ_). 12 | 13 | The calendrical system also includes the 14 | `Long Count `__, 15 | a modified base-20 counting scheme. Dates in the long count are usually written in the form *7.18.14.8.12*. 16 | """ 17 | import itertools 18 | from math import trunc 19 | 20 | from . import gregorian 21 | from .utils import amod 22 | 23 | EPOCH = 584282.5 24 | HAAB = [ 25 | "Pop", 26 | "Wo'", 27 | "Zip", 28 | "Sotz'", 29 | "Sek", 30 | "Xul", 31 | "Yaxk'in'", 32 | "Mol", 33 | "Ch'en", 34 | "Yax", 35 | "Sak'", 36 | "Keh", 37 | "Mak", 38 | "K'ank'in", 39 | "Muwan'", 40 | "Pax", 41 | "K'ayab", 42 | "Kumk'u", 43 | "Wayeb'", 44 | ] 45 | 46 | HAAB_TRANSLATIONS = [ 47 | "Mat", 48 | "Frog", 49 | "Red", 50 | "Bat", 51 | "Bee", 52 | "Dog", 53 | "First Sun", 54 | "Water", 55 | "Cave", 56 | "Green", 57 | "White", 58 | "Red", 59 | "Encloser", 60 | "Yellow Sun", 61 | "Screech Owl", 62 | "Planting Time", 63 | "Turtle", 64 | "Ripe Corn", 65 | "Nameless", 66 | ] 67 | 68 | TZOLKIN = [ 69 | "Imix'", 70 | "Ik'", 71 | "Ak'b'al", 72 | "K'an", 73 | "Chikchan", 74 | "Kimi", 75 | "Manik'", 76 | "Lamat", 77 | "Muluk", 78 | "Ok", 79 | "Chuwen", 80 | "Eb'", 81 | "B'en", 82 | "Ix", 83 | "Men", 84 | "K'ib'", 85 | "Kab'an", 86 | "Etz'nab'", 87 | "Kawak", 88 | "Ajaw", 89 | ] 90 | 91 | TZOLKIN_TRANSLATIONS = [ 92 | "Water", 93 | "Wind", 94 | "Darkness", 95 | "Net", 96 | "Feathered Serpent", 97 | "Death", 98 | "Deer", 99 | "Seed", 100 | "Jade", 101 | "Dog", 102 | "Thread", 103 | "Path", 104 | "Maize", 105 | "Tiger", 106 | "Bird", 107 | "Will", 108 | "Wisdom", 109 | "Obsidian Knife", 110 | "Thunder", 111 | "Sun", 112 | ] 113 | 114 | 115 | def to_jd(baktun, katun, tun, uinal, kin): 116 | '''Determine Julian day from Mayan long count''' 117 | return EPOCH + (baktun * 144000) + (katun * 7200) + (tun * 360) + (uinal * 20) + kin 118 | 119 | 120 | def from_jd(jd): 121 | '''Calculate Mayan long count from Julian day''' 122 | d = jd - EPOCH 123 | if d < 0: 124 | raise ValueError("Day out of range") 125 | baktun = trunc(d / 144000) 126 | d = d % 144000 127 | katun = trunc(d / 7200) 128 | d = d % 7200 129 | tun = trunc(d / 360) 130 | d = d % 360 131 | uinal = trunc(d / 20) 132 | kin = int((d % 20)) 133 | 134 | return (baktun, katun, tun, uinal, kin) 135 | 136 | 137 | def to_gregorian(baktun, katun, tun, uinal, kin): 138 | jd = to_jd(baktun, katun, tun, uinal, kin) 139 | return gregorian.from_jd(jd) 140 | 141 | 142 | def from_gregorian(year, month, day): 143 | jd = gregorian.to_jd(year, month, day) 144 | return from_jd(jd) 145 | 146 | 147 | def to_haab(jd): 148 | '''Determine Mayan Haab "month" and day from Julian day''' 149 | # Number of days since the start of the long count 150 | lcount = trunc(jd) + 0.5 - EPOCH 151 | # Long Count begins 348 days after the start of the cycle 152 | day = (lcount + 348) % 365 153 | 154 | count = day % 20 155 | month = trunc(day / 20) 156 | 157 | return int(count), HAAB[month] 158 | 159 | 160 | def to_tzolkin(jd): 161 | '''Determine Mayan Tzolkin "month" and day from Julian day''' 162 | lcount = trunc(jd) + 0.5 - EPOCH 163 | day = amod(lcount + 4, 13) 164 | name = amod(lcount + 20, 20) 165 | return int(day), TZOLKIN[int(name) - 1] 166 | 167 | 168 | def lc_to_haab(baktun, katun, tun, uinal, kin): 169 | jd = to_jd(baktun, katun, tun, uinal, kin) 170 | return to_haab(jd) 171 | 172 | 173 | def lc_to_tzolkin(baktun, katun, tun, uinal, kin): 174 | jd = to_jd(baktun, katun, tun, uinal, kin) 175 | return to_tzolkin(jd) 176 | 177 | 178 | def lc_to_haab_tzolkin(baktun, katun, tun, uinal, kin): 179 | jd = to_jd(baktun, katun, tun, uinal, kin) 180 | dates = to_tzolkin(jd) + to_haab(jd) 181 | return "{0} {1} {2} {3}".format(*dates) 182 | 183 | 184 | def translate_haab(h): 185 | return dict(list(zip(HAAB, HAAB_TRANSLATIONS))).get(h) 186 | 187 | 188 | def translate_tzolkin(tz): 189 | return dict(list(zip(TZOLKIN, TZOLKIN_TRANSLATIONS))).get(tz) 190 | 191 | 192 | def _haab_count(day, month): 193 | '''Return the count of the given haab in the cycle. e.g. 0 Pop == 1, 5 Wayeb' == 365''' 194 | if day < 0 or day > 19: 195 | raise IndexError("Invalid day number") 196 | 197 | try: 198 | i = HAAB.index(month) 199 | except ValueError as err: 200 | raise ValueError("'{0}' is not a valid Haab' month".format(month)) from err 201 | 202 | return min(i * 20, 360) + day 203 | 204 | 205 | def _tzolkin_from_count(count): 206 | number = amod(count, 13) 207 | name = TZOLKIN[count % 20 - 1] 208 | return number, name 209 | 210 | 211 | def _tzolkin_count(day, name): 212 | if day < 1 or day > 13: 213 | raise IndexError("Invalid day number") 214 | 215 | days = set(x + day for x in range(0, 260, 13)) 216 | 217 | try: 218 | n = 1 + TZOLKIN.index(name) 219 | except ValueError as err: 220 | raise ValueError("'{0}' is not a valid Tzolk'in day name".format(name)) from err 221 | 222 | names = set(y + n for y in range(0, 260, 20)) 223 | return days.intersection(names).pop() 224 | 225 | 226 | def tzolkin_generator(number=None, name=None): 227 | """For a given tzolkin name/number combination, return a generator 228 | that gives cycle, starting with the input""" 229 | 230 | # By default, it will start at the beginning 231 | number = number or 13 232 | name = name or "Ajaw" 233 | 234 | if number > 13: 235 | raise ValueError("Invalid day number") 236 | 237 | if name not in TZOLKIN: 238 | raise ValueError("Invalid day name") 239 | 240 | count = _tzolkin_count(number, name) 241 | 242 | ranged = itertools.chain(list(range(count, 260)), list(range(1, count))) 243 | 244 | for i in ranged: 245 | yield _tzolkin_from_count(i) 246 | 247 | 248 | def longcount_generator(baktun, katun, tun, uinal, kin): 249 | '''Generate long counts, starting with input''' 250 | j = to_jd(baktun, katun, tun, uinal, kin) 251 | 252 | while True: 253 | yield from_jd(j) 254 | j = j + 1 255 | 256 | 257 | def next_haab(month, jd): 258 | '''For a given haab month and a julian day count, find the next start of that month on or after the JDC''' 259 | if jd < EPOCH: 260 | raise IndexError("Input day is before Mayan epoch.") 261 | 262 | hday, hmonth = to_haab(jd) 263 | 264 | if hmonth == month: 265 | days = 1 - hday 266 | 267 | else: 268 | count1 = _haab_count(hday, hmonth) 269 | count2 = _haab_count(1, month) 270 | 271 | # Find number of days between haab of given jd and desired haab 272 | days = (count2 - count1) % 365 273 | 274 | # add in the number of days and return new jd 275 | return jd + days 276 | 277 | 278 | def next_tzolkin(tzolkin, jd): 279 | '''For a given tzolk'in day, and a julian day count, find the next occurrance of that tzolk'in after the date''' 280 | if jd < EPOCH: 281 | raise IndexError("Input day is before Mayan epoch.") 282 | 283 | count1 = _tzolkin_count(*to_tzolkin(jd)) 284 | count2 = _tzolkin_count(*tzolkin) 285 | 286 | add_days = (count2 - count1) % 260 287 | return jd + add_days 288 | 289 | 290 | def next_tzolkin_haab(tzolkin, haab, jd): 291 | '''For a given haab-tzolk'in combination, and a Julian day count, find the next occurrance of the combination after the date''' 292 | # get H & T of input jd, and their place in the 18,980 day cycle 293 | haabcount = _haab_count(*to_haab(jd)) 294 | haab_desired_count = _haab_count(*haab) 295 | 296 | # How many days between the input day and the desired day? 297 | haab_days = (haab_desired_count - haabcount) % 365 298 | 299 | possible_haab = set(h + haab_days for h in range(0, 18980, 365)) 300 | 301 | tzcount = _tzolkin_count(*to_tzolkin(jd)) 302 | tz_desired_count = _tzolkin_count(*tzolkin) 303 | # How many days between the input day and the desired day? 304 | tzolkin_days = (tz_desired_count - tzcount) % 260 305 | 306 | possible_tz = set(t + tzolkin_days for t in range(0, 18980, 260)) 307 | 308 | try: 309 | return possible_tz.intersection(possible_haab).pop() + jd 310 | except KeyError: 311 | raise IndexError("That Haab'-Tzolk'in combination isn't possible") 312 | 313 | 314 | def month_length(month): 315 | """Not the actual length of the month, but accounts for the 5 unlucky/nameless days""" 316 | if month == "Wayeb'": 317 | return 5 318 | return 20 319 | 320 | 321 | def haab_monthcalendar(baktun=None, katun=None, tun=None, uinal=None, kin=None, jdc=None): 322 | '''For a given long count, return a calender of the current haab month, divided into tzolkin "weeks"''' 323 | if not jdc: 324 | jdc = to_jd(baktun, katun, tun, uinal, kin) 325 | 326 | haab_number, haab_month = to_haab(jdc) 327 | first_j = jdc - haab_number + 1 328 | 329 | tzolkin_start_number, tzolkin_start_name = to_tzolkin(first_j) 330 | 331 | gen_longcount = longcount_generator(*from_jd(first_j)) 332 | gen_tzolkin = tzolkin_generator(tzolkin_start_number, tzolkin_start_name) 333 | 334 | # 13 day long tzolkin 'weeks' 335 | lpad = tzolkin_start_number - 1 336 | rpad = 13 - (tzolkin_start_number + 19 % 13) 337 | 338 | monlen = month_length(haab_month) 339 | 340 | days = [None] * lpad + list(range(1, monlen + 1)) + rpad * [None] 341 | 342 | def g(x, generate): 343 | if x is None: 344 | return None 345 | return next(generate) 346 | 347 | return [[(k, g(k, gen_tzolkin), g(k, gen_longcount)) for k in days[i : i + 13]] for i in range(0, len(days), 13)] 348 | 349 | 350 | def haab_monthcalendar_prospective(haabmonth, jdc): 351 | '''Give the monthcalendar for the next occurance of given haab month after jdc''' 352 | return haab_monthcalendar(jdc=next_haab(haabmonth, jdc)) 353 | -------------------------------------------------------------------------------- /tests/test_holidays.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | import unittest 3 | from datetime import datetime 4 | 5 | from convertdate import holidays, julian 6 | 7 | 8 | class TestHolidays(unittest.TestCase): 9 | def setUp(self): 10 | self.h = holidays.Holidays(2015) 11 | 12 | def test_nth_day_of_month(self): 13 | assert holidays.nth_day_of_month(4, 2, 4, 2014) == (2014, 4, 23) 14 | self.assertRaises(IndexError, holidays.nth_day_of_month, 5, 3, 4, 2014) 15 | self.assertRaises(IndexError, holidays.nth_day_of_month, 6, 2, 4, 2014) 16 | self.assertRaises(IndexError, holidays.nth_day_of_month, 1, 7, 4, 2014) 17 | assert holidays.nth_day_of_month(4, 3, 11, 2014) == (2014, 11, 27) 18 | assert holidays.nth_day_of_month(0, 3, 11, 2014) == (2014, 11, 27) 19 | 20 | def test_holidays(self): 21 | h = holidays.Holidays(2014) 22 | self.assertEqual(h.christmas, (2014, 12, 25)) 23 | assert h.thanksgiving == (2014, 11, 27) 24 | assert h.indigenous_peoples_day == (2014, 10, 13) 25 | 26 | assert h.independence_day == (2014, 7, 4) 27 | 28 | assert self.h.christmas == (2015, 12, 25) 29 | assert self.h.christmas_eve == (2015, 12, 24) 30 | assert self.h.new_years == (2015, 1, 1) 31 | assert self.h.new_years_eve == (2015, 12, 31) 32 | assert self.h.valentines_day == (2015, 2, 14) 33 | assert self.h.halloween == (2015, 10, 31) 34 | assert self.h.mothers_day == (2015, 5, 10) 35 | self.assertEqual(self.h.fathers_day, (2015, 6, 21)) 36 | 37 | def test_class(self): 38 | h = holidays.Holidays() 39 | assert h.year == datetime.now().year 40 | assert str(self.h) == 'Holidays(2015)' 41 | 42 | h.set_year(2010) 43 | assert h.year == 2010 44 | 45 | def test_events(self): 46 | assert holidays.new_years(2013) == (2013, 1, 1) 47 | assert holidays.martin_luther_king_day(2015) == (2015, 1, 19) 48 | 49 | assert holidays.lincolns_birthday(2015) == (2015, 2, 12) 50 | assert holidays.valentines_day(2015) == (2015, 2, 14) 51 | assert holidays.washingtons_birthday(2015) == (2015, 2, 22) 52 | assert holidays.presidents_day(2015) == (2015, 2, 16) 53 | 54 | assert holidays.pulaski_day(2015) == (2015, 3, 2) 55 | assert self.h.pulaski_day == (2015, 3, 2) 56 | 57 | assert holidays.may_day(2015) == (2015, 5, 1) 58 | 59 | assert holidays.indigenous_peoples_day(2015, 'canada') == (2015, 10, 12) 60 | 61 | assert holidays.independence_day(2015) == (2015, 7, 4) 62 | assert holidays.independence_day(2015, True) == (2015, 7, 3) 63 | 64 | def test_thanksgiving(self): 65 | assert holidays.thanksgiving(2013) == (2013, 11, 28) 66 | assert holidays.thanksgiving(1939) == (1939, 11, 23) 67 | self.assertEqual(holidays.thanksgiving(1941), (1941, 11, 20)) 68 | 69 | assert self.h.thanksgiving == (2015, 11, 26) 70 | 71 | assert holidays.thanksgiving(2015, 'canada') == (2015, 10, 12) 72 | 73 | def test_easterWestern(self): 74 | easters = [ 75 | (1994, 4, 3), 76 | (1995, 4, 16), 77 | (1996, 4, 7), 78 | (1997, 3, 30), 79 | (1998, 4, 12), 80 | (1999, 4, 4), 81 | (2000, 4, 23), 82 | (2001, 4, 15), 83 | (2002, 3, 31), 84 | (2003, 4, 20), 85 | (2004, 4, 11), 86 | (2005, 3, 27), 87 | (2006, 4, 16), 88 | (2007, 4, 8), 89 | (2008, 3, 23), 90 | (2009, 4, 12), 91 | (2010, 4, 4), 92 | (2011, 4, 24), 93 | (2012, 4, 8), 94 | (2013, 3, 31), 95 | (2014, 4, 20), 96 | (2015, 4, 5), 97 | (2016, 3, 27), 98 | (2017, 4, 16), 99 | (2018, 4, 1), 100 | (2019, 4, 21), 101 | (2020, 4, 12), 102 | (2021, 4, 4), 103 | (2022, 4, 17), 104 | (2023, 4, 9), 105 | (2024, 3, 31), 106 | (2025, 4, 20), 107 | (2026, 4, 5), 108 | (2027, 3, 28), 109 | (2028, 4, 16), 110 | (2029, 4, 1), 111 | (2030, 4, 21), 112 | (2031, 4, 13), 113 | (2032, 3, 28), 114 | (2033, 4, 17), 115 | (2034, 4, 9), 116 | (2345, 4, 22), 117 | ] 118 | 119 | for y, m, d in easters: 120 | self.assertEqual(holidays.easter(y), (y, m, d)) 121 | 122 | def test_easterEastern(self): 123 | easters = [ 124 | (1999, 4, 11), 125 | (2000, 4, 30), 126 | (2001, 4, 15), 127 | (2002, 5, 5), 128 | (2003, 4, 27), 129 | (2004, 4, 11), 130 | (2005, 5, 1), 131 | (2006, 4, 23), 132 | (2007, 4, 8), 133 | (2008, 4, 27), 134 | (2009, 4, 19), 135 | (2010, 4, 4), 136 | (2011, 4, 24), 137 | (2012, 4, 15), 138 | (2013, 5, 5), 139 | (2014, 4, 20), 140 | (2015, 4, 12), 141 | (2016, 5, 1), 142 | (2017, 4, 16), 143 | (2018, 4, 8), 144 | (2019, 4, 28), 145 | (2020, 4, 19), 146 | (2021, 5, 2), 147 | (2022, 4, 24), 148 | (2023, 4, 16), 149 | (2024, 5, 5), 150 | (2025, 4, 20), 151 | (2026, 4, 12), 152 | (2027, 5, 2), 153 | (2028, 4, 16), 154 | (2029, 4, 8), 155 | (2030, 4, 28), 156 | (2031, 4, 13), 157 | (2032, 5, 2), 158 | (2033, 4, 24), 159 | (2034, 4, 9), 160 | (2035, 4, 29), 161 | (2036, 4, 20), 162 | (2037, 4, 5), 163 | (2038, 4, 25), 164 | (2039, 4, 17), 165 | (2056, 4, 9), 166 | (2156, 4, 11), 167 | ] 168 | 169 | for y, m, d in easters: 170 | self.assertEqual(holidays.easter(y, "orthodox"), (y, m, d)) 171 | self.assertEqual(holidays.easter(y, "eastern"), (y, m, d)) 172 | 173 | self.assertEqual(self.h.easter, (2015, 4, 5)) 174 | 175 | def testNonChalcedonian(self): 176 | # In these years, Orthodox Easter falls on 6 April (Julian), 177 | # but Non-Chalcedonian churches celebrate it a week later on 13 Aprail 178 | years = ( 179 | 570, 180 | 665, 181 | 760, 182 | 1007, 183 | 1102, 184 | 1197, 185 | 1292, 186 | 1539, 187 | 1634, 188 | 1729, 189 | 1824, 190 | 2071, 191 | 2166, 192 | 2261, 193 | 2356, 194 | ) 195 | 196 | for y in years: 197 | orthodox = julian.from_gregorian(*holidays.easter(y, "orthodox")) 198 | eastern = julian.from_gregorian(*holidays.easter(y, "eastern")) 199 | self.assertNotEqual(orthodox, eastern) 200 | self.assertEqual((y, 4, 6), orthodox) 201 | self.assertEqual((y, 4, 13), eastern) 202 | 203 | for y in years: 204 | self.assertEqual(holidays.easter(y + 1, "orthodox"), holidays.easter(y + 1, "eastern")) 205 | 206 | def test_jewish_holidays(self): 207 | # http://www.chabad.org/holidays/passover/pesach_cdo/aid/671901/jewish/When-is-Passover-in-2013-2014-2015-2016-and-2017.htm 208 | # the date here is the start of the holiday, so the eve=1 option is used 209 | passovers = [(2013, 3, 25), (2014, 4, 14), (2015, 4, 3), (2016, 4, 22), (2017, 4, 10)] 210 | for y, m, d in passovers: 211 | self.assertEqual(holidays.passover(y, eve=1), (y, m, d)) 212 | 213 | rosh_hashanahs = [ 214 | (2014, 9, 24), 215 | (2015, 9, 13), 216 | (2016, 10, 2), 217 | (2017, 9, 20), 218 | ] 219 | for y, m, d in rosh_hashanahs: 220 | self.assertEqual(holidays.rosh_hashanah(y, eve=1), (y, m, d)) 221 | 222 | self.assertEqual(holidays.hanukkah(2015, True), (2015, 12, 6)) 223 | self.assertEqual(holidays.hanukkah(2015), (2015, 12, 7)) 224 | 225 | self.assertEqual(holidays.yom_kippur(2015), (2015, 9, 23)) 226 | self.assertEqual(holidays.yom_kippur(2015, True), (2015, 9, 22)) 227 | 228 | sukkots = [ 229 | (2016, 10, 17), 230 | (2015, 9, 28), 231 | ] 232 | for y, m, d in sukkots: 233 | self.assertEqual(holidays.sukkot(y, eve=0), (y, m, d)) 234 | 235 | shavuots = [(2016, 6, 12), (2015, 5, 24)] 236 | for y, m, d in shavuots: 237 | self.assertEqual(holidays.shavuot(y, eve=0), (y, m, d)) 238 | 239 | purims = [(2017, 3, 12), (2016, 3, 24)] 240 | for y, m, d in purims: 241 | self.assertEqual(holidays.purim(y, eve=0), (y, m, d)) 242 | 243 | tisha_bavs = [ 244 | (2019, 8, 11), 245 | (2020, 7, 30), 246 | (2021, 7, 18), 247 | (2022, 8, 7), 248 | (2023, 7, 27), 249 | ] 250 | for y, m, d in tisha_bavs: 251 | self.assertEqual(holidays.tisha_bav(y, eve=0), (y, m, d)) 252 | 253 | assert self.h.hanukkah == (2015, 12, 7) 254 | assert self.h.rosh_hashanah == (2015, 9, 14) 255 | assert self.h.yom_kippur == (2015, 9, 23) 256 | assert self.h.passover == (2015, 4, 4) 257 | 258 | assert self.h.tisha_bav == (2015, 7, 26) 259 | assert self.h.shemini_azeret == (2015, 10, 5) 260 | assert self.h.lag_baomer == (2015, 5, 7) 261 | assert self.h.tu_beshvat == (2015, 2, 4) 262 | 263 | def test_mexican_holidays(self): 264 | self.assertEqual(holidays.natalicio_benito_juarez(2015, False), (2015, 3, 21)) 265 | self.assertEqual(holidays.natalicio_benito_juarez(2015), (2015, 3, 16)) 266 | 267 | assert self.h.dia_constitucion == (2015, 2, 2) 268 | assert self.h.natalicio_benito_juarez == (2015, 3, 16) 269 | assert self.h.dia_independencia == (2015, 9, 16) 270 | assert self.h.dia_revolucion == (2015, 11, 16) 271 | 272 | def test_usa_holidays(self): 273 | assert self.h.independence_day == (2015, 7, 3) 274 | assert self.h.flag_day == (2015, 6, 14) 275 | assert self.h.election_day == (2015, 11, 3) 276 | assert self.h.presidents_day == (2015, 2, 16) 277 | assert self.h.washingtons_birthday == (2015, 2, 22) 278 | assert self.h.lincolns_birthday == (2015, 2, 12) 279 | assert self.h.memorial_day == (2015, 5, 25) 280 | assert self.h.labor_day == (2015, 9, 7) 281 | assert self.h.indigenous_peoples_day == (2015, 10, 12) 282 | assert self.h.veterans_day == (2015, 11, 11) 283 | assert self.h.martin_luther_king_day == (2015, 1, 19) 284 | 285 | def test_usa_holidays_observed(self): 286 | self.assertSequenceEqual(holidays.independence_day(2015), (2015, 7, 4)) 287 | assert holidays.independence_day(2015, True) == (2015, 7, 3) 288 | assert holidays.washingtons_birthday(2015) == (2015, 2, 22) 289 | assert holidays.washingtons_birthday(2015, True) == (2015, 2, 16) 290 | assert holidays.washingtons_birthday(2020, True) == (2020, 2, 17) 291 | assert holidays.new_years(2022, True) == (2021, 12, 31) 292 | self.assertSequenceEqual(holidays.christmas(2021, True), (2021, 12, 24)) 293 | 294 | def test_deprecated_columbus_day(self): 295 | with self.assertRaises(DeprecationWarning): 296 | holidays.columbus_day(2020) 297 | 298 | def test_islamic_holidays(self): 299 | """Test the dates of certain Islamic holidays.""" 300 | holidays_2015 = { 301 | 'eid_aladha': (2015, 9, 24), 302 | 'ramadan': (2015, 6, 18), 303 | 'eid_alfitr': (2015, 7, 18), 304 | } 305 | with self.subTest(): 306 | for name, date in holidays_2015.items(): 307 | self.assertEqual(getattr(self.h, name), date, name) 308 | 309 | h21 = holidays.Holidays(2021) 310 | holidays_2021 = { 311 | 'eid_aladha': (2021, 7, 20), 312 | 'ramadan': (2021, 4, 13), 313 | 'eid_alfitr': (2021, 5, 13), 314 | } 315 | with self.subTest(): 316 | for name, date in holidays_2021.items(): 317 | self.assertEqual(getattr(h21, name), date, name) 318 | -------------------------------------------------------------------------------- /src/convertdate/french_republican.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # This file is part of convertdate. 3 | # http://github.com/fitnr/convertdate 4 | # http://opensource.org/licenses/MIT 5 | # Copyright (c) 2016, fitnr 6 | # Licensed under the MIT license 7 | """ 8 | The `French Republican calendar `__ 9 | was created during the heroic overthrow of the Ancien Regime. 10 | 11 | Leap year calculations in the French Republican calendar are a matter of 12 | dispute. By default, `convertdate` calculates leap years using the 13 | autumnal equinox. You can also use one of three more systematic methods 14 | proposed over the years. 15 | 16 | - Romme, a co-creator of the calendar, proposed leap years in years 17 | divisible by four, except for years divisible by 100. 18 | - Some concordances were drawn up in the 19th century that gave leap 19 | years every 4 years, in years that give a remainder of three when 20 | divided by four (19, 23, 27, etc...). 21 | - Von Mädler proposed leap years in years divisible by four, except 22 | for years divisible by 128. 23 | 24 | You can specify any of these three methods with the method keyword 25 | argument in `french_republican` conversion functions. 26 | 27 | .. code-block:: python 28 | 29 | from convertdate import french_republican 30 | 31 | french_republican.to_gregorian(20, 1, 1), method='romme') 32 | # (1811, 9, 23) 33 | 34 | french_republican.to_gregorian(20, 1, 1), method='continuous') 35 | # (1811, 9, 24) 36 | 37 | french_republican.to_gregorian(20, 1, 1), method='madler') 38 | # (1811, 9, 23) 39 | 40 | All the conversion methods correctly assign the leap years implemented 41 | while calendar was in use (3, 7, 11). 42 | """ 43 | from math import trunc 44 | 45 | from pymeeus.Sun import Sun 46 | 47 | from . import gregorian 48 | from .data.french_republican_days import french_republican_days 49 | 50 | # julian day (1792, 9, 22) 51 | EPOCH = 2375839.5 52 | 53 | YEAR_EPOCH = 1791.0 54 | 55 | DAYS_IN_YEAR = 365.0 56 | 57 | MOIS = MONTHS = [ 58 | "Vendémiaire", 59 | "Brumaire", 60 | "Frimaire", 61 | "Nivôse", 62 | "Pluviôse", 63 | "Ventôse", 64 | "Germinal", 65 | "Floréal", 66 | "Prairial", 67 | "Messidor", 68 | "Thermidor", 69 | "Fructidor", 70 | "Sansculottides", 71 | ] 72 | 73 | LEAP_CYCLE_DAYS = 1461.0 # 365 * 4 + 1 74 | LEAP_CYCLE_YEARS = 4.0 75 | 76 | 77 | def leap(year, method=None): 78 | """ 79 | Determine if this is a leap year in the FR calendar using one of three methods: 4, 100, 128 80 | (every 4th years, every 4th or 400th but not 100th, every 4th but not 128th) 81 | 82 | Methods: 83 | * 4 (concordance rule): leap every four years: 3, 7, 11, 15, ... etc 84 | * 100 (Romme's rule): leap every 4th and 400th year, but not 100th: 85 | 20, 24, ... 96, 104, ... 396, 400, 404 ... 86 | * 128 (von Mädler's rule): leap every 4th but not 128th: 20, 24, ... 124, 132, ... 87 | * equinox [default]: use calculation of the equinox to determine date, never returns a leap year 88 | """ 89 | method = method or 'equinox' 90 | 91 | if year in (3, 7, 11): 92 | return True 93 | 94 | if year < 15: 95 | return False 96 | 97 | if method in (4, 'continuous') or (year <= 16 and method in (128, 'madler', 4, 'continuous')): 98 | return year % 4 == 3 99 | 100 | if method in (100, 'romme'): 101 | return (year % 4 == 0 and year % 100 != 0) or year % 400 == 0 102 | 103 | if method in (128, 'madler'): 104 | return year % 4 == 0 and year % 128 != 0 105 | 106 | if method == 'equinox': 107 | # Is equinox on 366th day after (year, 1, 1) 108 | startjd = to_jd(year, 1, 1, method='equinox') 109 | if premier_da_la_annee(startjd + 367) - startjd == 366.0: 110 | return True 111 | else: 112 | raise ValueError("Unknown leap year method. Try: continuous, romme, madler or equinox") 113 | 114 | return False 115 | 116 | 117 | def _previous_fall_equinox(jd): 118 | '''Return the julian day count of the previous fall equinox.''' 119 | y, _, _ = gregorian.from_jd(jd) 120 | eqx = Sun.get_equinox_solstice(y, "autumn").jde() 121 | if eqx > jd: 122 | eqx = Sun.get_equinox_solstice(y - 1, "autumn").jde() 123 | 124 | return eqx 125 | 126 | 127 | def _next_fall_equinox(jd): 128 | '''Return the julian day count of the previous fall equinox.''' 129 | y, _, _ = gregorian.from_jd(jd) 130 | eqx = Sun.get_equinox_solstice(y, "autumn").jde() 131 | if eqx < jd: 132 | eqx = Sun.get_equinox_solstice(y + 1, "autumn").jde() 133 | 134 | return eqx 135 | 136 | 137 | def premier_da_la_annee(jd): 138 | """ 139 | Returns Julian day number containing fall equinox (first day of the FR year) 140 | of the current FR year. 141 | """ 142 | previous = trunc(_previous_fall_equinox(jd) - 0.5) + 0.5 143 | 144 | if previous + 364 < jd: 145 | # test if current day is the equinox if the previous equinox was a long time ago 146 | nxt = trunc(_next_fall_equinox(jd) - 0.5) + 0.5 147 | 148 | if nxt <= jd: 149 | return nxt 150 | 151 | return previous 152 | 153 | 154 | def to_jd(year, month, day, method=None): 155 | '''Obtain Julian day from a given French Revolutionary calendar date.''' 156 | method = method or 'equinox' 157 | 158 | if day < 1 or day > 30: 159 | raise ValueError("Invalid day for this calendar") 160 | 161 | if month > 13: 162 | raise ValueError("Invalid month for this calendar") 163 | 164 | if month == 13 and day > 5 + leap(year, method=method): 165 | raise ValueError("Invalid day for this month in this calendar") 166 | 167 | if method == 'equinox': 168 | return _to_jd_equinox(year, month, day) 169 | 170 | return _to_jd_schematic(year, month, day, method) 171 | 172 | 173 | def _to_jd_schematic(year, month, day, method): 174 | '''Calculate JD using various leap-year calculation methods''' 175 | y0, y1, y2, y3, y4, y5 = 0, 0, 0, 0, 0, 0 176 | 177 | intercal_cycle_yrs, over_cycle_yrs, leap_suppression_yrs = None, None, None 178 | 179 | # Use the every-four-years method below year 16 (madler) or below 15 (romme) 180 | if (method in (100, 'romme') and year < 15) or (method in (128, 'madler') and year < 17): 181 | method = 4 182 | 183 | if method in (4, 'continuous'): 184 | # Leap years: 15, 19, 23, ... 185 | y5 = -365 186 | 187 | elif method in (100, 'romme'): 188 | year = year - 13 189 | y5 = DAYS_IN_YEAR * 12 + 3 190 | 191 | leap_suppression_yrs = 100.0 192 | leap_suppression_days = 36524 # leap_cycle_days * 25 - 1 193 | 194 | intercal_cycle_yrs = 400.0 195 | intercal_cycle_days = 146097 # leap_suppression_days * 4 + 1 196 | 197 | over_cycle_yrs = 4000.0 198 | over_cycle_days = 1460969 # intercal_cycle_days * 10 - 1 199 | 200 | elif method in (128, 'madler'): 201 | year = year - 17 202 | y5 = DAYS_IN_YEAR * 16 + 4 203 | 204 | leap_suppression_days = 46751 # 32 * leap_cycle_days - 1 205 | leap_suppression_yrs = 128 206 | 207 | else: 208 | raise ValueError("Unknown leap year method. Try: continuous, romme, madler or equinox") 209 | 210 | if over_cycle_yrs: 211 | y0 = trunc(year / over_cycle_yrs) * over_cycle_days 212 | year = year % over_cycle_yrs 213 | 214 | # count intercalary cycles in days (400 years long or None) 215 | if intercal_cycle_yrs: 216 | y1 = trunc(year / intercal_cycle_yrs) * intercal_cycle_days 217 | year = year % intercal_cycle_yrs 218 | 219 | # count leap suppresion cycles in days (100 or 128 years long) 220 | if leap_suppression_yrs: 221 | y2 = trunc(year / leap_suppression_yrs) * leap_suppression_days 222 | year = year % leap_suppression_yrs 223 | 224 | y3 = trunc(year / LEAP_CYCLE_YEARS) * LEAP_CYCLE_DAYS 225 | year = year % LEAP_CYCLE_YEARS 226 | 227 | # Adjust 'year' by one to account for lack of year 0 228 | y4 = year * DAYS_IN_YEAR 229 | 230 | yj = y0 + y1 + y2 + y3 + y4 + y5 231 | 232 | mj = (month - 1) * 30 233 | 234 | return EPOCH + yj + mj + day - 1 235 | 236 | 237 | def _to_jd_equinox(an, mois, jour): 238 | '''Return jd of this FR date, counting from the previous equinox.''' 239 | day_of_adr = (30 * (mois - 1)) + (jour - 1) 240 | equinoxe = _next_fall_equinox(gregorian.to_jd(an + YEAR_EPOCH, 1, 1)) 241 | return trunc(equinoxe - 0.5) + 0.5 + day_of_adr 242 | 243 | 244 | def from_jd(jd, method=None): 245 | """Calculate date in the French Revolutionary 246 | calendar from Julian day. The five or six 247 | "sansculottides" are considered a thirteenth 248 | month in the results of this function.""" 249 | method = method or 'equinox' 250 | 251 | if method == 'equinox': 252 | return _from_jd_equinox(jd) 253 | 254 | return _from_jd_schematic(jd, method) 255 | 256 | 257 | def _from_jd_schematic(jd, method): 258 | '''Convert from JD using various leap-year calculation methods''' 259 | if jd < EPOCH: 260 | raise ValueError("Can't convert days before the French Revolution") 261 | 262 | # days since Epoch 263 | J = trunc(jd) + 0.5 - EPOCH 264 | 265 | y0, y1, y2, y3, y4, y5 = 0, 0, 0, 0, 0, 0 266 | intercal_cycle_days = leap_suppression_days = over_cycle_days = None 267 | 268 | # Use the every-four-years method below year 17 269 | if (J <= DAYS_IN_YEAR * 12 + 3 and method in (100, 'romme')) or ( 270 | J <= DAYS_IN_YEAR * 17 + 4 and method in (128, 'madler') 271 | ): 272 | method = 4 273 | 274 | # set p and r in Hatcher algorithm 275 | if method in (4, 'continuous'): 276 | # Leap years: 15, 19, 23, ... 277 | # Reorganize so that leap day is last day of cycle 278 | J = J + 365 279 | y5 = -1 280 | 281 | elif method in (100, 'romme'): 282 | # Year 15 is not a leap year 283 | # Year 16 is leap, then multiples of 4, not multiples of 100, yes multiples of 400 284 | y5 = 12 285 | J = J - DAYS_IN_YEAR * 12 - 3 286 | 287 | leap_suppression_yrs = 100.0 288 | leap_suppression_days = 36524 # LEAP_CYCLE_DAYS * 25 - 1 289 | 290 | intercal_cycle_yrs = 400.0 291 | intercal_cycle_days = 146097 # leap_suppression_days * 4 + 1 292 | 293 | over_cycle_yrs = 4000.0 294 | over_cycle_days = 1460969 # intercal_cycle_days * 10 - 1 295 | 296 | y0 = trunc(J / over_cycle_days) * over_cycle_yrs 297 | J = J % over_cycle_days 298 | 299 | y1 = trunc(J / intercal_cycle_days) * intercal_cycle_yrs 300 | J = J % intercal_cycle_days 301 | 302 | elif method in (128, 'madler'): 303 | # Year 15 is a leap year, then year 20 and multiples of 4, not multiples of 128 304 | y5 = 16 305 | J = J - DAYS_IN_YEAR * 16 - 4 306 | 307 | leap_suppression_yrs = 128 308 | leap_suppression_days = 46751 # 32 * leap_cycle_days - 1 309 | 310 | else: 311 | raise ValueError("Unknown leap year method. Try: continuous, romme, madler or equinox") 312 | 313 | if leap_suppression_days: 314 | y2 = trunc(J / leap_suppression_days) * leap_suppression_yrs 315 | J = J % leap_suppression_days 316 | 317 | y3 = trunc(J / LEAP_CYCLE_DAYS) * LEAP_CYCLE_YEARS 318 | 319 | if J % LEAP_CYCLE_DAYS == LEAP_CYCLE_DAYS - 1: 320 | J = 1460 321 | else: 322 | J = J % LEAP_CYCLE_DAYS 323 | 324 | # 0 <= J <= 1460 325 | # J needs to be 365 here on leap days ONLY 326 | 327 | y4 = trunc(J / DAYS_IN_YEAR) 328 | 329 | if J == DAYS_IN_YEAR * 4: 330 | y4 = y4 - 1 331 | J = 365.0 332 | else: 333 | J = J % DAYS_IN_YEAR 334 | 335 | year = y0 + y1 + y2 + y3 + y4 + y5 336 | 337 | month = trunc(J / 30.0) 338 | J = J - month * 30 339 | 340 | return trunc(year) + 1, month + 1, trunc(J) + 1 341 | 342 | 343 | def _from_jd_equinox(jd): 344 | '''Calculate the FR day using the equinox as day 1''' 345 | jd = trunc(jd) + 0.5 346 | equinoxe = premier_da_la_annee(jd) 347 | 348 | an = int(gregorian.from_jd(equinoxe)[0] - YEAR_EPOCH) 349 | mois = trunc((jd - equinoxe) / 30.0) + 1 350 | jour = int((jd - equinoxe) % 30) + 1 351 | 352 | return (an, mois, jour) 353 | 354 | 355 | def decade(jour): 356 | return trunc(jour / 100.0) + 1 357 | 358 | 359 | def day_name(month, day): 360 | return french_republican_days[month][day - 1] 361 | 362 | 363 | def from_gregorian(year, month, day, method=None): 364 | return from_jd(gregorian.to_jd(year, month, day), method=method) 365 | 366 | 367 | def to_gregorian(an, mois, jour, method=None): 368 | return gregorian.from_jd(to_jd(an, mois, jour, method=method)) 369 | 370 | 371 | def format(an, mois, jour): 372 | """Convert a FR date into a string with the format DD MONTH YYYY.""" 373 | # pylint: disable=redefined-builtin 374 | return "{0} {1} {2}".format(jour, MOIS[mois - 1], an) 375 | -------------------------------------------------------------------------------- /tests/test_french_republican.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | import time 3 | import unittest 4 | 5 | from convertdate import french_republican as fr 6 | from convertdate import gregorian 7 | 8 | year_starts = [ 9 | ((1, 1, 1), (1792, 9, 22)), 10 | ((2, 1, 1), (1793, 9, 22)), 11 | ((3, 1, 1), (1794, 9, 22)), 12 | ((4, 1, 1), (1795, 9, 23)), 13 | ((5, 1, 1), (1796, 9, 22)), 14 | ((6, 1, 1), (1797, 9, 22)), 15 | ((7, 1, 1), (1798, 9, 22)), 16 | ((8, 1, 1), (1799, 9, 23)), 17 | ((9, 1, 1), (1800, 9, 23)), 18 | ((10, 1, 1), (1801, 9, 23)), 19 | ((11, 1, 1), (1802, 9, 23)), 20 | ((12, 1, 1), (1803, 9, 24)), 21 | ((13, 1, 1), (1804, 9, 23)), 22 | ((14, 1, 1), (1805, 9, 23)), 23 | ] 24 | 25 | leaps = [ 26 | ((3, 13, 6), (1795, 9, 22)), 27 | ((7, 13, 6), (1799, 9, 22)), 28 | ((11, 13, 6), (1803, 9, 23)), 29 | ] 30 | 31 | romme = [ 32 | ((15, 1, 1), (1806, 9, 23)), 33 | ((15, 13, 5), (1807, 9, 22)), 34 | ((16, 1, 1), (1807, 9, 23)), 35 | ((16, 13, 6), (1808, 9, 22)), 36 | ((17, 1, 1), (1808, 9, 23)), 37 | ((17, 13, 5), (1809, 9, 22)), 38 | ((18, 1, 1), (1809, 9, 23)), 39 | ((19, 1, 1), (1810, 9, 23)), 40 | ((20, 1, 1), (1811, 9, 23)), 41 | ((20, 13, 6), (1812, 9, 22)), 42 | ((222, 1, 1), (2013, 9, 22)), 43 | ((223, 1, 1), (2014, 9, 22)), 44 | ((225, 1, 1), (2016, 9, 22)), 45 | ] 46 | 47 | continuous = [ 48 | ((15, 1, 1), (1806, 9, 23)), 49 | ((15, 13, 5), (1807, 9, 22)), 50 | ((15, 13, 6), (1807, 9, 23)), 51 | ((16, 1, 1), (1807, 9, 24)), 52 | ((17, 1, 1), (1808, 9, 23)), 53 | ((18, 1, 1), (1809, 9, 23)), 54 | ((19, 1, 1), (1810, 9, 23)), 55 | ((19, 13, 6), (1811, 9, 23)), 56 | ((20, 1, 1), (1811, 9, 24)), 57 | ((220, 1, 1), (2011, 9, 25)), 58 | ((221, 1, 1), (2012, 9, 24)), 59 | ((222, 1, 1), (2013, 9, 24)), 60 | ((223, 1, 1), (2014, 9, 24)), 61 | ((223, 13, 6), (2015, 9, 24)), 62 | ((224, 1, 1), (2015, 9, 25)), 63 | ((225, 1, 1), (2016, 9, 24)), 64 | ] 65 | 66 | madler = [ 67 | ((15, 1, 1), (1806, 9, 23)), 68 | ((15, 13, 6), (1807, 9, 23)), 69 | ((16, 1, 1), (1807, 9, 24)), 70 | ((16, 13, 5), (1808, 9, 22)), 71 | ((17, 1, 1), (1808, 9, 23)), 72 | ((18, 1, 1), (1809, 9, 23)), 73 | ((18, 13, 5), (1810, 9, 22)), 74 | ((19, 1, 1), (1810, 9, 23)), 75 | ((19, 13, 5), (1811, 9, 22)), 76 | ((20, 1, 1), (1811, 9, 23)), 77 | ((20, 13, 6), (1812, 9, 22)), 78 | ((222, 1, 1), (2013, 9, 23)), 79 | ((223, 1, 1), (2014, 9, 23)), 80 | ((224, 1, 1), (2015, 9, 23)), 81 | ((224, 13, 6), (2016, 9, 22)), 82 | ((225, 1, 1), (2016, 9, 23)), 83 | ] 84 | 85 | 86 | class TestFrenchRepublican(unittest.TestCase): 87 | def setUp(self): 88 | self.tm = time.localtime() 89 | self.gregoriandate = (self.tm[0], self.tm[1], self.tm[2]) 90 | 91 | self.jd = gregorian.to_jd(self.gregoriandate[0], self.gregoriandate[1], self.gregoriandate[2]) 92 | 93 | self.x = gregorian.to_jd(2016, 2, 29) 94 | self.j = gregorian.to_jd(2015, 9, 24) 95 | 96 | # around the autumnal equinox 97 | self.start = 2457285 98 | 99 | def test_french_republican(self): 100 | assert self.jd == fr.to_jd(*fr.from_jd(self.jd)) 101 | assert fr.from_gregorian(2014, 6, 14) == (222, 9, 26) 102 | assert (2014, 6, 14) == fr.to_gregorian(222, 9, 26) 103 | 104 | assert (3, 13, 6) == fr.from_gregorian(1795, 9, 22) 105 | 106 | for jd in range(2378822, 2488395, 2000): 107 | self.assertEqual(jd + 0.5, gregorian.to_jd(*gregorian.from_jd(jd + 0.5))) 108 | 109 | def test_french_republican_leap(self): 110 | self.assertTrue(fr.leap(3)) 111 | self.assertTrue(fr.leap(3, 'madler')) 112 | self.assertTrue(fr.leap(3, 'romme')) 113 | self.assertTrue(fr.leap(3, 'continuous')) 114 | 115 | self.assertTrue(fr.leap(7)) 116 | self.assertTrue(fr.leap(7, 'madler')) 117 | self.assertTrue(fr.leap(7, 'romme')) 118 | self.assertTrue(fr.leap(7, 'continuous')) 119 | 120 | self.assertTrue(fr.leap(11)) 121 | self.assertTrue(fr.leap(11, 'madler')) 122 | self.assertTrue(fr.leap(11, 'romme')) 123 | self.assertTrue(fr.leap(11, 'continuous')) 124 | 125 | self.assertFalse(fr.leap(4)) 126 | self.assertFalse(fr.leap(14)) 127 | 128 | self.assertTrue(fr.leap(15)) 129 | self.assertTrue(fr.leap(15, 'madler')) 130 | self.assertFalse(fr.leap(15, 'romme')) 131 | self.assertTrue(fr.leap(15, 'continuous')) 132 | 133 | self.assertTrue(fr.leap(20)) 134 | self.assertTrue(fr.leap(20, 'madler')) 135 | self.assertTrue(fr.leap(20, 'romme')) 136 | self.assertFalse(fr.leap(20, 'continuous')) 137 | 138 | self.assertFalse(fr.leap(23)) 139 | self.assertFalse(fr.leap(23, 'madler')) 140 | self.assertFalse(fr.leap(23, 'romme')) 141 | self.assertTrue(fr.leap(23, 'continuous')) 142 | 143 | self.assertRaises(ValueError, fr.leap, 100, method='foo') 144 | 145 | def test_french_republican_decade(self): 146 | self.assertEqual(fr.decade(1), 1) 147 | 148 | def test_french_republican_format(self): 149 | self.assertEqual(fr.format(8, 2, 18), '18 Brumaire 8') 150 | 151 | def test_french_republican_to_jd_errors(self): 152 | self.assertRaises(ValueError, fr.to_jd, 100, 1, 0) 153 | self.assertRaises(ValueError, fr.to_jd, 100, 1, 31) 154 | self.assertRaises(ValueError, fr.to_jd, 100, 14, 1) 155 | self.assertRaises(ValueError, fr.to_jd, 4, 13, 6) 156 | self.assertRaises(ValueError, fr.to_jd, 100, 12, 1, method='foo') 157 | 158 | def test_french_republican_from_jd_errors(self): 159 | self.assertRaises(ValueError, fr.from_gregorian, 1789, 1, 1, 'romme') 160 | 161 | def test_french_republican_start_of_years_from_gregorian_equinoctal(self): 162 | for f, g in year_starts: 163 | self.assertEqual(f, fr.from_gregorian(*g)) 164 | 165 | def test_french_republican_start_of_years_to_gregorian_equinoctal(self): 166 | for f, g in year_starts: 167 | self.assertEqual(g, fr.to_gregorian(*f)) 168 | 169 | def test_french_republican_leap_days_from_gregorian_equinoctal(self): 170 | for f, g in leaps: 171 | self.assertEqual(f, fr.from_gregorian(*g)) 172 | 173 | def test_french_republican_leap_days_to_gregorian_equinoctal(self): 174 | for f, g in leaps: 175 | self.assertEqual(g, fr.to_gregorian(*f)) 176 | 177 | # Madler (128) 178 | 179 | def test_french_republican_leap_days_from_gregorian_madler(self): 180 | for f, g in leaps: 181 | self.assertEqual(f, fr.from_gregorian(*g, method='madler')) 182 | 183 | def test_french_republican_leap_days_to_gregorian_madler(self): 184 | for f, g in leaps: 185 | self.assertEqual(g, fr.to_gregorian(*f, method='madler')) 186 | 187 | def test_french_republican_schematic_madler_to_gregorian(self): 188 | for f, g in year_starts: 189 | self.assertEqual(g, fr.to_gregorian(*f, method=128)) 190 | 191 | for f, g in madler: 192 | self.assertEqual(g, fr.to_gregorian(*f, method=128)) 193 | 194 | def test_french_republican_schematic_madler_from_gregorian(self): 195 | for f, g in year_starts: 196 | self.assertEqual(g, fr.to_gregorian(*f, method=128)) 197 | 198 | for f, g in madler: 199 | self.assertEqual(f, fr.from_gregorian(*g, method=128)) 200 | 201 | def test_french_republican_schematic_madler(self): 202 | self.assertEqual(self.j, fr.to_jd(*fr.from_jd(self.j, method=128), method=128)) 203 | self.assertEqual(self.x, fr.to_jd(*fr.from_jd(self.x, method=128), method=128)) 204 | self.assertEqual(self.jd, fr.to_jd(*fr.from_jd(self.jd, method=128), method=128)) 205 | 206 | # # Romme (100) 207 | 208 | def test_french_republican_leap_days_from_gregorian_romme(self): 209 | for f, g in leaps: 210 | self.assertEqual(f, fr.from_gregorian(*g, method=100)) 211 | 212 | def test_french_republican_leap_days_to_gregorian_romme(self): 213 | for f, g in leaps: 214 | self.assertEqual(g, fr.to_gregorian(*f, method=100)) 215 | 216 | def test_french_republican_schematic_romme_to_gregorian(self): 217 | for f, g in year_starts: 218 | self.assertEqual(g, fr.to_gregorian(*f, method=100)) 219 | 220 | for f, g in romme: 221 | self.assertEqual(g, fr.to_gregorian(*f, method='romme')) 222 | 223 | def test_french_republican_schematic_romme_from_gregorian(self): 224 | for f, g in year_starts: 225 | self.assertEqual(f, fr.from_gregorian(*g, method='romme')) 226 | 227 | for f, g in romme: 228 | self.assertEqual(f, fr.from_gregorian(*g, method=100)) 229 | 230 | def test_french_republican_schematic_romme(self): 231 | self.assertEqual( 232 | self.gregoriandate, 233 | fr.to_gregorian(*fr.from_gregorian(*self.gregoriandate, method=100), method=100), 234 | ) 235 | 236 | self.assertEqual(self.jd, fr.to_jd(*fr.from_jd(self.jd, method='romme'), method=100)) 237 | self.assertEqual(self.x, fr.to_jd(*fr.from_jd(self.x, method=100), method=100)) 238 | assert self.j == fr.to_jd(*fr.from_jd(self.j, method=100), method=100) 239 | 240 | # Continuous (4) 241 | 242 | def test_french_republican_leap_days_from_gregorian_continuous(self): 243 | for f, g in leaps: 244 | self.assertEqual(f, fr.from_gregorian(*g, method=4)) 245 | 246 | def test_french_republican_leap_days_to_gregorian_continuous(self): 247 | for f, g in leaps: 248 | self.assertEqual(g, fr.to_gregorian(*f, method=4)) 249 | 250 | def test_french_republican_schematic_continuous_to_gregorian(self): 251 | for f, g in year_starts: 252 | self.assertEqual(g, fr.to_gregorian(*f, method=4)) 253 | 254 | for f, g in continuous: 255 | self.assertEqual(g, fr.to_gregorian(*f, method='continuous')) 256 | 257 | def test_french_republican_schematic_continuous_from_gregorian(self): 258 | self.assertEqual((16, 1, 1), fr.from_gregorian(1807, 9, 24, method='continuous')) 259 | 260 | for f, g in year_starts: 261 | self.assertEqual(f, fr.from_gregorian(*g, method=4)) 262 | 263 | for f, g in continuous: 264 | self.assertEqual(f, fr.from_gregorian(*g, method=4)) 265 | 266 | def test_french_republican_schematic_continuous(self): 267 | self.assertEqual(gregorian.from_jd(self.jd), fr.to_gregorian(*fr.from_jd(self.jd, method=4), method=4)) 268 | self.assertEqual(self.x, fr.to_jd(*fr.from_jd(self.x, method=4), method=4)) 269 | 270 | assert self.j == fr.to_jd(*fr.from_jd(self.j, method='continuous'), method=4) 271 | 272 | def test_french_republican_famous_dates(self): 273 | self.assertEqual(gregorian.to_jd(1793, 9, 22), fr.to_jd(2, 1, 1)) 274 | 275 | # 9 Thermidor II 276 | self.assertEqual(gregorian.to_jd(1794, 7, 27), fr.to_jd(2, 11, 9)) 277 | 278 | # 18 Brumaire An VIII 279 | assert gregorian.to_jd(1799, 11, 9) == fr.to_jd(8, 2, 18) 280 | 281 | assert fr.to_jd(2, 9, 22) == gregorian.to_jd(1794, 6, 10) 282 | assert fr.to_jd(4, 1, 13) == gregorian.to_jd(1795, 10, 5) 283 | assert fr.to_gregorian(5, 12, 18) == (1797, 9, 4) 284 | assert fr.to_jd(6, 8, 22) == gregorian.to_jd(1798, 5, 11) 285 | 286 | assert (2, 9, 22) == fr.from_gregorian(1794, 6, 10) 287 | assert (4, 1, 13) == fr.from_gregorian(1795, 10, 5) 288 | assert (5, 12, 18) == fr.from_gregorian(1797, 9, 4) 289 | assert (6, 8, 22) == fr.from_gregorian(1798, 5, 11) 290 | 291 | # Coup of 30 Prairial VII 292 | self.assertEqual(fr.to_gregorian(7, 9, 30), (1799, 6, 18)) 293 | 294 | def test_premier_da_la_annee(self): 295 | # Autumnal equinoxes in 1793 and 1794 296 | e0 = 2376204.5 297 | e1 = 2376569.5 298 | self.assertEqual(fr.premier_da_la_annee(e1 - 10), e0) 299 | self.assertEqual(fr.premier_da_la_annee(e1 + 100), e1) 300 | 301 | def test_french_republican_months(self): 302 | self.assertEqual(fr.MOIS[0], "Vendémiaire") 303 | self.assertEqual(fr.MOIS[1], "Brumaire") 304 | self.assertEqual(fr.MOIS[2], 'Frimaire') 305 | self.assertEqual(fr.MOIS[3], 'Nivôse') 306 | self.assertEqual(fr.MOIS[4], 'Pluviôse') 307 | self.assertEqual(fr.MOIS[5], 'Ventôse') 308 | self.assertEqual(fr.MOIS[6], 'Germinal') 309 | self.assertEqual(fr.MOIS[7], 'Floréal') 310 | self.assertEqual(fr.MOIS[8], 'Prairial') 311 | self.assertEqual(fr.MOIS[9], 'Messidor') 312 | self.assertEqual(fr.MOIS[10], 'Thermidor') 313 | self.assertEqual(fr.MOIS[12], 'Sansculottides') 314 | self.assertEqual(fr.MOIS[11], "Fructidor") 315 | 316 | def test_french_republican_schematic_error(self): 317 | self.assertRaises(ValueError, fr.from_jd, self.jd, method=400) 318 | self.assertRaises(ValueError, fr.from_jd, self.j, method=-1) 319 | 320 | def test_french_republican_names(self): 321 | self.assertEqual(fr.day_name(1, 1), "Raisin") 322 | self.assertEqual(fr.day_name(2, 1), "Pomme") 323 | self.assertEqual(fr.day_name(4, 18), "Pierre à chaux") 324 | self.assertEqual(fr.day_name(12, 15), "Truite") 325 | self.assertEqual(fr.day_name(13, 1), "La Fête de la Vertu") 326 | -------------------------------------------------------------------------------- /src/convertdate/data/positivist.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | DAY_NAMES = ( 4 | "Prometheus", 5 | "Hercules", 6 | "Orpheus", 7 | "Ulysses", 8 | "Lycurgus", 9 | "Romulus", 10 | "Numa", 11 | "Belus", 12 | "Sesostris", 13 | "Menu", 14 | "Cyrus", 15 | "Zoroaster", 16 | "The Druids", 17 | "Buddha", 18 | "Fuxi", 19 | "Laozi", 20 | "Mencius", 21 | "Theocrats of Tibet", 22 | "Theocrats of Japan", 23 | "Manco Cápac", 24 | "Confucius", 25 | "Abraham", 26 | "Samuel", 27 | "Solomon", 28 | "Isaiah", 29 | "St. John the Baptist", 30 | "Haroun-al-Raschid", 31 | "Muhammad", 32 | "Hesiod", 33 | "Tyrtéus", 34 | "Anacreon", 35 | "Pindar", 36 | "Sophocles", 37 | "Theocritus", 38 | "Aeschylus", 39 | "Scopas", 40 | "Zeuxis", 41 | "Ictinus", 42 | "Praxiteles", 43 | "Lysippus", 44 | "Apelles", 45 | "Phidias", 46 | "Aesop", 47 | "Plautus", 48 | "Terence", 49 | "Phaedrus", 50 | "Juvenal", 51 | "Lucian", 52 | "Aristophanes", 53 | "Ennius", 54 | "Lucretius", 55 | "Horace", 56 | "Tibullus", 57 | "Ovid", 58 | "Lucan", 59 | "Virgil", 60 | "Anaximander", 61 | "Anaximenes", 62 | "Heraclitus", 63 | "Anaxagoras", 64 | "Democritus", 65 | "Herodotus", 66 | "Thales", 67 | "Solon", 68 | "Xenophanes", 69 | "Empodocles", 70 | "Thucydides", 71 | "Archytas", 72 | "Apollonius of Tyana", 73 | "Pythagoras", 74 | "Aristippus", 75 | "Antisthenes", 76 | "Zeno", 77 | "Cicero", 78 | "Epictetus", 79 | "Tacitus", 80 | "Socrates", 81 | "Xenocrates", 82 | "Philo of Alexandria", 83 | "St. John the Evangelist", 84 | "St. Justin", 85 | "St. Clement of Alexandria", 86 | "Origen", 87 | "Plato", 88 | "Theophrastus", 89 | "Herophilus", 90 | "Erasistratus", 91 | "Celsus", 92 | "Galen", 93 | "Avicenna", 94 | "Hippocrates", 95 | "Euclid", 96 | "Aristéus", 97 | "Theodisius of Bithynia", 98 | "Hero", 99 | "Pappus", 100 | "Diophantus", 101 | "Apollonius", 102 | "Eudoxus", 103 | "Pytheas", 104 | "Aristarchus", 105 | "Eratos-thenes", 106 | "Ptolemy", 107 | "Albategnius", 108 | "Hipparchus", 109 | "Varro", 110 | "Columella", 111 | "Vitruvius", 112 | "Strabo", 113 | "Frontinus", 114 | "Plutarch", 115 | "Pliny the Elder", 116 | "Miltiades", 117 | "Leonidas", 118 | "Aristides", 119 | "Cimon", 120 | "Xenophon", 121 | "Phocion", 122 | "Themistocles", 123 | "Pericles", 124 | "Philip", 125 | "Demosthenes", 126 | "PtolemyLagus", 127 | "Philopoemen", 128 | "Polybius", 129 | "Alexander", 130 | "JuniusBrutus", 131 | "Camillus", 132 | "Fabricius", 133 | "Hannibal", 134 | "Paulus Aemilius", 135 | "Marius", 136 | "Scipio", 137 | "Augustus", 138 | "Vespasian", 139 | "Hadrian", 140 | "Antonius", 141 | "Papinian", 142 | "Alexander Severus", 143 | "Trajan", 144 | "St. Luke", 145 | "St. Cyprian", 146 | "St. Athanasius", 147 | "St. Jerome", 148 | "St. Ambrose", 149 | "St. Monica", 150 | "St. Augustine", 151 | "Constantine", 152 | "Theodosius", 153 | "St. Chrysostom", 154 | "St. Genevieve of Paris", 155 | "St. Pulcheria", 156 | "St. Gregory the Great", 157 | "Hildebrand", 158 | "St. Benedict", 159 | "St. Boniface", 160 | "St. Isidore of Seville", 161 | "Lanfranc", 162 | "Heloise", 163 | "the architects of the Middle Ages", 164 | "St. Bernard", 165 | "St. Francis Xavier", 166 | "St. Charles Borromeo", 167 | "St. Theresa", 168 | "St. Vincent de Paul", 169 | "Bourdaloue", 170 | "William Penn", 171 | "Bossuet", 172 | "Theodoric the Great", 173 | "Pelayo", 174 | "Otho the Great", 175 | "St. Henry", 176 | "Villers", 177 | "Don John of Austria", 178 | "Alfred", 179 | "Charles Martel", 180 | "El Cid", 181 | "Richard I", 182 | "Joan of Arc", 183 | "Albuquerque", 184 | "Bayard", 185 | "Godfrey", 186 | "St. Leo the Great", 187 | "Gerbert", 188 | "Peter the Hermit", 189 | "Suger", 190 | "Alexander III", 191 | "St. Francis of Assisi", 192 | "Innocent III", 193 | "St. Clotilde", 194 | "St. Bathilda", 195 | "St. Stephen of Hungary", 196 | "St. Elizabeth of Hungary", 197 | "Blanche of Castille", 198 | "St. Ferdinand III", 199 | "St. Louis", 200 | "the Troubadours", 201 | "Boccaccio", 202 | "Rabelais", 203 | "Cervantes", 204 | "La Fontain", 205 | "Defoe", 206 | "Aristo", 207 | "Leonardo da Vinci", 208 | "Michael Angelo", 209 | "Holbein", 210 | "Poussin", 211 | "Velásquez", 212 | "Teniers", 213 | "Raphael", 214 | "Froissart", 215 | "Camoens", 216 | "The Spanish Romancers", 217 | "Chateaubriand", 218 | "Walter Scott", 219 | "Manzoni", 220 | "Tasso", 221 | "Petrarca", 222 | "Thomas of Kempis", 223 | "Mademoiselle de Lafayette", 224 | "Fénélon", 225 | "Klopstock", 226 | "Byron", 227 | "Milton", 228 | "Marco Polo", 229 | "Jacques Coeur", 230 | "Vasco de Gama", 231 | "Napier", 232 | "Lacaille", 233 | "Cook", 234 | "Columbus", 235 | "Benvenuto Cellini", 236 | "Amontons", 237 | "Harrison", 238 | "Dollond", 239 | "Arkwright", 240 | "Conté", 241 | "Vaucanson", 242 | "Stevin", 243 | "Mariotte", 244 | "Papin", 245 | "Black", 246 | "Jouffroy", 247 | "Dalton", 248 | "Watt", 249 | "Bernard de Palissy", 250 | "Guglielmini", 251 | "Duhamel du Monceau", 252 | "Saussure", 253 | "Coulomb", 254 | "Carnot", 255 | "Montgolfier", 256 | "Lope de Vega", 257 | "Moreto", 258 | "Rojas", 259 | "Otway", 260 | "Lessing", 261 | "Goethe", 262 | "Calderon", 263 | "Tirso", 264 | "Vondel", 265 | "Racine", 266 | "Voltaire", 267 | "Metastasio", 268 | "Schiller", 269 | "Corneille", 270 | "Almarcon", 271 | "Mademoiselle de Motteville", 272 | "Mademoiselle de Sévigné", 273 | "Lesage", 274 | "Mademoiselle deStaal", 275 | "Fielding", 276 | "Moliere", 277 | "Pergolese", 278 | "Sacchini", 279 | "Gluck", 280 | "Beethoven", 281 | "Rossini", 282 | "Bellini", 283 | "Mozart", 284 | "Albertus Magnus", 285 | "Roger Bacon", 286 | "St. Bonaventura", 287 | "Ramus", 288 | "Montaigne", 289 | "Campanella", 290 | "St. Thomas Aquinas", 291 | "Hobbes", 292 | "Pascal", 293 | "Locke", 294 | "Vauvenargues", 295 | "Diderot", 296 | "Cabanis", 297 | "Lordbacon", 298 | "Grotius", 299 | "Fontenelle", 300 | "Vico", 301 | "Fréret", 302 | "Montesquieu", 303 | "Buffon", 304 | "Leibnitz", 305 | "Robertson", 306 | "Adam Smith", 307 | "Kant", 308 | "Condercet", 309 | "Joseph de Maistre", 310 | "Hegel", 311 | "Hume", 312 | "Marie de Molina", 313 | "Cosimo de Medici the Elder", 314 | "Philippe de Comines", 315 | "Isabella of Castille", 316 | "Charles V", 317 | "Henry Iv", 318 | "Louis Xi", 319 | "L'Hôpital", 320 | "Barneveldt", 321 | "Gustavus Adolphus", 322 | "De Witt", 323 | "Ruyter", 324 | "William III", 325 | "William the Silent", 326 | "Ximenes", 327 | "Sully", 328 | "Mazarin", 329 | "Colbert", 330 | "D'Aranda", 331 | "Turgot", 332 | "Richelieu", 333 | "Sidney", 334 | "Franklin", 335 | "Washington", 336 | "Jefferson", 337 | "Bolivar", 338 | "Francia", 339 | "Cromwell", 340 | "Copernicus", 341 | "Kepler", 342 | "Huyghens", 343 | "James Bernouilli", 344 | "Bradley", 345 | "Volta", 346 | "Galileo", 347 | "Vieta", 348 | "Wallis", 349 | "Clairaut", 350 | "Euler", 351 | "D'Alembert", 352 | "Lagrange", 353 | "Newton", 354 | "Bergmann", 355 | "Priestley", 356 | "Cavendish", 357 | "Guyton Morveau", 358 | "Berthollet", 359 | "Berzelius", 360 | "Lavoisier", 361 | "Harvey", 362 | "Boerhaave", 363 | "Linnaeus", 364 | "Haller", 365 | "Lamarck", 366 | "Broussais", 367 | "Gall", 368 | "the Dead", 369 | "Sainted Women", 370 | ) 371 | 372 | leap_day_replacements = { 373 | 0: "Cadmus", 374 | 1: "Theseus", 375 | 2: "Tiresias", 376 | 7: "Semiramus", 377 | 12: "Ossian", 378 | 19: "Tamehameha", 379 | 21: "Joseph", 380 | 23: "David", 381 | 26: "Abderrahman", 382 | 29: "Sappho", 383 | 32: "Euripides", 384 | 33: "Longus", 385 | 42: "Pilpay", 386 | 44: "Menander", 387 | 60: "Leucippus", 388 | 67: "Philolaus", 389 | 73: "Pliny the Younger", 390 | 74: "Arrian", 391 | 80: "St. Irenaeus", 392 | 82: "Tertullian", 393 | 89: "Averrhoes", 394 | 94: "Ctesibius", 395 | 98: "Aratus", 396 | 99: "Nearchus", 397 | 100: "Berosus", 398 | 101: "Sosigenes", 399 | 103: "Nasreddin", 400 | 117: "Epaminondas", 401 | 127: "Cincinnatus", 402 | 128: "Regulus", 403 | 131: "the Gracchi", 404 | 133: "Maecenas", 405 | 134: "Titus", 406 | 135: "Nerva", 407 | 136: "Marcus Aurelius", 408 | 137: "Ulpian", 409 | 138: "Aetius", 410 | 140: "St. James", 411 | 149: "St. Basil", 412 | 151: "Marcian", 413 | 154: "St. Anthony", 414 | 155: "St. Austin", 415 | 156: "St. Bruno", 416 | 157: "St. Anselm", 417 | 158: "Beatrice", 418 | 159: "St. Benezet", 419 | 161: "Ignatius Loyola", 420 | 162: "Fredrick Borromeo", 421 | 163: "St. Catharine of Siena", 422 | 164: "Abbé de l'Epée", 423 | 165: "Claude Fleury", 424 | 166: "George Fox", 425 | 170: "Henry the Fowler", 426 | 172: "La Valette", 427 | 173: "John Sobieski", 428 | 176: "Tancred", 429 | 177: "Saladin", 430 | 178: "Marina", 431 | 179: "Sir Walter Raleigh", 432 | 182: "Leo IV", 433 | 183: "Peter Damian", 434 | 185: "St. Eligius", 435 | 186: "Becket", 436 | 187: "St. Dominic", 437 | 190: "St. Mathilda of Tuscany", 438 | 191: "Mathias Corvinus", 439 | 194: "Alfonso X", 440 | 197: "Chaucer", 441 | 198: "Swift", 442 | 200: "Burns", 443 | 201: "Goldsmith", 444 | 203: "Titian", 445 | 204: "Paul Veronese", 446 | 205: "Rembrandt", 447 | 206: "Lesueuer", 448 | 207: "Murillo", 449 | 208: "Rubens", 450 | 210: "Joinville", 451 | 211: "Spenser", 452 | 214: "James Fenimore Cooper", 453 | 218: "Louis of Granada & Bunyan", 454 | 219: "Mademoiselle de Staël", 455 | 220: "St. Francis of Sales", 456 | 221: "Gessner", 457 | 222: "Élisa Mercœur & Shelly", 458 | 224: "Chardin", 459 | 225: "Gresham", 460 | 226: "Magellan", 461 | 227: "Briggs", 462 | 228: "Delambre", 463 | 229: "Tasman", 464 | 232: "Wheatstone", 465 | 233: "Pierre Leroy", 466 | 234: "Graham", 467 | 235: "Jacquard", 468 | 238: "Torricelli", 469 | 239: "Boyle", 470 | 240: "Worcester", 471 | 242: "Fulton", 472 | 243: "Thilorier", 473 | 246: "Riquet", 474 | 247: "Bourgelat", 475 | 248: "Bouguer", 476 | 249: "Borda", 477 | 250: "Vauban", 478 | 252: "Montalvan", 479 | 253: "Guillem de Castro", 480 | 254: "Guevara", 481 | 263: "Alfieri", 482 | 267: "Mademoiselle Roland", 483 | 268: "Lady Montagu", 484 | 269: "Sterne", 485 | 270: "Miss Edgeworth", 486 | 271: "Richardson", 487 | 273: "Palestrina", 488 | 274: "Grétry", 489 | 275: "Lully", 490 | 276: "Handel", 491 | 277: "Weber", 492 | 278: "Donizeti", 493 | 280: "John of Salisbury", 494 | 281: "Raymond Lully", 495 | 282: "Joachim", 496 | 283: "Nicholas of Cusa", 497 | 284: "Erasmus", 498 | 285: "Sir Thomas More", 499 | 287: "Spinoza", 500 | 288: "Giordano Bruno", 501 | 289: "Malebranche", 502 | 290: "Mademoiselle de Lambert", 503 | 291: "Duclos", 504 | 292: "George Leroy", 505 | 294: "Cujas", 506 | 295: "Maupertuis", 507 | 296: "Herder", 508 | 297: "Wincklemann", 509 | 298: "D'Aguesseau", 510 | 299: "Oken", 511 | 301: "Gibbon", 512 | 302: "Dunoyer", 513 | 303: "Fichte", 514 | 304: "Ferguson", 515 | 305: "Bonald", 516 | 306: "Sophie Germain", 517 | 310: "Guicciardini", 518 | 312: "Sixtus V", 519 | 323: "Oxenstiern", 520 | 324: "Walpole", 521 | 325: "Louis XIV", 522 | 326: "Pombal", 523 | 327: "Campomanes", 524 | 329: "Lambert", 525 | 330: "Hampden", 526 | 331: "Kosciusko", 527 | 332: "Madison", 528 | 333: "Toussaint L'Ouverture", 529 | 336: "Tycho Brahe", 530 | 337: "Halley", 531 | 338: "Varignon", 532 | 339: "John Bernouilli", 533 | 340: "Römer", 534 | 341: "Sauveur", 535 | 343: "Harriot", 536 | 344: "Fermat", 537 | 345: "Poinsot", 538 | 346: "Monge", 539 | 347: "Daniel Bernouilli", 540 | 348: "Joseph Fourier", 541 | 350: "Scheele", 542 | 351: "Davy", 543 | 353: "Geoffroy", 544 | 355: "Ritter", 545 | 357: "Charles Bell", 546 | 358: "Stahl & Barthez", 547 | 359: "Bernard de Jussieu", 548 | 360: "Félix Vicq-d'Azyr", 549 | 361: "Blainville", 550 | 362: "Morgagni", 551 | } 552 | 553 | DAY_NAMES_LEAP = [leap_day_replacements.get(i, x) for i, x in enumerate(DAY_NAMES)] 554 | 555 | FESTIVALS = { 556 | (1, 1): "the Great Being", 557 | (1, 7): "religion", 558 | (1, 14): "history", 559 | (1, 21): "nation", 560 | (1, 28): "community", 561 | (2, 7): "complete marriage", 562 | (2, 14): "chaste marriage", 563 | (2, 21): "unequal marriage", 564 | (2, 28): "subjective marriage", 565 | (3, 7): "natural fatherhood", 566 | (3, 14): "artificial fatherhood", 567 | (3, 21): "spiritual fatherhood", 568 | (3, 28): "temporal fatherhood", 569 | (4, 7): "natural filiation", 570 | (4, 14): "artificial filiation", 571 | (4, 21): "spiritual filiation", 572 | (4, 28): "temporal filiation", 573 | (5, 7): "natural brotherhood", 574 | (5, 14): "artificial brotherhood", 575 | (5, 21): "spiritual brotherhood", 576 | (5, 28): "temporal brotherhood", 577 | (6, 7): "complete permanent domesticity", 578 | (6, 14): "incomplete permanent domesticity", 579 | (6, 21): "complete passing domesticity", 580 | (6, 28): "incomplete passing domesticity", 581 | (7, 7): "animal gods", 582 | (7, 14): "fire gods", 583 | (7, 21): "sun gods", 584 | (7, 28): "war gods", 585 | (8, 7): "castes", 586 | (8, 14): "polytheistic arts", 587 | (8, 21): "polytheistic theory", 588 | (8, 28): "polytheistic society", 589 | (9, 7): "monotheistic theology", 590 | (9, 14): "Catholocism", 591 | (9, 21): "Islam", 592 | (9, 28): "metaphysics", 593 | (10, 7): "the mother", 594 | (10, 14): "the wife", 595 | (10, 21): "the daughter", 596 | (10, 28): "the sister", 597 | (11, 7): "artistic intellectuals", 598 | (11, 14): "scientific intellectuals", 599 | (11, 21): "secondary intellectual providence", 600 | (11, 28): "the elderly", 601 | (12, 7): "the bank", 602 | (12, 14): "commerce", 603 | (12, 21): "manufacturing", 604 | (12, 28): "agriculture", 605 | (13, 7): "inventors", 606 | (13, 14): "emotional labor", 607 | (13, 21): "meditation", 608 | (13, 28): "passive labor", 609 | (14, 1): "the Dead", 610 | (14, 2): "Sainted Women", 611 | } 612 | --------------------------------------------------------------------------------