├── MANIFEST.in ├── docs ├── _config.yml ├── index.md ├── advanced.md ├── basic.md └── iso-registry.md ├── .gitignore ├── workalendar ├── exceptions.py ├── __init__.py ├── usa │ ├── oregon.py │ ├── washington.py │ ├── massachusetts.py │ ├── maine.py │ ├── minnesota.py │ ├── wyoming.py │ ├── connecticut.py │ ├── new_jersey.py │ ├── new_york.py │ ├── utah.py │ ├── idaho.py │ ├── colorado.py │ ├── iowa.py │ ├── oklahoma.py │ ├── tennessee.py │ ├── california.py │ ├── illinois.py │ ├── new_hampshire.py │ ├── pennsylvania.py │ ├── missouri.py │ ├── arizona.py │ ├── louisiana.py │ ├── new_mexico.py │ ├── ohio.py │ ├── district_columbia.py │ ├── florida.py │ ├── south_dakota.py │ ├── arkansas.py │ ├── delaware.py │ ├── wisconsin.py │ ├── south_carolina.py │ ├── kentucky.py │ ├── north_dakota.py │ ├── nebraska.py │ ├── maryland.py │ ├── rhode_island.py │ ├── nevada.py │ ├── montana.py │ ├── alaska.py │ ├── vermont.py │ ├── mississippi.py │ ├── kansas.py │ ├── hawaii.py │ ├── michigan.py │ ├── virginia.py │ ├── west_virginia.py │ ├── alabama.py │ ├── north_carolina.py │ ├── georgia.py │ ├── indiana.py │ ├── __init__.py │ └── texas.py ├── tests │ ├── __init__.py │ ├── test_global_registry.py │ ├── test_registry_africa.py │ ├── test_registry_asia.py │ ├── test_registry_oceania.py │ ├── test_registry.py │ ├── test_registry_europe.py │ ├── test_registry_america.py │ ├── test_registry_usa.py │ ├── test_america.py │ └── test_oceania.py ├── asia │ ├── __init__.py │ ├── qatar.py │ ├── south_korea.py │ ├── taiwan.py │ ├── japan.py │ ├── singapore.py │ ├── hong_kong.py │ └── malaysia.py ├── europe │ ├── european_central_bank.py │ ├── belgium.py │ ├── luxembourg.py │ ├── russia.py │ ├── italy.py │ ├── norway.py │ ├── poland.py │ ├── france.py │ ├── estonia.py │ ├── austria.py │ ├── greece.py │ ├── croatia.py │ ├── spain.py │ ├── hungary.py │ ├── slovakia.py │ ├── malta.py │ ├── czech_republic.py │ ├── cyprus.py │ ├── bulgaria.py │ ├── denmark.py │ ├── portugal.py │ ├── slovenia.py │ ├── lithuania.py │ ├── netherlands.py │ ├── iceland.py │ ├── switzerland.py │ ├── finland.py │ ├── romania.py │ ├── latvia.py │ ├── sweden.py │ ├── ireland.py │ ├── __init__.py │ ├── united_kingdom.py │ └── germany.py ├── africa │ ├── __init__.py │ ├── madagascar.py │ ├── sao_tome.py │ ├── algeria.py │ ├── ivory_coast.py │ ├── benin.py │ ├── angola.py │ └── south_africa.py ├── oceania │ ├── __init__.py │ └── marshall_islands.py ├── america │ ├── panama.py │ ├── chile.py │ ├── mexico.py │ ├── __init__.py │ └── colombia.py └── registry.py ├── tox.ini ├── .github └── PULL_REQUEST_TEMPLATE.md ├── .travis.yml ├── LICENSE ├── Makefile ├── setup.py ├── README.rst └── CONTRIBUTING.rst /MANIFEST.in: -------------------------------------------------------------------------------- 1 | include README.rst 2 | -------------------------------------------------------------------------------- /docs/_config.yml: -------------------------------------------------------------------------------- 1 | theme: jekyll-theme-hacker 2 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.pyc 2 | *.egg-info 3 | dist/ 4 | build/ 5 | .coverage 6 | .tox/ 7 | # py.test cache 8 | .cache/ 9 | .pytest-cache/ 10 | .pytest_cache/ 11 | -------------------------------------------------------------------------------- /workalendar/exceptions.py: -------------------------------------------------------------------------------- 1 | """ 2 | Core Workalendar Exceptions 3 | """ 4 | 5 | 6 | class CalendarError(Exception): 7 | """ 8 | Base Calendar Error 9 | """ 10 | -------------------------------------------------------------------------------- /workalendar/__init__.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | import pkg_resources 3 | 4 | 5 | #: Module version, as defined in PEP-0396. 6 | __version__ = pkg_resources.get_distribution(__package__).version 7 | -------------------------------------------------------------------------------- /workalendar/usa/oregon.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | from __future__ import (absolute_import, division, print_function, 3 | unicode_literals) 4 | 5 | from ..registry import iso_register 6 | from .core import UnitedStates 7 | 8 | 9 | @iso_register('US-OR') 10 | class Oregon(UnitedStates): 11 | """Oregon""" 12 | include_columbus_day = False 13 | -------------------------------------------------------------------------------- /workalendar/usa/washington.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | from __future__ import (absolute_import, division, print_function, 3 | unicode_literals) 4 | 5 | from ..registry import iso_register 6 | from .core import UnitedStates 7 | 8 | 9 | @iso_register('US-WA') 10 | class Washington(UnitedStates): 11 | """Washington""" 12 | include_columbus_day = False 13 | -------------------------------------------------------------------------------- /workalendar/tests/__init__.py: -------------------------------------------------------------------------------- 1 | import warnings 2 | from datetime import date 3 | from unittest import TestCase 4 | 5 | from workalendar.core import Calendar 6 | 7 | 8 | class GenericCalendarTest(TestCase): 9 | cal_class = Calendar 10 | 11 | def setUp(self): 12 | warnings.simplefilter("ignore") 13 | self.year = date.today().year 14 | self.cal = self.cal_class() 15 | -------------------------------------------------------------------------------- /workalendar/usa/massachusetts.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | from __future__ import (absolute_import, division, print_function, 3 | unicode_literals) 4 | 5 | from .core import UnitedStates 6 | from ..registry import iso_register 7 | 8 | 9 | @iso_register('US-MA') 10 | class Massachusetts(UnitedStates): 11 | """Massachusetts""" 12 | include_patriots_day = True 13 | -------------------------------------------------------------------------------- /workalendar/usa/maine.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | from __future__ import (absolute_import, division, print_function, 3 | unicode_literals) 4 | 5 | from .core import UnitedStates 6 | from ..registry import iso_register 7 | 8 | 9 | @iso_register('US-ME') 10 | class Maine(UnitedStates): 11 | """Maine""" 12 | include_thanksgiving_friday = True 13 | include_patriots_day = True 14 | -------------------------------------------------------------------------------- /workalendar/usa/minnesota.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | from __future__ import (absolute_import, division, print_function, 3 | unicode_literals) 4 | 5 | from ..registry import iso_register 6 | from .core import UnitedStates 7 | 8 | 9 | @iso_register('US-MN') 10 | class Minnesota(UnitedStates): 11 | """Minnesota""" 12 | include_thanksgiving_friday = True 13 | include_columbus_day = False 14 | -------------------------------------------------------------------------------- /workalendar/usa/wyoming.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | from __future__ import (absolute_import, division, print_function, 3 | unicode_literals) 4 | 5 | from ..registry import iso_register 6 | from .core import UnitedStates 7 | 8 | 9 | @iso_register('US-WY') 10 | class Wyoming(UnitedStates): 11 | """Wyoming""" 12 | martin_luther_king_label = "Martin Luther King, Jr. / Wyoming Equality Day" 13 | -------------------------------------------------------------------------------- /workalendar/usa/connecticut.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | from __future__ import (absolute_import, division, print_function, 3 | unicode_literals) 4 | 5 | from .core import UnitedStates 6 | from ..registry import iso_register 7 | 8 | 9 | @iso_register('US-CT') 10 | class Connecticut(UnitedStates): 11 | """Connecticut""" 12 | include_good_friday = True 13 | include_lincoln_birthday = True 14 | -------------------------------------------------------------------------------- /workalendar/usa/new_jersey.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | from __future__ import (absolute_import, division, print_function, 3 | unicode_literals) 4 | 5 | from ..registry import iso_register 6 | from .core import UnitedStates 7 | 8 | 9 | @iso_register('US-NJ') 10 | class NewJersey(UnitedStates): 11 | """New Jersey""" 12 | include_good_friday = True 13 | include_election_day_every_year = True 14 | -------------------------------------------------------------------------------- /workalendar/usa/new_york.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | from __future__ import (absolute_import, division, print_function, 3 | unicode_literals) 4 | 5 | from ..registry import iso_register 6 | from .core import UnitedStates 7 | 8 | 9 | @iso_register('US-NY') 10 | class NewYork(UnitedStates): 11 | """New York""" 12 | include_lincoln_birthday = True 13 | include_election_day_every_year = True 14 | -------------------------------------------------------------------------------- /workalendar/usa/utah.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | from __future__ import (absolute_import, division, print_function, 3 | unicode_literals) 4 | 5 | from ..registry import iso_register 6 | from .core import UnitedStates 7 | 8 | 9 | @iso_register('US-UT') 10 | class Utah(UnitedStates): 11 | """Utah""" 12 | FIXED_HOLIDAYS = UnitedStates.FIXED_HOLIDAYS + ( 13 | (7, 24, "Pioneer Day"), 14 | ) 15 | -------------------------------------------------------------------------------- /workalendar/usa/idaho.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | from __future__ import (absolute_import, division, print_function, 3 | unicode_literals) 4 | 5 | from .core import UnitedStates 6 | from ..registry import iso_register 7 | 8 | 9 | @iso_register('US-ID') 10 | class Idaho(UnitedStates): 11 | """Idaho""" 12 | martin_luther_king_label = ( 13 | "Martin Luther King Jr. / Idaho Human Rights Day" 14 | ) 15 | -------------------------------------------------------------------------------- /workalendar/usa/colorado.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | from __future__ import (absolute_import, division, print_function, 3 | unicode_literals) 4 | 5 | from .core import UnitedStates 6 | from ..registry import iso_register 7 | 8 | 9 | @iso_register('US-CO') 10 | class Colorado(UnitedStates): 11 | """Colorado""" 12 | # Colorado has only federal state holidays. 13 | # NOTE: Cesar Chavez Day is an optional holiday 14 | -------------------------------------------------------------------------------- /workalendar/usa/iowa.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | from __future__ import (absolute_import, division, print_function, 3 | unicode_literals) 4 | 5 | from ..registry import iso_register 6 | from .core import UnitedStates 7 | 8 | 9 | @iso_register('US-IA') 10 | class Iowa(UnitedStates): 11 | """Iowa""" 12 | include_thanksgiving_friday = True 13 | include_columbus_day = False 14 | include_federal_presidents_day = False 15 | -------------------------------------------------------------------------------- /workalendar/usa/oklahoma.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | from __future__ import (absolute_import, division, print_function, 3 | unicode_literals) 4 | 5 | from ..registry import iso_register 6 | from .core import UnitedStates 7 | 8 | 9 | @iso_register('US-OK') 10 | class Oklahoma(UnitedStates): 11 | """Oklahoma""" 12 | include_thanksgiving_friday = True 13 | include_boxing_day = True 14 | include_columbus_day = False 15 | -------------------------------------------------------------------------------- /workalendar/asia/__init__.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | from .hong_kong import HongKong 3 | from .japan import Japan 4 | from .malaysia import Malaysia 5 | from .qatar import Qatar 6 | from .singapore import Singapore 7 | from .south_korea import SouthKorea 8 | from .taiwan import Taiwan 9 | 10 | 11 | __all__ = ( 12 | 'HongKong', 13 | 'Japan', 14 | 'Malaysia', 15 | 'Qatar', 16 | 'Singapore', 17 | 'SouthKorea', 18 | 'Taiwan', 19 | ) 20 | -------------------------------------------------------------------------------- /workalendar/europe/european_central_bank.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | from workalendar.core import WesternCalendar, ChristianMixin 3 | 4 | 5 | class EuropeanCentralBank(WesternCalendar, ChristianMixin): 6 | "European Central Bank" 7 | FIXED_HOLIDAYS = WesternCalendar.FIXED_HOLIDAYS + ( 8 | (5, 1, "Labour Day"), 9 | (12, 26, "St. Stephen's Day"), 10 | ) 11 | 12 | include_good_friday = True 13 | include_easter_monday = True 14 | -------------------------------------------------------------------------------- /workalendar/usa/tennessee.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | from __future__ import (absolute_import, division, print_function, 3 | unicode_literals) 4 | 5 | from ..registry import iso_register 6 | from .core import UnitedStates 7 | 8 | 9 | @iso_register('US-TN') 10 | class Tennessee(UnitedStates): 11 | """Tennessee""" 12 | include_columbus_day = False 13 | 14 | include_good_friday = True 15 | include_christmas_eve = True 16 | -------------------------------------------------------------------------------- /workalendar/usa/california.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | from __future__ import (absolute_import, division, print_function, 3 | unicode_literals) 4 | 5 | from .core import UnitedStates 6 | from ..registry import iso_register 7 | 8 | 9 | @iso_register('US-CA') 10 | class California(UnitedStates): 11 | """California""" 12 | include_thanksgiving_friday = True 13 | include_cesar_chavez_day = True 14 | include_columbus_day = False 15 | -------------------------------------------------------------------------------- /workalendar/usa/illinois.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | from __future__ import (absolute_import, division, print_function, 3 | unicode_literals) 4 | 5 | from .core import UnitedStates 6 | from ..registry import iso_register 7 | 8 | 9 | @iso_register('US-IL') 10 | class Illinois(UnitedStates): 11 | """Illinois""" 12 | include_thanksgiving_friday = True 13 | include_lincoln_birthday = True 14 | include_election_day_even = True 15 | -------------------------------------------------------------------------------- /workalendar/usa/new_hampshire.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | from __future__ import (absolute_import, division, print_function, 3 | unicode_literals) 4 | 5 | from ..registry import iso_register 6 | from .core import UnitedStates 7 | 8 | 9 | @iso_register('US-NH') 10 | class NewHampshire(UnitedStates): 11 | """New Hampshire""" 12 | include_thanksgiving_friday = True 13 | martin_luther_king_label = "Martin Luther King, Jr. Civil Rights Day" 14 | -------------------------------------------------------------------------------- /workalendar/usa/pennsylvania.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | from __future__ import (absolute_import, division, print_function, 3 | unicode_literals) 4 | 5 | from ..registry import iso_register 6 | from .core import UnitedStates 7 | 8 | 9 | @iso_register('US-PA') 10 | class Pennsylvania(UnitedStates): 11 | """Pennsylvania""" 12 | include_good_friday = True 13 | include_thanksgiving_friday = True 14 | include_election_day_every_year = True 15 | -------------------------------------------------------------------------------- /workalendar/usa/missouri.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | from __future__ import (absolute_import, division, print_function, 3 | unicode_literals) 4 | 5 | from .core import UnitedStates 6 | from ..registry import iso_register 7 | 8 | 9 | @iso_register('US-MO') 10 | class Missouri(UnitedStates): 11 | """Missouri""" 12 | FIXED_HOLIDAYS = UnitedStates.FIXED_HOLIDAYS + ( 13 | (5, 8, "Truman Day"), 14 | ) 15 | include_lincoln_birthday = True 16 | -------------------------------------------------------------------------------- /workalendar/usa/arizona.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | from __future__ import (absolute_import, division, print_function, 3 | unicode_literals) 4 | 5 | from .core import UnitedStates 6 | from ..registry import iso_register 7 | 8 | 9 | @iso_register('US-AZ') 10 | class Arizona(UnitedStates): 11 | """Arizona""" 12 | martin_luther_king_label = "Dr. Martin Luther King Jr./Civil Rights Day" 13 | presidents_day_label = "Lincoln/Washington Presidents' Day" 14 | -------------------------------------------------------------------------------- /workalendar/usa/louisiana.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | from __future__ import (absolute_import, division, print_function, 3 | unicode_literals) 4 | 5 | from .core import UnitedStates 6 | from ..registry import iso_register 7 | 8 | 9 | @iso_register('US-LA') 10 | class Louisiana(UnitedStates): 11 | """Louisiana""" 12 | include_good_friday = True 13 | include_election_day_even = True 14 | include_columbus_day = False 15 | include_mardi_gras = True 16 | -------------------------------------------------------------------------------- /workalendar/usa/new_mexico.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | from __future__ import (absolute_import, division, print_function, 3 | unicode_literals) 4 | 5 | from ..registry import iso_register 6 | from .core import UnitedStates 7 | 8 | 9 | @iso_register('US-NM') 10 | class NewMexico(UnitedStates): 11 | """New Mexico""" 12 | include_thanksgiving_friday = True 13 | thanksgiving_friday_label = "Presidents' Day" 14 | include_federal_presidents_day = False 15 | -------------------------------------------------------------------------------- /workalendar/usa/ohio.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | from __future__ import (absolute_import, division, print_function, 3 | unicode_literals) 4 | 5 | from ..registry import iso_register 6 | from .core import UnitedStates 7 | 8 | 9 | @iso_register('US-OH') 10 | class Ohio(UnitedStates): 11 | """Ohio""" 12 | 13 | # NOTE: Ohio includes only Federal holidays. 14 | # The wikipedia page say it also includes Election Day, but no official 15 | # document confirms this. 16 | -------------------------------------------------------------------------------- /workalendar/africa/__init__.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | from .algeria import Algeria 4 | from .benin import Benin 5 | from .ivory_coast import IvoryCoast 6 | from .madagascar import Madagascar 7 | from .sao_tome import SaoTomeAndPrincipe 8 | from .south_africa import SouthAfrica 9 | from .angola import Angola 10 | 11 | 12 | __all__ = ( 13 | 'Algeria', 14 | 'Benin', 15 | 'IvoryCoast', 16 | 'Madagascar', 17 | 'SaoTomeAndPrincipe', 18 | 'SouthAfrica', 19 | 'Angola', 20 | ) 21 | -------------------------------------------------------------------------------- /workalendar/usa/district_columbia.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | from __future__ import (absolute_import, division, print_function, 3 | unicode_literals) 4 | 5 | from .core import UnitedStates 6 | from ..registry import iso_register 7 | 8 | 9 | @iso_register('US-DC') 10 | class DistrictOfColumbia(UnitedStates): 11 | "District of Columbia" 12 | include_inauguration_day = True 13 | FIXED_HOLIDAYS = UnitedStates.FIXED_HOLIDAYS + ( 14 | (4, 16, "Emancipation Day"), 15 | ) 16 | -------------------------------------------------------------------------------- /workalendar/usa/florida.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | from __future__ import (absolute_import, division, print_function, 3 | unicode_literals) 4 | 5 | from .core import UnitedStates 6 | from ..registry import iso_register 7 | 8 | 9 | @iso_register('US-FL') 10 | class Florida(UnitedStates): 11 | """Florida""" 12 | include_thanksgiving_friday = True 13 | thanksgiving_friday_label = "Friday after Thanksgiving" 14 | include_columbus_day = False 15 | include_federal_presidents_day = False 16 | -------------------------------------------------------------------------------- /workalendar/usa/south_dakota.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | from __future__ import (absolute_import, division, print_function, 3 | unicode_literals) 4 | 5 | from ..registry import iso_register 6 | from .core import UnitedStates 7 | 8 | 9 | @iso_register('US-SD') 10 | class SouthDakota(UnitedStates): 11 | """South Dakota""" 12 | columbus_day_label = "Native Americans Day" 13 | 14 | # NOTE: South Dakota has all federal holidays, except Columbus Day, 15 | # but it's renamed as "Native Americans Day" 16 | -------------------------------------------------------------------------------- /workalendar/usa/arkansas.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | from __future__ import (absolute_import, division, print_function, 3 | unicode_literals) 4 | 5 | from .core import UnitedStates 6 | from ..registry import iso_register 7 | 8 | 9 | @iso_register('US-AR') 10 | class Arkansas(UnitedStates): 11 | """Arkansas""" 12 | include_christmas_eve = True 13 | presidents_day_label = ("George Washington's Birthday" 14 | " and Daisy Gatson Bates Day") 15 | include_columbus_day = False 16 | -------------------------------------------------------------------------------- /workalendar/usa/delaware.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | from __future__ import (absolute_import, division, print_function, 3 | unicode_literals) 4 | 5 | from .core import UnitedStates 6 | from ..registry import iso_register 7 | 8 | 9 | @iso_register('US-DE') 10 | class Delaware(UnitedStates): 11 | """Delaware""" 12 | include_good_friday = True 13 | include_thanksgiving_friday = True 14 | include_federal_presidents_day = False 15 | include_columbus_day = False 16 | include_election_day_even = True 17 | -------------------------------------------------------------------------------- /workalendar/usa/wisconsin.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | from __future__ import (absolute_import, division, print_function, 3 | unicode_literals) 4 | 5 | from ..registry import iso_register 6 | from .core import UnitedStates 7 | 8 | 9 | @iso_register('US-WI') 10 | class Wisconsin(UnitedStates): 11 | """Wisconsin""" 12 | include_columbus_day = False 13 | include_federal_presidents_day = False 14 | include_christmas_eve = True 15 | FIXED_HOLIDAYS = UnitedStates.FIXED_HOLIDAYS + ( 16 | (12, 31, "New Years Eve"), 17 | ) 18 | -------------------------------------------------------------------------------- /tox.ini: -------------------------------------------------------------------------------- 1 | [tox] 2 | envlist = py27,flake8,py34,py35,py36,py37,py36-cov,py27-cov 3 | 4 | [testenv] 5 | deps = 6 | pytest 7 | cov: pytest-cov 8 | 9 | commands_pre = 10 | python setup.py develop 11 | python --version 12 | commands = 13 | py.test {posargs: workalendar} 14 | 15 | [testenv:py36-cov] 16 | commands = 17 | py.test --cov=workalendar {posargs: workalendar} 18 | 19 | [testenv:py27-cov] 20 | commands = 21 | py.test --cov=workalendar {posargs: workalendar} 22 | 23 | [testenv:flake8] 24 | deps = 25 | flake8 26 | 27 | commands = flake8 workalendar 28 | -------------------------------------------------------------------------------- /workalendar/usa/south_carolina.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | from __future__ import (absolute_import, division, print_function, 3 | unicode_literals) 4 | 5 | from ..registry import iso_register 6 | from .core import UnitedStates 7 | 8 | 9 | @iso_register('US-SC') 10 | class SouthCarolina(UnitedStates): 11 | """South Carolina""" 12 | FIXED_HOLIDAYS = UnitedStates.FIXED_HOLIDAYS + ( 13 | (5, 10, "Confederate Memorial Day"), 14 | ) 15 | include_thanksgiving_friday = True 16 | include_christmas_eve = True 17 | include_boxing_day = True 18 | include_columbus_day = False 19 | -------------------------------------------------------------------------------- /workalendar/usa/kentucky.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | from __future__ import (absolute_import, division, print_function, 3 | unicode_literals) 4 | 5 | from .core import UnitedStates 6 | from ..registry import iso_register 7 | 8 | 9 | @iso_register('US-KY') 10 | class Kentucky(UnitedStates): 11 | """Kentucky""" 12 | include_good_friday = True 13 | include_thanksgiving_friday = True 14 | include_christmas_eve = True 15 | include_columbus_day = False 16 | include_federal_presidents_day = False 17 | FIXED_HOLIDAYS = UnitedStates.FIXED_HOLIDAYS + ( 18 | (12, 31, "New Year's Eve"), 19 | ) 20 | -------------------------------------------------------------------------------- /workalendar/usa/north_dakota.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | from __future__ import (absolute_import, division, print_function, 3 | unicode_literals) 4 | 5 | from ..registry import iso_register 6 | from .core import UnitedStates 7 | 8 | 9 | @iso_register('US-ND') 10 | class NorthDakota(UnitedStates): 11 | """North Dakota""" 12 | include_columbus_day = False 13 | include_good_friday = True 14 | 15 | # NOTE: At the time of writing, the Public Holidays for USA wikipedia page 16 | # doesn't mention Good Friday, although the official Secretary of State page 17 | # does: http://sos.nd.gov/about-office/holiday-office-closings 18 | -------------------------------------------------------------------------------- /workalendar/asia/qatar.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | from __future__ import (absolute_import, division, print_function, 3 | unicode_literals) 4 | from ..core import Calendar 5 | from ..core import FRI, SAT, IslamicMixin 6 | from ..registry import iso_register 7 | 8 | 9 | @iso_register('QA') 10 | class Qatar(IslamicMixin, Calendar): 11 | "Qatar" 12 | WEEKEND_DAYS = (FRI, SAT) 13 | 14 | FIXED_HOLIDAYS = ( 15 | (12, 18, "National Day"), 16 | ) 17 | include_start_ramadan = True 18 | include_eid_al_fitr = True 19 | length_eid_al_fitr = 4 20 | include_eid_al_adha = True 21 | length_eid_al_adha = 4 22 | -------------------------------------------------------------------------------- /workalendar/usa/nebraska.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | from __future__ import (absolute_import, division, print_function, 3 | unicode_literals) 4 | 5 | from ..core import FRI 6 | from ..registry import iso_register 7 | from .core import UnitedStates 8 | 9 | 10 | @iso_register('US-NE') 11 | class Nebraska(UnitedStates): 12 | """Nebraska""" 13 | include_thanksgiving_friday = True 14 | 15 | def get_variable_days(self, year): 16 | days = super(Nebraska, self).get_variable_days(year) 17 | days.append( 18 | (self.get_last_weekday_in_month(year, 4, FRI), "Arbor Day") 19 | ) 20 | return days 21 | -------------------------------------------------------------------------------- /workalendar/europe/belgium.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | from __future__ import unicode_literals 3 | from workalendar.core import WesternCalendar, ChristianMixin 4 | from workalendar.registry import iso_register 5 | 6 | 7 | @iso_register('BE') 8 | class Belgium(WesternCalendar, ChristianMixin): 9 | 'Belgium' 10 | 11 | FIXED_HOLIDAYS = WesternCalendar.FIXED_HOLIDAYS + ( 12 | (5, 1, "Labour Day"), 13 | (7, 21, "National Day"), 14 | (11, 11, "Armistice of 1918"), 15 | ) 16 | 17 | include_easter_monday = True 18 | include_ascension = True 19 | include_whit_monday = True 20 | include_assumption = True 21 | include_all_saints = True 22 | -------------------------------------------------------------------------------- /workalendar/tests/test_global_registry.py: -------------------------------------------------------------------------------- 1 | from unittest import TestCase 2 | 3 | from workalendar.registry import registry 4 | 5 | 6 | class GlobalRegistry(TestCase): 7 | 8 | def test_name(self): 9 | classes = (v for k, v in registry.region_registry.items()) 10 | classes = list(classes) 11 | for klass in classes: 12 | # All classes have a `name` class property 13 | self.assertTrue(hasattr(klass, 'name')) 14 | # All classes have a non-empty name 15 | self.assertTrue(klass.name) 16 | # All those properties are equivalent to the class docstring 17 | self.assertEqual(klass.name, klass.__doc__) 18 | -------------------------------------------------------------------------------- /workalendar/usa/maryland.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | from __future__ import (absolute_import, division, print_function, 3 | unicode_literals) 4 | 5 | from .core import UnitedStates 6 | from ..registry import iso_register 7 | 8 | 9 | @iso_register('US-MD') 10 | class Maryland(UnitedStates): 11 | """Maryland""" 12 | thanksgiving_friday_label = "Native American Heritage Day" 13 | include_thanksgiving_friday = True 14 | 15 | def get_variable_days(self, year): 16 | days = super(Maryland, self).get_variable_days(year) 17 | if Maryland.is_presidential_year(year): 18 | days.append(self.get_election_day(year)) 19 | return days 20 | -------------------------------------------------------------------------------- /workalendar/europe/luxembourg.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | from __future__ import unicode_literals 3 | from workalendar.core import WesternCalendar, ChristianMixin 4 | from workalendar.registry import iso_register 5 | 6 | 7 | @iso_register('LU') 8 | class Luxembourg(WesternCalendar, ChristianMixin): 9 | 'Luxembourg' 10 | 11 | include_easter_monday = True 12 | include_ascension = True 13 | include_whit_monday = True 14 | include_all_saints = True 15 | include_assumption = True 16 | include_boxing_day = True 17 | 18 | FIXED_HOLIDAYS = WesternCalendar.FIXED_HOLIDAYS + ( 19 | (5, 1, "Labour Day"), 20 | (6, 23, "Luxembourg National Holiday"), 21 | ) 22 | -------------------------------------------------------------------------------- /workalendar/europe/russia.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | from __future__ import unicode_literals 3 | from workalendar.core import WesternCalendar 4 | from workalendar.registry import iso_register 5 | 6 | 7 | @iso_register('RU') 8 | class Russia(WesternCalendar): 9 | 'Russia' 10 | 11 | shift_new_years_day = True 12 | 13 | FIXED_HOLIDAYS = WesternCalendar.FIXED_HOLIDAYS + ( 14 | (1, 2, "Day After New Year"), 15 | (1, 7, "Christmas"), 16 | (2, 23, "Defendence of the Fatherland"), 17 | (3, 8, "International Women's Day"), 18 | (5, 1, "Labour Day"), 19 | (5, 9, "Victory Day"), 20 | (6, 12, "National Day"), 21 | (11, 5, "Day of Unity"), 22 | ) 23 | -------------------------------------------------------------------------------- /workalendar/usa/rhode_island.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | from __future__ import (absolute_import, division, print_function, 3 | unicode_literals) 4 | 5 | from ..core import MON 6 | from ..registry import iso_register 7 | from .core import UnitedStates 8 | 9 | 10 | @iso_register('US-RI') 11 | class RhodeIsland(UnitedStates): 12 | """Rhode Island""" 13 | include_federal_presidents_day = False 14 | include_election_day_even = True 15 | 16 | def get_variable_days(self, year): 17 | days = super(RhodeIsland, self).get_variable_days(year) 18 | days.append( 19 | (self.get_nth_weekday_in_month(year, 8, MON, 2), "Victory Day") 20 | ) 21 | return days 22 | -------------------------------------------------------------------------------- /workalendar/africa/madagascar.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | from __future__ import (absolute_import, division, print_function, 3 | unicode_literals) 4 | from ..core import WesternCalendar, ChristianMixin 5 | from ..registry import iso_register 6 | 7 | 8 | @iso_register('MG') 9 | class Madagascar(WesternCalendar, ChristianMixin): 10 | "Madagascar" 11 | FIXED_HOLIDAYS = WesternCalendar.FIXED_HOLIDAYS + ( 12 | (3, 29, "Martyrs' Day"), 13 | (5, 1, "Labour Day"), 14 | (6, 26, "Independence Day"), 15 | ) 16 | include_easter_monday = True 17 | include_ascension = True 18 | include_whit_monday = True 19 | include_assumption = True 20 | include_all_saints = True 21 | -------------------------------------------------------------------------------- /workalendar/africa/sao_tome.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | from __future__ import (absolute_import, division, print_function, 3 | unicode_literals) 4 | 5 | from ..core import WesternCalendar, ChristianMixin 6 | from ..registry import iso_register 7 | 8 | 9 | @iso_register('ST') 10 | class SaoTomeAndPrincipe(WesternCalendar, ChristianMixin): 11 | "São Tomé and Príncipe" 12 | FIXED_HOLIDAYS = WesternCalendar.FIXED_HOLIDAYS + ( 13 | (2, 3, "Martyr's Day"), 14 | (5, 1, "Labour Day"), 15 | (7, 12, "Independence Day"), 16 | (9, 6, "Armed Forces Day"), 17 | (9, 30, "Agricultural Reform Day"), 18 | (12, 21, "São Tomé Day"), 19 | ) 20 | include_all_saints = True 21 | -------------------------------------------------------------------------------- /workalendar/oceania/__init__.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | from .australia import ( 3 | Australia, 4 | AustralianCapitalTerritory, 5 | NewSouthWales, 6 | NorthernTerritory, 7 | Queensland, 8 | SouthAustralia, 9 | Tasmania, 10 | Hobart, 11 | Victoria, 12 | WesternAustralia 13 | ) 14 | from .marshall_islands import MarshallIslands 15 | 16 | 17 | __all__ = ( 18 | # Australia and al. 19 | 'Australia', 20 | 'AustralianCapitalTerritory', 21 | 'NewSouthWales', 22 | 'NorthernTerritory', 23 | 'Queensland', 24 | 'SouthAustralia', 25 | 'Tasmania', 26 | 'Hobart', 27 | 'Victoria', 28 | 'WesternAustralia', 29 | # Other oceanian countries 30 | 'MarshallIslands', 31 | ) 32 | -------------------------------------------------------------------------------- /workalendar/usa/nevada.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | from __future__ import (absolute_import, division, print_function, 3 | unicode_literals) 4 | 5 | from ..core import FRI 6 | from ..registry import iso_register 7 | from .core import UnitedStates 8 | 9 | 10 | @iso_register('US-NV') 11 | class Nevada(UnitedStates): 12 | """Nevada""" 13 | include_thanksgiving_friday = True 14 | thanksgiving_friday_label = "Family Day" 15 | include_columbus_day = False 16 | 17 | def get_variable_days(self, year): 18 | days = super(Nevada, self).get_variable_days(year) 19 | days.append( 20 | (self.get_last_weekday_in_month(year, 10, FRI), "Nevada Day") 21 | ) 22 | return days 23 | -------------------------------------------------------------------------------- /workalendar/usa/montana.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | from __future__ import (absolute_import, division, print_function, 3 | unicode_literals) 4 | import warnings 5 | 6 | from .core import UnitedStates 7 | from ..registry import iso_register 8 | 9 | 10 | @iso_register('US-MT') 11 | class Montana(UnitedStates): 12 | """Montana""" 13 | include_election_day_even = True 14 | 15 | def get_variable_days(self, year): 16 | warnings.warn( 17 | "Montana states is supposed to observe General Election Day on " 18 | "even years, but for some reason some sources are including it " 19 | "in 2019. Please use with care." 20 | ) 21 | return super(Montana, self).get_variable_days(year) 22 | -------------------------------------------------------------------------------- /docs/index.md: -------------------------------------------------------------------------------- 1 | # Workalendar documentation 2 | 3 | ## Overview 4 | 5 | Workalendar is a Python module that offers classes able to handle calendars, list legal / religious holidays and gives working-day-related computation functions. 6 | 7 | ## Status 8 | 9 | This library is ready for production, although we may warn eventual users: some calendars may not be up-to-date, and this library doesn't cover all the existing countries on earth (yet). 10 | 11 | ## Available Calendars 12 | 13 | See [the repository README](https://github.com/peopledoc/workalendar#available-calendars) for the most up-to-date catalog of the available calendars. 14 | 15 | ## Usage examples 16 | 17 | * [Basic usage](basic.md) 18 | * [Advanced usage](advanced.md) 19 | * [ISO Registry](iso-registry.md) 20 | -------------------------------------------------------------------------------- /workalendar/europe/italy.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | from __future__ import unicode_literals 3 | from workalendar.core import WesternCalendar, ChristianMixin 4 | from workalendar.registry import iso_register 5 | 6 | 7 | @iso_register('IT') 8 | class Italy(WesternCalendar, ChristianMixin): 9 | 'Italy' 10 | 11 | FIXED_HOLIDAYS = WesternCalendar.FIXED_HOLIDAYS + ( 12 | (4, 25, "Liberation Day"), 13 | (5, 1, "International Workers' Day"), 14 | (6, 2, "Republic Day"), 15 | ) 16 | include_immaculate_conception = True 17 | include_epiphany = True 18 | include_easter_monday = True 19 | include_assumption = True 20 | include_all_saints = True 21 | include_boxing_day = True 22 | boxing_day_label = "St Stephen's Day" 23 | -------------------------------------------------------------------------------- /workalendar/usa/alaska.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | from __future__ import (absolute_import, division, print_function, 3 | unicode_literals) 4 | 5 | from .core import UnitedStates 6 | from workalendar.core import MON 7 | from workalendar.registry import iso_register 8 | 9 | 10 | @iso_register('US-AK') 11 | class Alaska(UnitedStates): 12 | """Alaska""" 13 | FIXED_HOLIDAYS = UnitedStates.FIXED_HOLIDAYS + ( 14 | (10, 18, 'Alaska Day'), 15 | ) 16 | include_columbus_day = False 17 | 18 | def get_variable_days(self, year): 19 | days = super(Alaska, self).get_variable_days(year) 20 | days.append( 21 | (Alaska.get_last_weekday_in_month(year, 3, MON), "Seward's Day") 22 | ) 23 | return days 24 | -------------------------------------------------------------------------------- /.github/PULL_REQUEST_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | refs # 2 | 3 | 4 | 5 | For information, read and make sure you're okay with the [CONTRIBUTING document](https://github.com/novafloss/workalendar/blob/master/CONTRIBUTING.rst#adding-new-calendars). 6 | 7 | - [ ] Tests with a significant number of years to be tested for your calendar. 8 | - [ ] Docstrings for the Calendar class and specific methods. 9 | - [ ] Calendar country / label added to the README.rst file, 10 | - [ ] Changelog amended with a mention like: "Added ```` by ``@pseudo`` (#)" 11 | 12 | 13 | 14 | - [ ] Tests with a significant number of years to be tested for your calendar. 15 | - [ ] Changelog amended with a mention describing your changes. 16 | -------------------------------------------------------------------------------- /workalendar/tests/test_registry_africa.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | from unittest import TestCase 3 | 4 | from workalendar.africa import ( 5 | Algeria, 6 | Benin, 7 | IvoryCoast, 8 | Madagascar, 9 | SaoTomeAndPrincipe, 10 | SouthAfrica, 11 | ) 12 | 13 | from workalendar.registry import registry 14 | 15 | 16 | class RegistryAfrica(TestCase): 17 | def test_africa(self): 18 | classes = (v for k, v in registry.region_registry.items()) 19 | classes = list(classes) 20 | self.assertIn(Algeria, classes) 21 | self.assertIn(Benin, classes) 22 | self.assertIn(IvoryCoast, classes) 23 | self.assertIn(Madagascar, classes) 24 | self.assertIn(SaoTomeAndPrincipe, classes) 25 | self.assertIn(SouthAfrica, classes) 26 | -------------------------------------------------------------------------------- /workalendar/usa/vermont.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | from __future__ import (absolute_import, division, print_function, 3 | unicode_literals) 4 | 5 | from ..core import TUE 6 | from ..registry import iso_register 7 | from .core import UnitedStates 8 | 9 | 10 | @iso_register('US-VT') 11 | class Vermont(UnitedStates): 12 | """Vermont""" 13 | FIXED_HOLIDAYS = UnitedStates.FIXED_HOLIDAYS + ( 14 | (8, 16, "Bennington Battle Day"), 15 | ) 16 | include_columbus_day = False 17 | 18 | def get_variable_days(self, year): 19 | days = super(Vermont, self).get_variable_days(year) 20 | days.append( 21 | (self.get_nth_weekday_in_month(year, 3, TUE, 1), 22 | "Town Meeting Day") 23 | ) 24 | return days 25 | -------------------------------------------------------------------------------- /workalendar/europe/norway.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | from __future__ import unicode_literals 3 | from workalendar.core import WesternCalendar, ChristianMixin 4 | from workalendar.registry import iso_register 5 | 6 | 7 | @iso_register('NO') 8 | class Norway(WesternCalendar, ChristianMixin): 9 | 'Norway' 10 | 11 | include_holy_thursday = True 12 | include_good_friday = True 13 | include_easter_sunday = True 14 | include_easter_monday = True 15 | include_ascension = True 16 | include_whit_monday = True 17 | include_whit_sunday = True 18 | include_boxing_day = True 19 | boxing_day_label = "St Stephen's Day" 20 | 21 | FIXED_HOLIDAYS = WesternCalendar.FIXED_HOLIDAYS + ( 22 | (5, 1, "Labour Day"), 23 | (5, 17, "Constitution Day"), 24 | ) 25 | -------------------------------------------------------------------------------- /workalendar/usa/mississippi.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | from __future__ import (absolute_import, division, print_function, 3 | unicode_literals) 4 | 5 | from .core import UnitedStates 6 | from ..registry import iso_register 7 | 8 | 9 | @iso_register('US-MS') 10 | class Mississippi(UnitedStates): 11 | """Mississippi""" 12 | include_thanksgiving_friday = True 13 | include_confederation_day = True 14 | include_columbus_day = False 15 | 16 | martin_luther_king_label = ("Martin Luther King's" 17 | " and Robert E. Lee's Birthdays") 18 | veterans_day_label = "Armistice Day (Veterans Day)" 19 | national_memorial_day_label = ("National Memorial Day / " 20 | "Jefferson Davis Birthday") 21 | -------------------------------------------------------------------------------- /workalendar/tests/test_registry_asia.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | from unittest import TestCase 3 | 4 | from workalendar.asia import ( 5 | HongKong, 6 | Japan, 7 | Malaysia, 8 | Qatar, 9 | Singapore, 10 | SouthKorea, 11 | Taiwan, 12 | ) 13 | 14 | from workalendar.registry import registry 15 | 16 | 17 | class RegistryAsia(TestCase): 18 | def test_asia(self): 19 | classes = (v for k, v in registry.region_registry.items()) 20 | classes = list(classes) 21 | self.assertIn(HongKong, classes) 22 | self.assertIn(Japan, classes) 23 | self.assertIn(Malaysia, classes) 24 | self.assertIn(Qatar, classes) 25 | self.assertIn(Singapore, classes) 26 | self.assertIn(SouthKorea, classes) 27 | self.assertIn(Taiwan, classes) 28 | -------------------------------------------------------------------------------- /workalendar/europe/poland.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | from __future__ import unicode_literals 3 | from workalendar.core import WesternCalendar, ChristianMixin 4 | from workalendar.registry import iso_register 5 | 6 | 7 | @iso_register('PL') 8 | class Poland(WesternCalendar, ChristianMixin): 9 | 'Poland' 10 | 11 | FIXED_HOLIDAYS = WesternCalendar.FIXED_HOLIDAYS + ( 12 | (1, 6, 'Trzech Kroli'), 13 | (5, 1, 'Labour Day'), 14 | (5, 3, 'Constitution Day'), 15 | (11, 11, 'Independence Day'), 16 | ) 17 | include_easter_sunday = True 18 | include_easter_monday = True 19 | include_whit_sunday = True 20 | whit_sunday_label = "Pentecost Sunday" 21 | include_corpus_christi = True 22 | include_assumption = True 23 | include_all_saints = True 24 | include_boxing_day = True 25 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | sudo: false 2 | language: python 3 | install: 4 | - pip install tox 5 | 6 | script: 7 | - tox -e $TOXENV 8 | 9 | matrix: 10 | fast_finish: true 11 | include: 12 | # Python version is just for the look on travis. 13 | - python: 2.7 14 | env: TOXENV=py27 15 | 16 | - python: 3.4 17 | env: TOXENV=py34 18 | 19 | - python: 3.5 20 | env: TOXENV=py35 21 | 22 | - python: 3.6 23 | env: TOXENV=py36 24 | 25 | - python: 3.7 26 | env: TOXENV=py37 27 | # As of September 2018, Python 3.7.0 is not available on "vanilla" travis builds. 28 | dist: xenial 29 | sudo: true 30 | 31 | # Lints and others 32 | - python: 3.6 33 | env: TOXENV=flake8 34 | - python: 2.7 35 | env: TOXENV=py27-cov 36 | - python: 3.6 37 | env: TOXENV=py36-cov 38 | -------------------------------------------------------------------------------- /workalendar/africa/algeria.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | from __future__ import (absolute_import, division, print_function, 3 | unicode_literals) 4 | from ..core import WesternCalendar, IslamicMixin 5 | from ..registry import iso_register 6 | 7 | 8 | @iso_register('DZ') 9 | class Algeria(WesternCalendar, IslamicMixin): 10 | "Algeria" 11 | # Islamic holidays 12 | include_prophet_birthday = True 13 | include_eid_al_fitr = True 14 | include_day_of_sacrifice = True 15 | include_islamic_new_year = True 16 | 17 | FIXED_HOLIDAYS = WesternCalendar.FIXED_HOLIDAYS + ( 18 | (5, 1, "Labour Day"), 19 | (7, 5, "Independence Day"), 20 | (11, 1, "Anniversary of the revolution"), 21 | ) 22 | 23 | ISLAMIC_HOLIDAYS = IslamicMixin.ISLAMIC_HOLIDAYS + ( 24 | (1, 10, "Ashura"), 25 | ) 26 | -------------------------------------------------------------------------------- /workalendar/europe/france.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | from __future__ import unicode_literals 3 | from workalendar.core import WesternCalendar, ChristianMixin 4 | from workalendar.registry import iso_register 5 | 6 | 7 | @iso_register('FR') 8 | class France(WesternCalendar, ChristianMixin): 9 | 'France' 10 | 11 | include_easter_monday = True 12 | include_ascension = True 13 | include_whit_monday = True 14 | include_all_saints = True 15 | include_assumption = True 16 | 17 | FIXED_HOLIDAYS = WesternCalendar.FIXED_HOLIDAYS + ( 18 | (5, 1, "Labour Day"), 19 | (5, 8, "Victory in Europe Day"), 20 | (7, 14, "Bastille Day"), 21 | (11, 11, "Armistice Day"), 22 | ) 23 | 24 | 25 | class FranceAlsaceMoselle(France): 26 | "France Alsace/Moselle" 27 | include_good_friday = True 28 | include_boxing_day = True 29 | -------------------------------------------------------------------------------- /workalendar/europe/estonia.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | from __future__ import unicode_literals 3 | from workalendar.core import WesternCalendar, ChristianMixin 4 | from workalendar.registry import iso_register 5 | 6 | 7 | @iso_register('EE') 8 | class Estonia(WesternCalendar, ChristianMixin): 9 | 'Estonia' 10 | 11 | include_good_friday = True 12 | include_easter_sunday = True 13 | include_whit_sunday = True 14 | whit_sunday_label = 'Nelipühade 1. püha' 15 | include_christmas_eve = True 16 | include_christmas = True 17 | include_boxing_day = True 18 | boxing_day_label = "Teine jõulupüha" 19 | 20 | FIXED_HOLIDAYS = WesternCalendar.FIXED_HOLIDAYS + ( 21 | (2, 24, "Independence Day"), 22 | (5, 1, "Kevadpüha"), 23 | (6, 23, "Võidupüha"), 24 | (6, 24, "Jaanipäev"), 25 | (8, 20, "Taasiseseisvumispäev") 26 | ) 27 | -------------------------------------------------------------------------------- /workalendar/europe/austria.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | from __future__ import unicode_literals 3 | from workalendar.core import WesternCalendar, ChristianMixin 4 | from workalendar.registry import iso_register 5 | 6 | 7 | @iso_register('AT') 8 | class Austria(WesternCalendar, ChristianMixin): 9 | 'Austria' 10 | 11 | FIXED_HOLIDAYS = WesternCalendar.FIXED_HOLIDAYS + ( 12 | (5, 1, "National Holiday"), # Staatsfeiertag 13 | (10, 26, "National Holiday"), # Nationalfeiertag 14 | ) 15 | 16 | include_epiphany = True 17 | include_easter_monday = True 18 | include_ascension = True 19 | include_whit_monday = True 20 | include_corpus_christi = True 21 | include_assumption = True 22 | include_all_saints = True 23 | include_immaculate_conception = True 24 | include_christmas = True 25 | include_boxing_day = True 26 | boxing_day_label = "St. Stephen's Day" # Stefanitag 27 | -------------------------------------------------------------------------------- /workalendar/europe/greece.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | from __future__ import unicode_literals 3 | from workalendar.core import WesternCalendar, OrthodoxMixin 4 | from workalendar.registry import iso_register 5 | 6 | 7 | @iso_register('GR') 8 | class Greece(OrthodoxMixin, WesternCalendar): 9 | 'Greece' 10 | 11 | FIXED_HOLIDAYS = WesternCalendar.FIXED_HOLIDAYS + ( 12 | (3, 25, "Independence Day"), 13 | (5, 1, "Labour Day"), 14 | (10, 28, "Ohi Day"), 15 | ) 16 | include_epiphany = True 17 | include_clean_monday = True 18 | include_annunciation = True 19 | include_good_friday = True 20 | include_easter_sunday = True 21 | include_easter_monday = True 22 | include_whit_sunday = True 23 | whit_sunday_label = "Pentecost" 24 | include_whit_monday = True 25 | include_assumption = True 26 | include_boxing_day = True 27 | boxing_day_label = "Glorifying Mother of God" 28 | -------------------------------------------------------------------------------- /workalendar/africa/ivory_coast.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | from __future__ import (absolute_import, division, print_function, 3 | unicode_literals) 4 | from ..core import WesternCalendar, IslamicMixin, ChristianMixin 5 | from ..registry import iso_register 6 | 7 | 8 | @iso_register('CI') 9 | class IvoryCoast(WesternCalendar, ChristianMixin, IslamicMixin): 10 | "Ivory Coast" 11 | include_easter_monday = True 12 | include_ascension = True 13 | include_whit_monday = True 14 | include_assumption = True 15 | include_all_saints = True 16 | include_day_after_prophet_birthday = True 17 | include_eid_al_fitr = True 18 | include_day_of_sacrifice = True 19 | include_day_of_sacrifice_label = "Feast of the Sacrifice" 20 | 21 | FIXED_HOLIDAYS = WesternCalendar.FIXED_HOLIDAYS + ( 22 | (5, 1, "Labour Day"), 23 | (8, 7, "Independence Day"), 24 | (11, 15, "National Peace Day"), 25 | ) 26 | -------------------------------------------------------------------------------- /workalendar/europe/croatia.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | from __future__ import unicode_literals 3 | from workalendar.core import WesternCalendar, ChristianMixin 4 | from workalendar.registry import iso_register 5 | 6 | 7 | @iso_register('HR') 8 | class Croatia(WesternCalendar, ChristianMixin): 9 | 'Croatia' 10 | 11 | FIXED_HOLIDAYS = WesternCalendar.FIXED_HOLIDAYS + ( 12 | (5, 1, "International Workers' Day"), 13 | (6, 22, "Anti-Fascist Struggle Day"), 14 | (6, 25, "Statehood Day"), 15 | (8, 5, "Victory & Homeland Thanksgiving & Day of Croatian defenders"), 16 | (10, 8, "Independence Day"), 17 | ) 18 | 19 | include_epiphany = True 20 | include_easter_sunday = True 21 | include_easter_monday = True 22 | include_corpus_christi = True 23 | include_assumption = True 24 | include_all_saints = True 25 | include_christmas = True 26 | include_boxing_day = True 27 | boxing_day_label = "St. Stephen's Day" 28 | -------------------------------------------------------------------------------- /workalendar/africa/benin.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | from __future__ import (absolute_import, division, print_function, 3 | unicode_literals) 4 | from ..core import WesternCalendar, IslamicMixin, ChristianMixin 5 | from ..registry import iso_register 6 | 7 | 8 | @iso_register('BJ') 9 | class Benin(WesternCalendar, IslamicMixin, ChristianMixin): 10 | "Benin" 11 | include_easter_monday = True 12 | include_ascension = True 13 | include_whit_monday = True 14 | include_assumption = True 15 | include_all_saints = True 16 | # Islamic holidays 17 | include_prophet_birthday = True 18 | include_eid_al_fitr = True 19 | include_day_of_sacrifice = True 20 | include_day_of_sacrifice_label = "Tabaski" 21 | 22 | FIXED_HOLIDAYS = WesternCalendar.FIXED_HOLIDAYS + ( 23 | (1, 10, "Traditional Day"), 24 | (5, 1, "Labour Day"), 25 | (8, 1, "Independence Day"), 26 | (10, 26, "Armed Forces Day"), 27 | (11, 30, "National Day"), 28 | ) 29 | -------------------------------------------------------------------------------- /workalendar/usa/kansas.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | from __future__ import (absolute_import, division, print_function, 3 | unicode_literals) 4 | 5 | from .core import UnitedStates 6 | from ..registry import iso_register 7 | 8 | # FIXME: According to wikipedia, Kansas only has all federal holidays, except 9 | # the Columbus Day and Washington's Birthday. 10 | # Unfortunately, other sources mention XMas Eve for 2018, but not for other 11 | # years. 12 | # I'm a bit sad here... 13 | # 14 | # Sources to lookup, if you want to help: 15 | # * http://www.admin.ks.gov/docs/default-source/ops/holidays/holidays2018.pdf 16 | # * http://www.kansas.gov/employee/documents/2017calendar.pdf 17 | # * https://publicholidays.us/kansas/2018-dates/ 18 | # * https://en.wikipedia.org/wiki/Public_holidays_in_the_United_States#Kansas 19 | # * https://publicholidays.us/kansas/2018-dates/ 20 | 21 | 22 | @iso_register('US-KS') 23 | class Kansas(UnitedStates): 24 | """Kansas""" 25 | include_federal_presidents_day = False 26 | include_columbus_day = False 27 | -------------------------------------------------------------------------------- /workalendar/usa/hawaii.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | from __future__ import (absolute_import, division, print_function, 3 | unicode_literals) 4 | 5 | from ..core import FRI 6 | from ..registry import iso_register 7 | 8 | from .core import UnitedStates 9 | 10 | 11 | @iso_register('US-HI') 12 | class Hawaii(UnitedStates): 13 | """Hawaii""" 14 | include_good_friday = True 15 | include_columbus_day = False 16 | include_election_day_even = True 17 | 18 | FIXED_HOLIDAYS = UnitedStates.FIXED_HOLIDAYS + ( 19 | (3, 26, "Prince Jonah Kuhio Kalanianaole Day"), 20 | (6, 11, "King Kamehameha Day"), 21 | ) 22 | 23 | def get_statehood_day(self, year): 24 | """ 25 | Statehood Day: 3rd Friday in August. 26 | """ 27 | return ( 28 | self.get_nth_weekday_in_month(year, 8, FRI, 3), 29 | "Statehood Day" 30 | ) 31 | 32 | def get_variable_days(self, year): 33 | days = super(Hawaii, self).get_variable_days(year) 34 | days.append(self.get_statehood_day(year)) 35 | return days 36 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2013 Novapost 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy 4 | of this software and associated documentation files (the "Software"), to deal 5 | in the Software without restriction, including without limitation the rights 6 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | copies of the Software, and to permit persons to whom the Software is 8 | furnished to do so, subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in all 11 | copies or substantial portions of the Software. 12 | 13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 19 | SOFTWARE. 20 | -------------------------------------------------------------------------------- /workalendar/europe/spain.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | from __future__ import (absolute_import, division, print_function, 3 | unicode_literals) 4 | from workalendar.core import WesternCalendar, ChristianMixin 5 | from workalendar.registry import iso_register 6 | 7 | 8 | @iso_register('ES') 9 | class Spain(WesternCalendar, ChristianMixin): 10 | 'Spain' 11 | 12 | include_epiphany = True 13 | include_immaculate_conception = True 14 | include_good_friday = True 15 | include_assumption = True 16 | include_all_saints = True 17 | 18 | FIXED_HOLIDAYS = WesternCalendar.FIXED_HOLIDAYS + ( 19 | (5, 1, "Día del trabajador"), 20 | (10, 12, "Fiesta nacional de España"), 21 | (12, 6, "Día de la Constitución Española") 22 | ) 23 | 24 | 25 | class Catalonia(Spain): 26 | "Catalonia" 27 | 28 | include_easter_monday = True 29 | include_boxing_day = True 30 | boxing_day_label = "Sant Esteve" 31 | 32 | FIXED_HOLIDAYS = Spain.FIXED_HOLIDAYS + ( 33 | (6, 24, "Sant Joan"), 34 | (9, 11, "Diada nacional de Catalunya"), 35 | ) 36 | -------------------------------------------------------------------------------- /workalendar/america/panama.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | from __future__ import (absolute_import, division, print_function, 3 | unicode_literals) 4 | 5 | from datetime import timedelta 6 | 7 | from ..core import WesternCalendar, ChristianMixin 8 | from ..registry import iso_register 9 | 10 | 11 | @iso_register('PA') 12 | class Panama(WesternCalendar, ChristianMixin): 13 | "Panama" 14 | include_good_friday = True 15 | include_easter_saturday = True 16 | include_easter_sunday = True 17 | 18 | FIXED_HOLIDAYS = WesternCalendar.FIXED_HOLIDAYS + ( 19 | (1, 9, "Martyrs' Day"), 20 | (5, 1, "Labour Day"), 21 | (11, 3, "Independence Day"), 22 | (11, 5, "Colon Day"), 23 | (11, 10, "Shout in Villa de los Santos"), 24 | (12, 2, "Independence from Spain"), 25 | (12, 8, "Mothers' Day"), 26 | ) 27 | 28 | def get_variable_days(self, year): 29 | days = super(Panama, self).get_variable_days(year) 30 | days.append( 31 | (self.get_ash_wednesday(year) - timedelta(days=1), "Carnival") 32 | ) 33 | return days 34 | -------------------------------------------------------------------------------- /workalendar/europe/hungary.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | from __future__ import unicode_literals 3 | from workalendar.core import WesternCalendar, ChristianMixin 4 | from workalendar.registry import iso_register 5 | 6 | 7 | @iso_register('HU') 8 | class Hungary(WesternCalendar, ChristianMixin): 9 | 'Hungary' 10 | 11 | include_easter_sunday = True 12 | include_easter_monday = True 13 | include_whit_sunday = True 14 | whit_sunday_label = "Pentecost Sunday" 15 | include_whit_monday = True 16 | whit_monday_label = "Pentecost Monday" 17 | include_boxing_day = True 18 | boxing_day_label = "Second Day of Christmas" 19 | include_all_saints = True 20 | 21 | FIXED_HOLIDAYS = WesternCalendar.FIXED_HOLIDAYS + ( 22 | (3, 15, "National Day"), 23 | (5, 1, "Labour Day"), 24 | (8, 20, "St Stephen's Day"), 25 | (10, 23, "National Day"), 26 | ) 27 | 28 | def get_variable_days(self, year): 29 | # As of 2017, Good Friday became a holiday 30 | self.include_good_friday = (year >= 2017) 31 | days = super(Hungary, self).get_variable_days(year) 32 | return days 33 | -------------------------------------------------------------------------------- /workalendar/europe/slovakia.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | from __future__ import unicode_literals 3 | from workalendar.core import WesternCalendar, ChristianMixin 4 | from workalendar.registry import iso_register 5 | 6 | 7 | @iso_register('SK') 8 | class Slovakia(WesternCalendar, ChristianMixin): 9 | 'Slovakia' 10 | 11 | include_epiphany = True 12 | include_easter_monday = True 13 | include_good_friday = True 14 | include_all_saints = True 15 | include_christmas_eve = True 16 | include_boxing_day = True 17 | boxing_day_label = "St. Stephen's Day (The Second Christmas Day)" 18 | 19 | FIXED_HOLIDAYS = WesternCalendar.FIXED_HOLIDAYS + ( 20 | (1, 1, "Day of the Establishment of the Slovak Republic"), 21 | (5, 1, "Labour Day"), 22 | (5, 8, "Liberation Day"), 23 | (7, 5, "Saints Cyril and Methodius Day"), 24 | (8, 29, "Slovak National Uprising anniversary"), 25 | (9, 1, "Day of the Constitution of the Slovak Republic"), 26 | (9, 15, "Day of Blessed Virgin Mary, patron saint of Slovakia"), 27 | (11, 17, "Struggle for Freedom and Democracy Day"), 28 | ) 29 | -------------------------------------------------------------------------------- /workalendar/europe/malta.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | from __future__ import unicode_literals 3 | from workalendar.core import WesternCalendar, ChristianMixin 4 | from workalendar.registry import iso_register 5 | 6 | 7 | @iso_register('MT') 8 | class Malta(WesternCalendar, ChristianMixin): 9 | 'Malta' 10 | 11 | include_good_friday = True 12 | include_assumption = True 13 | include_immaculate_conception = True 14 | include_christmas = True 15 | 16 | FIXED_HOLIDAYS = WesternCalendar.FIXED_HOLIDAYS + ( 17 | # National Holidays 18 | (3, 31, "Freedom Day"), # (Jum il-Ħelsien) 19 | (6, 7, "Sette Giugno"), 20 | (9, 8, "Victory Day"), # (Jum il-Vitorja) 21 | (9, 21, "Independence Day"), # (Jum l-Indipendenza) 22 | (12, 13, "Republic Day"), # (Jum ir-Repubblika) 23 | # Public Holidays 24 | (1, 1, "New Year's Day"), # (L-Ewwel tas-Sena) 25 | (2, 10, "Feast of Saint Paul's Shipwreck"), 26 | (3, 19, "Feast of Saint Joseph"), # (San Ġużepp) 27 | (5, 1, "Worker's Day"), # (Jum il-Ħaddiem) 28 | (6, 29, "Feast of Saint Peter & Saint Paul"), # (L-Imnarja) 29 | ) 30 | -------------------------------------------------------------------------------- /workalendar/europe/czech_republic.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | from __future__ import unicode_literals 3 | from workalendar.core import WesternCalendar, ChristianMixin 4 | from workalendar.registry import iso_register 5 | 6 | 7 | @iso_register('CZ') 8 | class CzechRepublic(WesternCalendar, ChristianMixin): 9 | 'Czech Republic' 10 | 11 | include_easter_monday = True 12 | include_good_friday = True 13 | 14 | FIXED_HOLIDAYS = WesternCalendar.FIXED_HOLIDAYS + ( 15 | (1, 1, "Restoration Day of the Independent Czech State"), 16 | (5, 1, "Labour Day"), 17 | (5, 8, "Liberation Day"), 18 | (7, 5, "Saints Cyril and Methodius Day"), 19 | (7, 6, "Jan Hus Day"), 20 | (9, 28, "St. Wenceslas Day (Czech Statehood Day)"), 21 | (10, 28, "Independent Czechoslovak State Day"), 22 | (11, 17, "Struggle for Freedom and Democracy Day"), 23 | (12, 24, "Christmas Eve"), 24 | (12, 26, "St. Stephen's Day (The Second Christmas Day)"), 25 | ) 26 | 27 | def get_variable_days(self, year): 28 | # As of 2016, Good Friday became a holiday 29 | self.include_good_friday = (year >= 2016) 30 | return super(CzechRepublic, self).get_variable_days(year) 31 | -------------------------------------------------------------------------------- /workalendar/usa/michigan.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | from __future__ import (absolute_import, division, print_function, 3 | unicode_literals) 4 | 5 | from datetime import date 6 | from ..core import SUN 7 | from ..registry import iso_register 8 | 9 | from .core import UnitedStates 10 | 11 | 12 | @iso_register('US-MI') 13 | class Michigan(UnitedStates): 14 | """Michigan""" 15 | include_christmas_eve = True 16 | include_thanksgiving_friday = True 17 | include_election_day_even = True 18 | include_columbus_day = False 19 | 20 | def get_fixed_holidays(self, year): 21 | days = super(Michigan, self).get_fixed_holidays(year) 22 | 23 | # New Year's Eve to be added 24 | new_years_eve = date(year, 12, 31) 25 | days.append( 26 | (new_years_eve, "New Years Eve") 27 | ) 28 | 29 | # Christmas Eve & New Year's Eve shift when it falls on SUN 30 | xmas_eve = date(year, 12, 24) 31 | if xmas_eve.weekday() == SUN: 32 | days.append( 33 | (date(year, 12, 22), "Christmas Eve shift") 34 | ) 35 | days.append( 36 | (date(year, 12, 29), "New Years Eve Shift") 37 | ) 38 | 39 | return days 40 | -------------------------------------------------------------------------------- /workalendar/europe/cyprus.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | from __future__ import unicode_literals 3 | from datetime import timedelta 4 | from workalendar.core import WesternCalendar, ChristianMixin 5 | from workalendar.registry import iso_register 6 | 7 | 8 | @iso_register('CY') 9 | class Cyprus(WesternCalendar, ChristianMixin): 10 | 'Cyprus' 11 | 12 | include_epiphany = True 13 | include_clean_monday = True 14 | include_good_friday = True 15 | include_easter_saturday = True 16 | include_easter_sunday = True 17 | include_easter_monday = True 18 | include_whit_monday = True 19 | whit_monday_label = 'Pentecost Monday' 20 | include_christmas_eve = True 21 | include_christmas_day = True 22 | include_boxing_day = True 23 | 24 | FIXED_HOLIDAYS = WesternCalendar.FIXED_HOLIDAYS + ( 25 | (3, 25, "Greek Independence Day"), 26 | (4, 1, "Cyprus National Day"), 27 | (5, 1, "Labour Day"), 28 | (7, 15, "Dormition of the Theotokos"), 29 | (10, 1, "Cyprus Independence Day"), 30 | (10, 28, "Greek National Day"), 31 | ) 32 | 33 | def get_variable_days(self, year): 34 | days = super(Cyprus, self).get_variable_days(year) 35 | days.append((self.get_easter_monday(year) + 36 | timedelta(days=1), "Easter Tuesday")) 37 | return days 38 | -------------------------------------------------------------------------------- /workalendar/europe/bulgaria.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | from __future__ import unicode_literals 3 | from workalendar.core import WesternCalendar, ChristianMixin 4 | from workalendar.registry import iso_register 5 | 6 | 7 | @iso_register('BG') 8 | class Bulgaria(WesternCalendar, ChristianMixin): 9 | 'Bulgaria' 10 | 11 | FIXED_HOLIDAYS = WesternCalendar.FIXED_HOLIDAYS + ( 12 | (3, 3, "Liberation Day"), # Ден на Освобождението на Б 13 | (5, 1, "International Workers' Day"), # Ден на труда и на междунар 14 | (5, 6, "Saint George's Day"), # Гергьовден, ден на храброс 15 | (5, 24, "Saints Cyril & Methodius Day"), # Ден на българската просвет 16 | (9, 6, "Unification Day"), # Ден на Съединението 17 | (9, 22, "Independence Day"), # Ден на независимостта на Б 18 | # wikipedia says Non-attendance day for schools, otherwise a working da 19 | # (11, 1, "National Awakening Day"), # Ден на народните будители 20 | 21 | ) 22 | 23 | include_easter_sunday = True 24 | include_easter_monday = True 25 | include_christmas_eve = True # Бъдни вечер 26 | include_christmas = True # Рождество Христово 27 | include_boxing_day = True 28 | 29 | # wikipedia says The Bulgarians have two days of Christmas, 30 | # both called Christmas Day 31 | boxing_day_label = "Christmas" 32 | -------------------------------------------------------------------------------- /workalendar/oceania/marshall_islands.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | from __future__ import absolute_import, unicode_literals 3 | 4 | from ..core import WesternCalendar, ChristianMixin 5 | from ..core import FRI 6 | from ..registry import iso_register 7 | 8 | 9 | @iso_register('MH') 10 | class MarshallIslands(WesternCalendar, ChristianMixin): 11 | "Marshall Islands" 12 | FIXED_HOLIDAYS = WesternCalendar.FIXED_HOLIDAYS + ( 13 | (3, 3, "Remembrance Day"), 14 | (5, 1, "Constitution Day"), 15 | (11, 17, "Presidents' Day"), 16 | (12, 31, "New Year's Eve"), 17 | ) 18 | include_good_friday = True 19 | 20 | def get_variable_days(self, year): 21 | days = super(MarshallIslands, self).get_variable_days(year) 22 | days.append(( 23 | MarshallIslands.get_nth_weekday_in_month(year, 7, FRI), 24 | "Fishermen's Holiday" 25 | )) 26 | days.append(( 27 | MarshallIslands.get_nth_weekday_in_month(year, 9, FRI), 28 | "Labour Day" 29 | )) 30 | days.append(( 31 | MarshallIslands.get_last_weekday_in_month(year, 9, FRI), 32 | "Manit Day" 33 | )) 34 | days.append(( 35 | MarshallIslands.get_nth_weekday_in_month(year, 12, FRI), 36 | "Gospel Day" 37 | )) 38 | return days 39 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | TEST_ARGS ?= 2 | TEST_ENVS ?= 3 | TEST_REBUILD ?= yes 4 | 5 | # target: help - Display callable targets. 6 | .PHONY: help 7 | help: 8 | @echo "Reference card for usual actions in development environment." 9 | @echo "Here are available targets:" 10 | @egrep -o "^# target: (.+)" [Mm]akefile | sed 's/# target: / * /' 11 | 12 | # target: install - Install workalendar in your current env 13 | .PHONY: install 14 | install: 15 | pip install -e ./ 16 | 17 | 18 | # target: tox_install - Install tox in your current env 19 | .PHONY: tox_install 20 | tox_install: 21 | pip install tox --upgrade 22 | 23 | # target: test - run tox tests. 24 | # target: Use TEST_ENVS and TEST_ARGS to target your tests more precisely. 25 | # target: WARNING: tox should be installed in your current env. 26 | # shall we rebuild the env or not? 27 | ifeq (${TEST_REBUILD},"yes") 28 | TOX_COMMAND=tox -r 29 | else 30 | TOX_COMMAND=tox 31 | endif 32 | .PHONY: test 33 | test: 34 | ifneq (${TEST_ENVS},) 35 | ${TOX_COMMAND} -e ${TEST_ENVS} -- ${TEST_ARGS} 36 | else 37 | ${TOX_COMMAND} -- ${TEST_ARGS} 38 | endif 39 | 40 | # target: wheel_install - install wheel in your current environment 41 | wheel_install: 42 | pip install wheel 43 | 44 | # target: pypi - upload current version on PYPI 45 | .PHONY: pypi 46 | pypi: wheel_install 47 | python setup.py sdist bdist_wheel upload 48 | -------------------------------------------------------------------------------- /workalendar/africa/angola.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | from __future__ import (absolute_import, division, print_function, 3 | unicode_literals) 4 | from datetime import timedelta 5 | from workalendar.core import WesternCalendar 6 | from workalendar.core import ChristianMixin 7 | from workalendar.registry import iso_register 8 | 9 | 10 | @iso_register('AO') 11 | class Angola(WesternCalendar, ChristianMixin): 12 | """Angola""" 13 | 14 | include_good_friday = True 15 | include_easter_sunday = True 16 | include_christmas = True 17 | include_all_souls = True 18 | 19 | FIXED_HOLIDAYS = WesternCalendar.FIXED_HOLIDAYS + ( 20 | (2, 4, "Dia do Inicio da Luta Armada"), 21 | (3, 8, "Dia Internacional da Mulher"), 22 | (4, 4, "Dia da Paz"), 23 | (5, 1, "Dia Internacional do Trabalhador"), 24 | (9, 17, "Dia do Fundador da Nação e do Herói Nacional"), 25 | (11, 11, "Dia da Independência Nacional"), 26 | ) 27 | 28 | def get_variable_entrudo(self, year): 29 | easter_sunday = self.get_easter_sunday(year) 30 | return easter_sunday - timedelta(days=47) 31 | 32 | def get_variable_days(self, year): 33 | days = super(Angola, self).get_variable_days(year) 34 | days.append((self.get_variable_entrudo(year), "Dia de Carnaval")) 35 | return days 36 | -------------------------------------------------------------------------------- /workalendar/europe/denmark.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | from __future__ import unicode_literals 3 | from datetime import timedelta 4 | from workalendar.core import WesternCalendar, ChristianMixin 5 | from workalendar.registry import iso_register 6 | 7 | 8 | @iso_register('DK') 9 | class Denmark(WesternCalendar, ChristianMixin): 10 | 'Denmark' 11 | 12 | include_palm_sunday = True 13 | include_holy_thursday = True 14 | include_good_friday = True 15 | include_easter_sunday = True 16 | include_easter_monday = True 17 | include_ascension = True 18 | include_whit_sunday = True 19 | whit_sunday_label = "Pentecost Sunday" 20 | include_whit_monday = True 21 | whit_monday_label = "Pentecost Monday" 22 | include_boxing_day = True 23 | boxing_day_label = "Second Day of Christmas" 24 | include_christmas_eve = True 25 | 26 | FIXED_HOLIDAYS = WesternCalendar.FIXED_HOLIDAYS + ( 27 | (6, 5, "Constitution Day"), 28 | (12, 31, "New Year's Eve") 29 | ) 30 | 31 | def get_store_bededag(self, year): # 'great prayer day' 32 | easter_sunday = self.get_easter_sunday(year) 33 | return easter_sunday + timedelta(days=26) 34 | 35 | def get_variable_days(self, year): 36 | days = super(Denmark, self).get_variable_days(year) 37 | days.append((self.get_store_bededag(year), "Store Bededag")) 38 | return days 39 | -------------------------------------------------------------------------------- /workalendar/europe/portugal.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | from __future__ import unicode_literals 3 | from datetime import date 4 | from workalendar.core import WesternCalendar, ChristianMixin 5 | from workalendar.registry import iso_register 6 | 7 | 8 | @iso_register('PT') 9 | class Portugal(WesternCalendar, ChristianMixin): 10 | 'Portugal' 11 | 12 | include_good_friday = True 13 | include_easter_sunday = True 14 | include_christmas = True 15 | 16 | FIXED_HOLIDAYS = WesternCalendar.FIXED_HOLIDAYS + ( 17 | (4, 25, "Dia da Liberdade"), 18 | (5, 1, "Dia do Trabalhador"), 19 | (6, 10, "Dia de Portugal"), 20 | (8, 15, "Assunção de Nossa Senhora"), 21 | (12, 8, "Imaculada Conceição"), 22 | ) 23 | 24 | def get_fixed_holidays(self, year): 25 | days = super(Portugal, self).get_fixed_holidays(year) 26 | if year > 2015 or year < 2013: 27 | 28 | days.append((date(year, 10, 5), "Implantação da República")) 29 | days.append((date(year, 11, 1), "Todos os santos")) 30 | days.append((date(year, 12, 1), "Restauração da Independência")) 31 | return days 32 | 33 | def get_variable_days(self, year): 34 | days = super(Portugal, self).get_variable_days(year) 35 | if year > 2015 or year < 2013: 36 | days.append((self.get_corpus_christi(year), "Corpus Christi")) 37 | return days 38 | -------------------------------------------------------------------------------- /workalendar/tests/test_registry_oceania.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | from unittest import TestCase 3 | 4 | from workalendar.oceania import ( 5 | Australia, 6 | MarshallIslands, 7 | # Australian territories 8 | AustralianCapitalTerritory, 9 | NewSouthWales, 10 | NorthernTerritory, 11 | Queensland, 12 | SouthAustralia, 13 | Tasmania, 14 | # FIXME: there's no ISO code for this city. Shall we add it? 15 | # Hobart, 16 | Victoria, 17 | WesternAustralia 18 | ) 19 | 20 | from workalendar.registry import registry 21 | 22 | AUSTRALIAN_TERRITORIES = ( 23 | AustralianCapitalTerritory, 24 | NewSouthWales, 25 | NorthernTerritory, 26 | Queensland, 27 | SouthAustralia, 28 | Tasmania, 29 | Victoria, 30 | WesternAustralia 31 | ) 32 | 33 | 34 | class RegistryOceania(TestCase): 35 | def test_oceania(self): 36 | classes = (v for k, v in registry.region_registry.items()) 37 | classes = list(classes) 38 | self.assertIn(Australia, classes) 39 | self.assertIn(MarshallIslands, classes) 40 | for klass in AUSTRALIAN_TERRITORIES: 41 | self.assertIn(klass, classes) 42 | 43 | def test_australia_territories(self): 44 | # Get all the subregions 45 | classes = (v for k, v in registry.get_subregions('AU').items()) 46 | classes = list(classes) 47 | for klass in AUSTRALIAN_TERRITORIES: 48 | self.assertIn(klass, classes) 49 | -------------------------------------------------------------------------------- /workalendar/asia/south_korea.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | from __future__ import (absolute_import, division, print_function, 3 | unicode_literals) 4 | 5 | from ..core import ChineseNewYearCalendar, WesternCalendar 6 | from ..registry import iso_register 7 | 8 | 9 | @iso_register('KR') 10 | class SouthKorea(WesternCalendar, ChineseNewYearCalendar): 11 | "South Korea" 12 | FIXED_HOLIDAYS = WesternCalendar.FIXED_HOLIDAYS + ( 13 | (3, 1, "Independence Day"), 14 | (5, 5, "Children's Day"), 15 | (6, 6, "Memorial Day"), 16 | (8, 15, "Liberation Day"), 17 | (10, 3, "National Foundation Day"), 18 | (10, 9, "Hangul Day"), 19 | (12, 25, "Christmas Day"), 20 | ) 21 | chinese_new_year_label = "Korean New Year's Day" 22 | include_chinese_new_year_eve = True 23 | chinese_new_year_eve_label = "Korean New Year's Day" 24 | include_chinese_second_day = True 25 | chinese_second_day_label = "Korean New Year's Day" 26 | 27 | def get_variable_days(self, year): 28 | days = super(SouthKorea, self).get_variable_days(year) 29 | days.extend([ 30 | (ChineseNewYearCalendar.lunar(year, 4, 8), "Buddha's Birthday"), 31 | # Midautumn Festival (3 days) 32 | (ChineseNewYearCalendar.lunar(year, 8, 14), "Midautumn Festival"), 33 | (ChineseNewYearCalendar.lunar(year, 8, 15), "Midautumn Festival"), 34 | (ChineseNewYearCalendar.lunar(year, 8, 16), "Midautumn Festival"), 35 | ]) 36 | return days 37 | -------------------------------------------------------------------------------- /workalendar/europe/slovenia.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | from __future__ import unicode_literals 3 | from datetime import date 4 | from workalendar.core import WesternCalendar, ChristianMixin 5 | from workalendar.registry import iso_register 6 | 7 | 8 | @iso_register('SI') 9 | class Slovenia(WesternCalendar, ChristianMixin): 10 | 'Slovenia' 11 | 12 | include_easter_sunday = True 13 | include_easter_monday = True 14 | include_whit_sunday = True 15 | include_assumption = True 16 | include_christmas = True 17 | 18 | FIXED_HOLIDAYS = WesternCalendar.FIXED_HOLIDAYS + ( 19 | (2, 8, "Preseren Day, the Slovenian Cultural Holiday"), 20 | (4, 27, "Day of Uprising Against Occupation"), 21 | (5, 1, "Labour Day"), 22 | (5, 2, "Labour Day"), 23 | (6, 25, "Statehood Day"), 24 | (10, 31, "Reformation Day"), 25 | (11, 1, "Day of Remembrance of the Dead"), 26 | (12, 26, "Independence and Unity Day"), 27 | ) 28 | 29 | def get_variable_days(self, year): 30 | days = super(Slovenia, self).get_variable_days(year) 31 | 32 | # From 1955 until May 2012, when the National Assembly of Slovenia 33 | # passed the Public Finance Balance Act, 2 January was a work-free day. 34 | # It has been re-introduced in 2017. 35 | # Source - Wikipedia 36 | # https://en.wikipedia.org/wiki/Public_holidays_in_Slovenia 37 | if 1955 <= year <= 2012 or year >= 2017: 38 | days.append((date(year, 1, 2), "January 2nd")) 39 | 40 | return days 41 | -------------------------------------------------------------------------------- /workalendar/asia/taiwan.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | from __future__ import (absolute_import, division, print_function, 3 | unicode_literals) 4 | 5 | from ..core import ChineseNewYearCalendar, WesternCalendar 6 | from ..core import EphemMixin 7 | from ..registry import iso_register 8 | 9 | 10 | @iso_register('TW') 11 | class Taiwan(EphemMixin, ChineseNewYearCalendar, WesternCalendar): 12 | "Taiwan (Republic of China)" 13 | FIXED_HOLIDAYS = ( 14 | WesternCalendar.FIXED_HOLIDAYS + 15 | ( 16 | (2, 28, "228 Peace Memorial Day"), 17 | (4, 4, "Combination of Women's Day and Children's Day"), 18 | (10, 10, "National Day/Double Tenth Day"), 19 | ) 20 | ) 21 | include_chinese_new_year_eve = True 22 | include_chinese_second_day = True 23 | 24 | def get_variable_days(self, year): 25 | days = super(Taiwan, self).get_variable_days(year) 26 | # Qingming begins when the sun reaches the celestial 27 | # longitude of 15° (usually around April 4th or 5th) 28 | qingming = EphemMixin.solar_term(self, year, 15, 'Asia/Taipei') 29 | 30 | days.extend([ 31 | ( 32 | ChineseNewYearCalendar.lunar(year, 1, 3), 33 | "Chinese New Year (3rd day)" 34 | ), 35 | (qingming, "Qingming Festival"), 36 | (ChineseNewYearCalendar.lunar(year, 5, 5), "Dragon Boat Festival"), 37 | (ChineseNewYearCalendar.lunar(year, 8, 15), "Mid-Autumn Festival"), 38 | ]) 39 | return days 40 | -------------------------------------------------------------------------------- /workalendar/usa/virginia.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | from __future__ import (absolute_import, division, print_function, 3 | unicode_literals) 4 | """ 5 | Virginia module 6 | 7 | You may or may want not to treat the Day before Thanksgiving as a non-working 8 | day by implementing the following class: 9 | 10 | .. code:: 11 | 12 | from workalenda.usa import Virginia as VirginiaBase 13 | 14 | class Virginia(VirginiaBase): 15 | include_thanksgiving_wednesday = False 16 | 17 | """ 18 | from ..core import WED, FRI 19 | from ..registry import iso_register 20 | from .core import UnitedStates 21 | 22 | 23 | @iso_register('US-VA') 24 | class Virginia(UnitedStates): 25 | """Virginia""" 26 | include_christmas_eve = True 27 | include_thanksgiving_friday = True 28 | include_boxing_day = True 29 | presidents_day_label = "George Washington Day" 30 | # Virginia specific. By default, it's treated as a holiday, but 31 | # you may chose to exclude it for you own uses. See the module doc. 32 | include_thanksgiving_wednesday = True 33 | 34 | def get_variable_days(self, year): 35 | days = super(Virginia, self).get_variable_days(year) 36 | days.append( 37 | (self.get_nth_weekday_in_month(year, 1, FRI, 3), 38 | "Lee-Jackson Day") 39 | ) 40 | if self.include_thanksgiving_wednesday: 41 | days.append( 42 | (self.get_nth_weekday_in_month(year, 11, WED, 4), 43 | "Day before Thanksgiving (start at noon)") 44 | ) 45 | 46 | return days 47 | -------------------------------------------------------------------------------- /workalendar/europe/lithuania.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | from __future__ import unicode_literals 3 | from workalendar.core import WesternCalendar, ChristianMixin 4 | from workalendar.registry import iso_register 5 | from workalendar.core import SUN 6 | 7 | 8 | @iso_register('LT') 9 | class Lithuania(WesternCalendar, ChristianMixin): 10 | 'Lithuania' 11 | 12 | FIXED_HOLIDAYS = WesternCalendar.FIXED_HOLIDAYS + ( 13 | (2, 16, "Restoration of the State Day"), 14 | (3, 11, "Restoration of Independence Day"), 15 | (5, 1, "Labour Day"), 16 | (6, 24, "St. John's Day"), 17 | (7, 6, "Anniversary of the Coronation of King Mindaugas"), 18 | (8, 15, "Assumption Day"), 19 | (11, 1, "All Saints' Day"), 20 | ) 21 | 22 | include_easter_sunday = True 23 | include_easter_monday = True 24 | include_christmas_eve = True 25 | include_christmas = True 26 | include_boxing_day = True 27 | boxing_day_label = "Second day of Christmas" 28 | 29 | def get_mothers_day(self, year): 30 | return ( 31 | Lithuania.get_nth_weekday_in_month(year, 5, SUN, 1), 32 | "Mother's day" 33 | ) 34 | 35 | def get_fathers_day(self, year): 36 | return ( 37 | Lithuania.get_nth_weekday_in_month(year, 6, SUN, 1), 38 | "Father's day" 39 | ) 40 | 41 | def get_variable_days(self, year): 42 | days = super(Lithuania, self).get_variable_days(year) 43 | days.append(self.get_mothers_day(year)) 44 | days.append(self.get_fathers_day(year)) 45 | return days 46 | -------------------------------------------------------------------------------- /workalendar/europe/netherlands.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | from __future__ import unicode_literals 3 | from datetime import date 4 | from workalendar.core import WesternCalendar, ChristianMixin 5 | from workalendar.registry import iso_register 6 | 7 | 8 | @iso_register('NL') 9 | class Netherlands(WesternCalendar, ChristianMixin): 10 | 'Netherlands' 11 | 12 | include_good_friday = True 13 | include_easter_sunday = True 14 | include_easter_monday = True 15 | include_ascension = True 16 | include_whit_sunday = True 17 | include_whit_monday = True 18 | include_boxing_day = True 19 | 20 | FIXED_HOLIDAYS = WesternCalendar.FIXED_HOLIDAYS + ( 21 | (5, 5, "Liberation Day"), 22 | ) 23 | 24 | def get_king_queen_day(self, year): 25 | """27 April unless this is a Sunday in which case it is the 26th 26 | 27 | Before 2013 it was called Queensday, falling on 28 | 30 April, unless this is a Sunday in which case it is the 29th. 29 | """ 30 | if year > 2013: 31 | if date(year, 4, 27).weekday() != 6: 32 | return date(year, 4, 27), "King's day" 33 | else: 34 | return date(year, 4, 26), "King's day" 35 | else: 36 | if date(year, 4, 30).weekday() != 6: 37 | return date(year, 4, 30), "Queen's day" 38 | else: 39 | return date(year, 4, 29), "Queen's day" 40 | 41 | def get_variable_days(self, year): 42 | days = super(Netherlands, self).get_variable_days(year) 43 | days.append(self.get_king_queen_day(year)) 44 | return days 45 | -------------------------------------------------------------------------------- /workalendar/europe/iceland.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | from __future__ import unicode_literals 3 | from datetime import date 4 | from workalendar.core import WesternCalendar, ChristianMixin 5 | from workalendar.core import THU, MON 6 | from workalendar.registry import iso_register 7 | 8 | 9 | @iso_register('IS') 10 | class Iceland(WesternCalendar, ChristianMixin): 11 | 'Iceland' 12 | 13 | include_holy_thursday = True 14 | include_good_friday = True 15 | include_easter_monday = True 16 | include_ascension = True 17 | include_whit_monday = True 18 | include_christmas_eve = True 19 | include_boxing_day = True 20 | boxing_day_label = "St Stephen's Day" 21 | 22 | FIXED_HOLIDAYS = WesternCalendar.FIXED_HOLIDAYS + ( 23 | (5, 1, "Labour Day"), 24 | (6, 17, "Icelandic National Day"), 25 | (12, 31, "New Year's Eve"), 26 | ) 27 | 28 | def get_first_day_of_summer(self, year): 29 | """It's the first thursday *after* April, 18th. 30 | If April the 18th is a thursday, then it jumps to the 24th. 31 | """ 32 | return Iceland.get_nth_weekday_in_month( 33 | year, 4, THU, 34 | start=date(year, 4, 19)) 35 | 36 | def get_commerce_day(self, year): 37 | return Iceland.get_nth_weekday_in_month(year, 8, MON) 38 | 39 | def get_variable_days(self, year): 40 | days = super(Iceland, self).get_variable_days(year) 41 | days.extend([ 42 | (self.get_first_day_of_summer(year), "First day of summer"), 43 | (self.get_commerce_day(year), "Commerce Day"), 44 | ]) 45 | return days 46 | -------------------------------------------------------------------------------- /workalendar/usa/west_virginia.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | from __future__ import (absolute_import, division, print_function, 3 | unicode_literals) 4 | """ 5 | West Virginia 6 | 7 | Christmas Eve and New Years Eve are considered as half-holidays. By default, 8 | they're not included as "non-working days". If for your personal use you want 9 | to include them, you may just have to create a class like this: 10 | 11 | .. code:: 12 | 13 | class WestVirginiaIncludeEves(WestVirginia): 14 | west_virginia_include_christmas_eve = True 15 | west_virginia_include_nye = True 16 | 17 | """ 18 | from datetime import date 19 | 20 | from ..registry import iso_register 21 | from .core import UnitedStates 22 | 23 | 24 | @iso_register('US-WV') 25 | class WestVirginia(UnitedStates): 26 | """West Virginia""" 27 | include_thanksgiving_friday = True 28 | include_election_day_even = True 29 | election_day_label = "Election Day / Susan B. Anthony Day" 30 | 31 | # West Virginia specific "half-holidays" 32 | west_virginia_include_christmas_eve = False 33 | west_virginia_include_nye = False 34 | FIXED_HOLIDAYS = UnitedStates.FIXED_HOLIDAYS + ( 35 | (6, 20, "West Virgina Day"), 36 | ) 37 | shift_exceptions = ( 38 | (12, 24), 39 | (12, 31), 40 | ) 41 | 42 | def get_fixed_holidays(self, year): 43 | days = super(WestVirginia, self).get_fixed_holidays(year) 44 | if self.west_virginia_include_christmas_eve: 45 | days.append( 46 | (date(year, 12, 24), "Christmas Eve") 47 | ) 48 | if self.west_virginia_include_nye: 49 | days.append( 50 | (date(year, 12, 31), "New Years Eve") 51 | ) 52 | return days 53 | -------------------------------------------------------------------------------- /workalendar/america/chile.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | from __future__ import (absolute_import, division, print_function, 3 | unicode_literals) 4 | 5 | from datetime import date 6 | 7 | from ..core import WesternCalendar, ChristianMixin 8 | from ..core import MON, TUE, WED, FRI 9 | from ..registry import iso_register 10 | 11 | 12 | @iso_register('CL') 13 | class Chile(WesternCalendar, ChristianMixin): 14 | "Chile" 15 | FIXED_HOLIDAYS = WesternCalendar.FIXED_HOLIDAYS + ( 16 | (5, 1, "Labour Day"), 17 | (5, 21, "Navy Day"), 18 | (6, 29, "Saint Peter and Saint Paul"), 19 | (7, 16, "Our Lady of Mount Carmel"), 20 | (9, 18, "National holiday"), 21 | (9, 19, "Army holiday"), 22 | (10, 12, "Columbus Day"), 23 | (12, 31, "Banking Holiday"), 24 | ) 25 | include_good_friday = True 26 | include_easter_saturday = True 27 | include_assumption = True 28 | include_all_saints = True 29 | include_immaculate_conception = True 30 | 31 | def get_variable_days(self, year): 32 | days = super(Chile, self).get_variable_days(year) 33 | september_17 = date(year, 9, 17) 34 | if september_17.weekday() == MON: 35 | days.append((september_17, '"Bridge" holiday')) 36 | september_20 = date(year, 9, 20) 37 | if september_20.weekday() == FRI: 38 | days.append((september_20, '"Bridge" holiday')) 39 | 40 | reformation_day = date(year, 10, 31) 41 | if reformation_day.weekday() == WED: 42 | reformation_day = date(year, 11, 2) 43 | elif reformation_day.weekday() == TUE: 44 | reformation_day = date(year, 10, 27) 45 | 46 | days.append((reformation_day, "Reformation Day")) 47 | 48 | return days 49 | -------------------------------------------------------------------------------- /workalendar/europe/switzerland.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | from __future__ import unicode_literals 3 | from datetime import date, timedelta 4 | from workalendar.core import WesternCalendar, ChristianMixin 5 | from workalendar.registry import iso_register 6 | 7 | 8 | @iso_register('CH') 9 | class Switzerland(WesternCalendar, ChristianMixin): 10 | 'Switzerland' 11 | 12 | include_good_friday = True 13 | include_easter_sunday = True 14 | include_easter_monday = True 15 | include_ascension = True 16 | include_whit_sunday = True 17 | include_whit_monday = True 18 | include_christmas = True 19 | include_boxing_day = True 20 | 21 | FIXED_HOLIDAYS = WesternCalendar.FIXED_HOLIDAYS + ( 22 | (1, 2, "Berchtold's Day"), 23 | (5, 1, "Labour Day"), 24 | (8, 1, "National Holiday"), 25 | ) 26 | 27 | 28 | @iso_register('CH-VD') 29 | class Vaud(Switzerland): 30 | 'Vaud' 31 | 32 | include_boxing_day = False 33 | include_federal_thanksgiving_monday = True 34 | 35 | FIXED_HOLIDAYS = WesternCalendar.FIXED_HOLIDAYS + ( 36 | (1, 2, "Berchtold's Day"), 37 | (8, 1, "National Holiday"), 38 | ) 39 | 40 | def get_federal_thanksgiving_monday(self, year): 41 | "Monday following the 3rd sunday of September" 42 | september_1st = date(year, 9, 1) 43 | return ( 44 | september_1st + 45 | (6 - september_1st.weekday()) * timedelta(days=1) + # 1st sunday 46 | timedelta(days=15) # Monday following 3rd sunday 47 | ) 48 | 49 | def get_variable_days(self, year): 50 | days = super(Vaud, self).get_variable_days(year) 51 | if self.include_federal_thanksgiving_monday: 52 | days.append((self.get_federal_thanksgiving_monday(year), 53 | "Federal Thanksgiving Monday")) 54 | return days 55 | -------------------------------------------------------------------------------- /workalendar/europe/finland.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | from __future__ import unicode_literals 3 | from datetime import date 4 | from workalendar.core import WesternCalendar, ChristianMixin 5 | from workalendar.core import FRI, SAT 6 | from workalendar.registry import iso_register 7 | 8 | 9 | @iso_register('FI') 10 | class Finland(WesternCalendar, ChristianMixin): 11 | 'Finland' 12 | 13 | include_epiphany = True 14 | include_good_friday = True 15 | include_easter_sunday = True 16 | include_easter_monday = True 17 | include_ascension = True 18 | include_whit_sunday = True 19 | whit_sunday_label = 'Pentecost' 20 | include_christmas_eve = True 21 | include_boxing_day = True 22 | boxing_day_label = "St. Stephen's Day" 23 | 24 | FIXED_HOLIDAYS = WesternCalendar.FIXED_HOLIDAYS + ( 25 | (5, 1, "Labour Day"), 26 | (12, 6, "Independence Day"), 27 | ) 28 | 29 | def get_midsummer_eve(self, year): 30 | date_eve = Finland.get_nth_weekday_in_month( 31 | year, 6, FRI, start=date(year, 6, 19)) 32 | return date_eve 33 | 34 | def get_midsummer_day(self, year): 35 | date_eve = Finland.get_nth_weekday_in_month( 36 | year, 6, SAT, start=date(year, 6, 20)) 37 | return date_eve 38 | 39 | def get_variable_all_saints(self, year): 40 | all_saints = date(year, 10, 31) 41 | if all_saints.weekday() != SAT: 42 | all_saints = Finland.get_nth_weekday_in_month( 43 | year, 11, SAT) 44 | return all_saints 45 | 46 | def get_variable_days(self, year): 47 | days = super(Finland, self).get_variable_days(year) 48 | days.append((self.get_midsummer_eve(year), "Midsummer's Eve")) 49 | days.append((self.get_midsummer_day(year), "Midsummer's Day")) 50 | days.append((self.get_variable_all_saints(year), "All Saints")) 51 | return days 52 | -------------------------------------------------------------------------------- /workalendar/europe/romania.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | from __future__ import unicode_literals 3 | from datetime import date 4 | from workalendar.core import WesternCalendar, OrthodoxMixin 5 | from workalendar.registry import iso_register 6 | 7 | 8 | @iso_register('RO') 9 | class Romania(WesternCalendar, OrthodoxMixin): 10 | 'Romania' 11 | 12 | FIXED_HOLIDAYS = WesternCalendar.FIXED_HOLIDAYS + ( 13 | (1, 2, "Day After New Years"), 14 | (1, 24, "Union Day"), 15 | (5, 1, "Labour Day"), 16 | (8, 15, "Dormition of the Theotokos"), 17 | (11, 30, "St. Andrew's Day"), 18 | (12, 1, "National Day/Great Union"), 19 | ) 20 | 21 | include_good_friday = True 22 | include_easter_sunday = True 23 | include_easter_monday = True 24 | include_whit_sunday = True 25 | whit_sunday_label = 'Pentecost' 26 | include_whit_monday = True 27 | 28 | include_christmas = True 29 | include_boxing_day = True 30 | boxing_day_label = 'Christmas Day' 31 | 32 | def get_childrens_day(self, year): 33 | """returns a possibly empty list of (date, holiday_name) tuples""" 34 | days = [] 35 | if year >= 2017: 36 | actual_date = date(year, 6, 1) 37 | days = [(actual_date, "Children's Day")] 38 | 39 | return days 40 | 41 | def get_liberation_day(self, year): 42 | """returns a possibly empty list of (date, holiday_name) tuples""" 43 | days = [] 44 | if year >= 1949 and year <= 1990: 45 | actual_date = date(year, 8, 23) 46 | days = [(actual_date, "Liberation from Fascist Occupation Day")] 47 | 48 | return days 49 | 50 | def get_variable_days(self, year): 51 | days = super(Romania, self).get_variable_days(year) 52 | days.extend(self.get_childrens_day(year)) 53 | days.extend(self.get_liberation_day(year)) 54 | return days 55 | -------------------------------------------------------------------------------- /workalendar/america/mexico.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | from __future__ import (absolute_import, division, print_function, 3 | unicode_literals) 4 | 5 | from datetime import date, timedelta 6 | 7 | from ..core import WesternCalendar, ChristianMixin 8 | from ..core import SUN, MON, SAT 9 | from ..registry import iso_register 10 | 11 | 12 | @iso_register('MX') 13 | class Mexico(WesternCalendar, ChristianMixin): 14 | "Mexico" 15 | FIXED_HOLIDAYS = WesternCalendar.FIXED_HOLIDAYS + ( 16 | (5, 1, "Labour Day"), 17 | (9, 16, "Independence Day"), 18 | ) 19 | 20 | def get_variable_days(self, year): 21 | days = super(Mexico, self).get_variable_days(year) 22 | days.append( 23 | (Mexico.get_nth_weekday_in_month(year, 2, MON), 24 | "Constitution Day")) 25 | 26 | days.append( 27 | (Mexico.get_nth_weekday_in_month(year, 3, MON, 3), 28 | "Benito Juárez's birthday")) 29 | 30 | days.append( 31 | (Mexico.get_nth_weekday_in_month(year, 11, MON, 3), 32 | "Revolution Day")) 33 | 34 | return days 35 | 36 | def get_calendar_holidays(self, year): 37 | days = super(Mexico, self).get_calendar_holidays(year) 38 | # If any statutory day is on Sunday, the monday is off 39 | # If it's on a Saturday, the Friday is off 40 | for day, label in days: 41 | if day.weekday() == SAT: 42 | days.append((day - timedelta(days=1), "%s substitute" % label)) 43 | elif day.weekday() == SUN: 44 | days.append((day + timedelta(days=1), "%s substitute" % label)) 45 | # Extra: if new year's day is a saturday, the friday before is off 46 | next_new_year = date(year + 1, 1, 1) 47 | if next_new_year.weekday(): 48 | days.append((date(year, 12, 31), "New Year Day substitute")) 49 | return days 50 | -------------------------------------------------------------------------------- /workalendar/usa/alabama.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | from __future__ import (absolute_import, division, print_function, 3 | unicode_literals) 4 | 5 | from .core import UnitedStates 6 | from ..core import MON 7 | from ..registry import iso_register 8 | 9 | 10 | @iso_register('US-AL') 11 | class Alabama(UnitedStates): 12 | "Alabama" 13 | include_confederation_day = True 14 | martin_luther_king_label = "Robert E. Lee/Martin Luther King Birthday" 15 | presidents_day_label = "George Washington/Thomas Jefferson Birthday" 16 | columbus_day_label = ("Columbus Day / Fraternal Day /" 17 | " American Indian Heritage Day") 18 | 19 | def get_jefferson_davis_birthday(self, year): 20 | """ 21 | The first MON of June is Jefferson Davis Birthday 22 | """ 23 | return ( 24 | self.get_nth_weekday_in_month(year, 6, MON, 1), 25 | "Jefferson Davis Birthday" 26 | ) 27 | 28 | def get_variable_days(self, year): 29 | days = super(Alabama, self).get_variable_days(year) 30 | days.append(self.get_jefferson_davis_birthday(year)) 31 | return days 32 | 33 | 34 | class AlabamaBaldwinCounty(Alabama): 35 | "Baldwin County, Alabama" 36 | include_mardi_gras = True 37 | 38 | 39 | class AlabamaMobileCounty(Alabama): 40 | "Mobile County, Alabama" 41 | include_mardi_gras = True 42 | 43 | 44 | class AlabamaPerryCounty(Alabama): 45 | "Mobile Perry, Alabama" 46 | 47 | def get_obama_day(self, year): 48 | """ 49 | Obama Day happens on the 2nd MON of November. 50 | """ 51 | return ( 52 | self.get_nth_weekday_in_month(year, 11, MON, 2), 53 | "Obama Day" 54 | ) 55 | 56 | def get_variable_days(self, year): 57 | days = super(AlabamaPerryCounty, self).get_variable_days(year) 58 | days.append(self.get_obama_day(year)) 59 | return days 60 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | import io 4 | from os.path import join, dirname, abspath 5 | import sys 6 | from setuptools import setup, find_packages 7 | 8 | PY2 = sys.version_info[0] == 2 9 | 10 | 11 | def read_relative_file(filename): 12 | """Returns contents of the given file, whose path is supposed relative 13 | to this module.""" 14 | path = join(dirname(abspath(__file__)), filename) 15 | with io.open(path, encoding='utf-8') as f: 16 | return f.read() 17 | 18 | 19 | NAME = 'workalendar' 20 | DESCRIPTION = 'Worldwide holidays and working days helper and toolkit.' 21 | REQUIREMENTS = [ 22 | 'python-dateutil', 23 | 'lunardate', 24 | 'pytz', 25 | 'pyCalverter', 26 | 'setuptools>=1.0', 27 | ] 28 | version = '3.2.0.dev0' 29 | __VERSION__ = version 30 | 31 | if PY2: 32 | REQUIREMENTS.append('pyephem') 33 | else: 34 | REQUIREMENTS.append('ephem') 35 | 36 | params = dict( 37 | name=NAME, 38 | description=DESCRIPTION, 39 | packages=find_packages(), 40 | version=__VERSION__, 41 | long_description=read_relative_file('README.rst'), 42 | author='Bruno Bord', 43 | author_email='bruno.bord@people-doc.com', 44 | url='https://github.com/peopledoc/workalendar', 45 | license='MIT License', 46 | include_package_data=True, 47 | install_requires=REQUIREMENTS, 48 | zip_safe=False, 49 | classifiers=[ 50 | 'Development Status :: 5 - Production/Stable', 51 | 'Intended Audience :: Developers', 52 | 'License :: OSI Approved :: MIT License', 53 | 'Programming Language :: Python', 54 | 'Programming Language :: Python :: 2', 55 | 'Programming Language :: Python :: 2.7', 56 | 'Programming Language :: Python :: 3', 57 | 'Programming Language :: Python :: 3.4', 58 | 'Programming Language :: Python :: 3.5', 59 | 'Programming Language :: Python :: 3.6', 60 | 'Programming Language :: Python :: 3.7', 61 | ], 62 | ) 63 | 64 | if __name__ == '__main__': 65 | setup(**params) 66 | -------------------------------------------------------------------------------- /workalendar/asia/japan.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | from __future__ import (absolute_import, division, print_function, 3 | unicode_literals) 4 | from datetime import date 5 | 6 | from ..core import MON, EphemMixin 7 | from ..core import WesternCalendar 8 | from ..registry import iso_register 9 | 10 | 11 | @iso_register('JP') 12 | class Japan(WesternCalendar, EphemMixin): 13 | "Japan" 14 | 15 | FIXED_HOLIDAYS = WesternCalendar.FIXED_HOLIDAYS + ( 16 | (2, 11, "Foundation Day"), 17 | (4, 29, "Showa Day"), 18 | (5, 3, "Constitution Memorial Day"), 19 | (5, 4, "Greenery Day"), 20 | (5, 5, "Children's Day"), 21 | (11, 3, "Culture Day"), 22 | (11, 23, "Labour Thanksgiving Day"), 23 | (12, 23, "The Emperor's Birthday"), 24 | ) 25 | 26 | def get_fixed_holidays(self, year): 27 | """ 28 | Fixed holidays for Japan. 29 | 30 | As of 2016, the "Mountain Day is being added" 31 | """ 32 | days = super(Japan, self).get_fixed_holidays(year) 33 | if year >= 2016: 34 | days.append((date(year, 8, 11), "Mountain Day")) 35 | return days 36 | 37 | def get_variable_days(self, year): 38 | # usual variable days 39 | days = super(Japan, self).get_variable_days(year) 40 | equinoxes = self.calculate_equinoxes(year, 'Asia/Tokyo') 41 | coming_of_age_day = Japan.get_nth_weekday_in_month(year, 1, MON, 2) 42 | marine_day = Japan.get_nth_weekday_in_month(year, 7, MON, 3) 43 | respect_for_the_aged = Japan.get_nth_weekday_in_month(year, 9, MON, 3) 44 | health_and_sport = Japan.get_nth_weekday_in_month(year, 10, MON, 2) 45 | days.extend([ 46 | (coming_of_age_day, 'Coming of Age Day'), 47 | (marine_day, "Marine Day"), 48 | (equinoxes[0], "Vernal Equinox Day"), 49 | (respect_for_the_aged, "Respect-for-the-Aged Day"), 50 | (equinoxes[1], "Autumnal Equinox Day"), 51 | (health_and_sport, "Health and Sports Day"), 52 | ]) 53 | return days 54 | -------------------------------------------------------------------------------- /workalendar/europe/latvia.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | from __future__ import unicode_literals 3 | from datetime import date 4 | from workalendar.core import WesternCalendar, ChristianMixin 5 | from workalendar.registry import iso_register 6 | 7 | 8 | @iso_register('LV') 9 | class Latvia(WesternCalendar, ChristianMixin): 10 | 'Latvia' 11 | 12 | FIXED_HOLIDAYS = WesternCalendar.FIXED_HOLIDAYS + ( 13 | (5, 1, "Labour Day"), 14 | (6, 23, "Midsummer Day"), 15 | (6, 24, "St. John's Day"), 16 | (11, 18, "Proclamation Day"), 17 | (12, 31, "New Years Eve"), 18 | ) 19 | 20 | include_good_friday = True 21 | include_easter_sunday = True 22 | include_easter_monday = True 23 | include_christmas_eve = True 24 | include_christmas = True 25 | include_boxing_day = True 26 | 27 | def get_independence_days(self, year): 28 | """returns a possibly empty list of (date, holiday_name) tuples""" 29 | days = [] 30 | if year > 2004: 31 | actual_date = date(year, 5, 4) 32 | days = [(actual_date, "Restoration of Independence Day")] 33 | if actual_date.weekday() in self.get_weekend_days(): 34 | days += [(self.find_following_working_day(actual_date), 35 | "Restoration of Independence Observed")] 36 | return days 37 | 38 | def get_republic_days(self, year): 39 | """returns a possibly empty list of (date, holiday_name) tuples""" 40 | days = [] 41 | if year > 1918: 42 | actual_date = date(year, 11, 18) 43 | days = [(actual_date, "Proclamation of Republic Day")] 44 | if actual_date.weekday() in self.get_weekend_days(): 45 | days += [(self.find_following_working_day(actual_date), 46 | "Proclamation of Republic Observed")] 47 | return days 48 | 49 | def get_variable_days(self, year): 50 | days = super(Latvia, self).get_variable_days(year) 51 | days.extend(self.get_independence_days(year)) 52 | days.extend(self.get_republic_days(year)) 53 | return days 54 | -------------------------------------------------------------------------------- /workalendar/europe/sweden.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | from __future__ import unicode_literals 3 | from datetime import date 4 | from workalendar.core import WesternCalendar, ChristianMixin 5 | from workalendar.core import FRI, SAT 6 | from workalendar.registry import iso_register 7 | 8 | 9 | @iso_register('SE') 10 | class Sweden(WesternCalendar, ChristianMixin): 11 | 'Sweden' 12 | 13 | include_epiphany = True 14 | include_good_friday = True 15 | include_easter_sunday = True 16 | include_easter_monday = True 17 | include_ascension = True 18 | include_whit_sunday = True 19 | whit_sunday_label = "Pentecost" 20 | # Christmas Eve is not a holiday but not a work day either 21 | include_christmas_eve = True 22 | include_boxing_day = True 23 | boxing_day_label = "Second Day of Christmas" 24 | 25 | FIXED_HOLIDAYS = WesternCalendar.FIXED_HOLIDAYS + ( 26 | (5, 1, "Labour Day"), 27 | (6, 6, "National Day"), 28 | # New Year's Eve is not a holiday but not a work day either 29 | (12, 31, "New Year's Eve") 30 | ) 31 | 32 | # Midsummer Eve is not a holiday but not a work day either 33 | def get_midsummer_eve(self, year): 34 | date_eve = Sweden.get_nth_weekday_in_month( 35 | year, 6, FRI, start=date(year, 6, 19)) 36 | return date_eve 37 | 38 | def get_midsummer_day(self, year): 39 | date_eve = Sweden.get_nth_weekday_in_month( 40 | year, 6, SAT, start=date(year, 6, 20)) 41 | return date_eve 42 | 43 | def get_variable_all_saints(self, year): 44 | all_saints = date(year, 10, 31) 45 | if all_saints.weekday() != SAT: 46 | all_saints = Sweden.get_nth_weekday_in_month( 47 | year, 11, SAT) 48 | return all_saints 49 | 50 | def get_variable_days(self, year): 51 | days = super(Sweden, self).get_variable_days(year) 52 | days.append((self.get_midsummer_day(year), "Midsummer's Day")) 53 | days.append((self.get_midsummer_eve(year), "Midsummer's Eve")) 54 | days.append((self.get_variable_all_saints(year), "All Saints")) 55 | return days 56 | -------------------------------------------------------------------------------- /workalendar/usa/north_carolina.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | from __future__ import (absolute_import, division, print_function, 3 | unicode_literals) 4 | 5 | import warnings 6 | from datetime import date 7 | 8 | from ..core import MON, TUE, WED, THU, FRI, SAT, SUN 9 | from ..registry import iso_register 10 | from .core import UnitedStates 11 | 12 | 13 | @iso_register('US-NC') 14 | class NorthCarolina(UnitedStates): 15 | """North Carolina""" 16 | include_good_friday = True 17 | include_christmas_eve = True 18 | include_thanksgiving_friday = True 19 | include_boxing_day = True 20 | include_federal_presidents_day = False 21 | include_columbus_day = False 22 | 23 | def get_christmas_shifts(self, year): 24 | """ 25 | Return Specific Christmas days extra shifts. 26 | There must be 3 holidays in a row: Christmas Eve, Christmas Day and 27 | Boxing Day. If one or the other falls on SUN/SAT, extra days must be 28 | added. 29 | """ 30 | xmas = date(year, 12, 25) 31 | if xmas.weekday() in (TUE, WED, THU): 32 | # No shift, move along 33 | return [] 34 | if xmas.weekday() == FRI: 35 | return [ 36 | (date(year, 12, 28), "Boxing day shift"), 37 | ] 38 | elif xmas.weekday() == SAT: 39 | warnings.warn( 40 | "We didn't find any documentation about this configuration for" 41 | " the Christmas shift. Please use with care or help us build" 42 | " a better Workalendar" 43 | ) 44 | return [ 45 | (date(year, 12, 27), "Christmas Day shift"), 46 | (date(year, 12, 28), "Boxing Day shift"), 47 | ] 48 | elif xmas.weekday() == SUN: 49 | return [ 50 | (date(year, 12, 23), "Christmas Eve shift"), 51 | (date(year, 12, 27), "Boxing Day shift"), 52 | ] 53 | elif xmas.weekday() == MON: 54 | return [ 55 | (date(year, 12, 27), "Christmas Eve shift"), 56 | ] 57 | 58 | def get_variable_days(self, year): 59 | days = super(NorthCarolina, self).get_variable_days(year) 60 | days.extend(self.get_christmas_shifts(year)) 61 | return days 62 | -------------------------------------------------------------------------------- /workalendar/usa/georgia.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | from __future__ import (absolute_import, division, print_function, 3 | unicode_literals) 4 | 5 | import warnings 6 | from datetime import date 7 | from ..core import MON, TUE, WED, THU, FRI, SAT 8 | from ..registry import iso_register 9 | from .core import UnitedStates 10 | 11 | 12 | @iso_register('US-GA') 13 | class Georgia(UnitedStates): 14 | """Georgia""" 15 | include_confederation_day = True 16 | include_federal_presidents_day = False 17 | label_washington_birthday_december = "Washington's Birthday (Observed)" 18 | 19 | def get_washington_birthday_december(self, year): 20 | """ 21 | Washington birthday observance 22 | Similar to Christmas Eve, but with special rules. 23 | It's only observed in Georgia. 24 | """ 25 | warnings.warn( 26 | "Washington birthday rules for Georgia State are confusing. " 27 | "Use this calendar with care") 28 | christmas_day = date(year, 12, 25).weekday() 29 | if christmas_day == MON: 30 | day = date(year, 12, 26) # TUE 31 | elif christmas_day == TUE: 32 | day = date(year, 12, 24) # MON 33 | elif christmas_day == WED: 34 | day = date(year, 12, 26) # TUE 35 | elif christmas_day == THU: 36 | day = date(year, 12, 26) # FRI 37 | elif christmas_day == FRI: 38 | day = date(year, 12, 24) # THU 39 | elif christmas_day == SAT: 40 | day = date(year, 12, 23) # THU 41 | else: # christmas_day == SUN: 42 | day = date(year, 12, 23) # FRI 43 | return (day, self.label_washington_birthday_december) 44 | 45 | def get_robert_lee_birthday(self, year): 46 | """ 47 | Robert E. Lee's birthday. 48 | 49 | Happens on the 4 Friday of November. 50 | """ 51 | return ( 52 | self.get_nth_weekday_in_month(year, 11, FRI, 4), 53 | "Robert E. Lee's Birthday (Observed)" 54 | ) 55 | 56 | def get_variable_days(self, year): 57 | days = super(Georgia, self).get_variable_days(year) 58 | days.extend([ 59 | self.get_robert_lee_birthday(year), 60 | self.get_washington_birthday_december(year), 61 | ]) 62 | return days 63 | -------------------------------------------------------------------------------- /workalendar/america/__init__.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | from .brazil import ( 3 | Brazil, BrazilAcre, BrazilAlagoas, BrazilAmapa, BrazilAmazonas, 4 | BrazilBahia, BrazilCeara, BrazilDistritoFederal, BrazilEspiritoSanto, 5 | BrazilGoias, BrazilMaranhao, BrazilMatoGrosso, BrazilMatoGrossoDoSul, 6 | BrazilPara, BrazilParaiba, BrazilPernambuco, BrazilPiaui, 7 | BrazilRioDeJaneiro, BrazilRioGrandeDoNorte, BrazilRioGrandeDoSul, 8 | BrazilRondonia, BrazilRoraima, BrazilSantaCatarina, BrazilSaoPauloState, 9 | BrazilSaoPauloCity, BrazilSergipe, BrazilTocantins, BrazilVitoriaCity, 10 | BrazilVilaVelhaCity, BrazilCariacicaCity, BrazilGuarapariCity, 11 | BrazilSerraCity, BrazilBankCalendar 12 | ) 13 | from .canada import ( 14 | Canada, Ontario, Quebec, BritishColumbia, Alberta, Saskatchewan, Manitoba, 15 | NewBrunswick, NovaScotia, PrinceEdwardIsland, Newfoundland, Yukon, 16 | NorthwestTerritories, Nunavut 17 | ) 18 | 19 | from .chile import Chile 20 | from .colombia import Colombia 21 | from .mexico import Mexico 22 | from .panama import Panama 23 | 24 | 25 | __all__ = ( 26 | 'Brazil', 27 | 'BrazilAcre', 28 | 'BrazilAlagoas', 29 | 'BrazilAmapa', 30 | 'BrazilAmazonas', 31 | 'BrazilBahia', 32 | 'BrazilCeara', 33 | 'BrazilDistritoFederal', 34 | 'BrazilEspiritoSanto', 35 | 'BrazilGoias', 36 | 'BrazilMaranhao', 37 | 'BrazilMatoGrosso', 38 | 'BrazilMatoGrossoDoSul', 39 | 'BrazilPara', 40 | 'BrazilParaiba', 41 | 'BrazilPernambuco', 42 | 'BrazilPiaui', 43 | 'BrazilRioDeJaneiro', 44 | 'BrazilRioGrandeDoNorte', 45 | 'BrazilRioGrandeDoSul', 46 | 'BrazilRondonia', 47 | 'BrazilRoraima', 48 | 'BrazilSantaCatarina', 49 | 'BrazilSaoPauloState', 50 | 'BrazilSaoPauloCity', 51 | 'BrazilSergipe', 52 | 'BrazilTocantins', 53 | 'BrazilVitoriaCity', 54 | 'BrazilVilaVelhaCity', 55 | 'BrazilCariacicaCity', 56 | 'BrazilGuarapariCity', 57 | 'BrazilSerraCity', 58 | 'BrazilBankCalendar', 59 | # Canada 60 | 'Canada', 61 | 'Ontario', 62 | 'Quebec', 63 | 'BritishColumbia', 64 | 'Alberta', 65 | 'Saskatchewan', 66 | 'Manitoba', 67 | 'NewBrunswick', 68 | 'NovaScotia', 69 | 'PrinceEdwardIsland', 70 | 'Newfoundland', 71 | 'Yukon', 72 | 'NorthwestTerritories', 73 | 'Nunavut', 74 | # Other american countries 75 | 'Chile', 76 | 'Colombia', 77 | 'Mexico', 78 | 'Panama', 79 | ) 80 | -------------------------------------------------------------------------------- /workalendar/europe/ireland.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | from __future__ import unicode_literals 3 | from datetime import date, timedelta 4 | from workalendar.core import WesternCalendar, ChristianMixin 5 | from workalendar.core import MON 6 | from workalendar.registry import iso_register 7 | 8 | 9 | @iso_register('IE') 10 | class Ireland(WesternCalendar, ChristianMixin): 11 | 'Ireland' 12 | 13 | include_easter_monday = True 14 | include_boxing_day = True 15 | boxing_day_label = "St. Stephen's Day" 16 | shift_new_years_day = True 17 | 18 | def get_june_holiday(self, year): 19 | return ( 20 | Ireland.get_nth_weekday_in_month(year, 6, MON), 21 | "June Holiday" 22 | ) 23 | 24 | def get_august_holiday(self, year): 25 | return ( 26 | Ireland.get_nth_weekday_in_month(year, 8, MON), 27 | "August Holiday" 28 | ) 29 | 30 | def get_variable_days(self, year): 31 | self.include_whit_monday = (year <= 1973) 32 | days = super(Ireland, self).get_variable_days(year) 33 | 34 | # St Patrick's day 35 | st_patrick = date(year, 3, 17) 36 | days.append((st_patrick, "Saint Patrick's Day")) 37 | if st_patrick.weekday() in self.get_weekend_days(): 38 | days.append(( 39 | self.find_following_working_day(st_patrick), 40 | "Saint Patrick substitute")) 41 | 42 | # May Day 43 | if year >= 1994: 44 | days.append(( 45 | Ireland.get_nth_weekday_in_month(year, 5, MON), 46 | "May Day" 47 | )) 48 | 49 | days.append(self.get_june_holiday(year)) 50 | days.append(self.get_august_holiday(year)) 51 | 52 | if year >= 1977: 53 | days.append(( 54 | Ireland.get_last_weekday_in_month(year, 10, MON), 55 | "October Holiday" 56 | )) 57 | 58 | # Boxing day & Xmas shift 59 | christmas = date(year, 12, 25) 60 | st_stephens_day = date(year, 12, 26) 61 | if christmas.weekday() in self.get_weekend_days(): 62 | shift = self.find_following_working_day(christmas) 63 | days.append((shift, "Christmas Shift")) 64 | days.append((shift + timedelta(days=1), "St. Stephen's Day Shift")) 65 | elif st_stephens_day.weekday() in self.get_weekend_days(): 66 | shift = self.find_following_working_day(st_stephens_day) 67 | days.append((shift, "St. Stephen's Day Shift")) 68 | return days 69 | -------------------------------------------------------------------------------- /workalendar/tests/test_registry.py: -------------------------------------------------------------------------------- 1 | from datetime import date 2 | from unittest import TestCase 3 | 4 | from workalendar.registry import IsoRegistry 5 | from workalendar.core import Calendar 6 | 7 | 8 | class RegionCalendar(Calendar): 9 | 'Region' 10 | 11 | def holidays(self, year=None): 12 | return tuple(( 13 | (date(year, 12, 25), 'Christmas'), 14 | (date(year, 1, 1), 'New year'), 15 | )) 16 | 17 | def get_weekend_days(self): 18 | return [] # no week-end, yes, it's sad 19 | 20 | 21 | class SubRegionCalendar(Calendar): 22 | 'Sub Region' 23 | 24 | def holidays(self, year=None): 25 | return tuple(( 26 | (date(year, 12, 25), 'Christmas'), 27 | (date(year, 1, 1), 'New year'), 28 | )) 29 | 30 | def get_weekend_days(self): 31 | return [] # no week-end, yes, it's sad 32 | 33 | 34 | class MockCalendarTest(TestCase): 35 | 36 | def setUp(self): 37 | self.region = RegionCalendar 38 | self.subregion = SubRegionCalendar 39 | 40 | def test_register(self): 41 | registry = IsoRegistry() 42 | self.assertEqual(0, len(registry.region_registry.items())) 43 | registry.register('RE', self.region) 44 | self.assertEqual(1, len(registry.region_registry.items())) 45 | self.assertEqual(RegionCalendar, registry.region_registry['RE']) 46 | 47 | def test_get_calendar_class(self): 48 | registry = IsoRegistry() 49 | registry.register('RE', self.region) 50 | calendar_class = registry.get_calendar_class('RE') 51 | self.assertEqual(calendar_class, RegionCalendar) 52 | 53 | def test_get_subregions(self): 54 | registry = IsoRegistry() 55 | registry.register('RE', self.region) 56 | registry.register('RE-SR', self.subregion) 57 | registry.register('OR-SR', self.subregion) 58 | subregions = registry.get_subregions('RE') 59 | self.assertIn('RE-SR', subregions) 60 | self.assertEqual(1, len(subregions)) 61 | 62 | def test_get_items(self): 63 | registry = IsoRegistry() 64 | registry.register('RE', self.region) 65 | registry.register('RE-SR', self.subregion) 66 | registry.register('OR-SR', self.subregion) 67 | items = registry.items(['RE'], include_subregions=True) 68 | self.assertEqual(2, len(items)) 69 | self.assertIn('RE', items) 70 | self.assertIn('RE-SR', items) 71 | items = registry.items(['RE'], include_subregions=False) 72 | self.assertEqual(1, len(items)) 73 | self.assertIn('RE', items) 74 | -------------------------------------------------------------------------------- /workalendar/europe/__init__.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | from .austria import Austria 3 | from .belgium import Belgium 4 | from .bulgaria import Bulgaria 5 | from .croatia import Croatia 6 | from .cyprus import Cyprus 7 | from .czech_republic import CzechRepublic 8 | from .estonia import Estonia 9 | from .european_central_bank import EuropeanCentralBank 10 | from .denmark import Denmark 11 | from .finland import Finland 12 | from .france import France, FranceAlsaceMoselle 13 | from .greece import Greece 14 | from .hungary import Hungary 15 | from .iceland import Iceland 16 | from .ireland import Ireland 17 | from .italy import Italy 18 | from .latvia import Latvia 19 | from .lithuania import Lithuania 20 | from .luxembourg import Luxembourg 21 | from .malta import Malta 22 | from .netherlands import Netherlands 23 | from .norway import Norway 24 | from .poland import Poland 25 | from .portugal import Portugal 26 | from .romania import Romania 27 | from .russia import Russia 28 | from .slovakia import Slovakia 29 | from .slovenia import Slovenia 30 | from .spain import Spain, Catalonia 31 | from .sweden import Sweden 32 | from .switzerland import Switzerland, Vaud 33 | from .united_kingdom import UnitedKingdom, UnitedKingdomNorthernIreland 34 | 35 | # Germany 36 | from .germany import ( 37 | Germany, BadenWurttemberg, Bavaria, Berlin, Brandenburg, Bremen, 38 | Hamburg, Hesse, MecklenburgVorpommern, LowerSaxony, 39 | NorthRhineWestphalia, RhinelandPalatinate, Saarland, Saxony, 40 | SaxonyAnhalt, SchleswigHolstein, Thuringia 41 | ) 42 | 43 | 44 | __all__ = ( 45 | 'Austria', 46 | 'Belgium', 47 | 'Bulgaria', 48 | 'Catalonia', 49 | 'Croatia', 50 | 'Cyprus', 51 | 'CzechRepublic', 52 | 'Denmark', 53 | 'Estonia', 54 | 'EuropeanCentralBank', 55 | 'Finland', 56 | 'France', 57 | 'FranceAlsaceMoselle', 58 | 'Greece', 59 | 'Hungary', 60 | 'Iceland', 61 | 'Ireland', 62 | 'Italy', 63 | 'Latvia', 64 | 'Lithuania', 65 | 'Luxembourg', 66 | 'Malta', 67 | 'Netherlands', 68 | 'Norway', 69 | 'Poland', 70 | 'Portugal', 71 | 'Romania', 72 | 'Russia', 73 | 'Slovakia', 74 | 'Slovenia', 75 | 'Spain', 76 | 'Sweden', 77 | 'Switzerland', 78 | 'Vaud', 79 | 'UnitedKingdom', 80 | 'UnitedKingdomNorthernIreland', 81 | 82 | # Germany 83 | 'Germany', 'BadenWurttemberg', 'Bavaria', 'Berlin', 'Brandenburg', 84 | 'Bremen', 'Hamburg', 'Hesse', 'MecklenburgVorpommern', 'LowerSaxony', 85 | 'NorthRhineWestphalia', 'RhinelandPalatinate', 'Saarland', 'Saxony', 86 | 'SaxonyAnhalt', 'SchleswigHolstein', 'Thuringia', 87 | ) 88 | -------------------------------------------------------------------------------- /workalendar/asia/singapore.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | from __future__ import (absolute_import, division, print_function, 3 | unicode_literals) 4 | from datetime import date 5 | 6 | from ..core import ( 7 | ChineseNewYearCalendar, WesternCalendar, ChristianMixin, IslamicMixin 8 | ) 9 | from ..registry import iso_register 10 | 11 | 12 | @iso_register('SG') 13 | class Singapore(WesternCalendar, 14 | ChineseNewYearCalendar, ChristianMixin, IslamicMixin): 15 | "Singapore" 16 | include_good_friday = True 17 | include_eid_al_fitr = True 18 | eid_al_fitr_label = "Hari Raya Puasa" 19 | include_day_of_sacrifice = True 20 | day_of_sacrifice_label = "Hari Raya Haji" 21 | 22 | FIXED_HOLIDAYS = WesternCalendar.FIXED_HOLIDAYS + ( 23 | (5, 1, "Labour Day"), 24 | (8, 9, "National Day"), 25 | ) 26 | 27 | # Diwali/Deepavali is sometimes celebrated on a different day to India 28 | # so this can't be put into a HinduMixin 29 | DEEPAVALI = { 30 | 2000: date(2000, 10, 26), 31 | 2001: date(2001, 11, 14), 32 | 2002: date(2002, 11, 3), 33 | 2003: date(2003, 10, 23), 34 | 2004: date(2004, 11, 11), 35 | 2005: date(2005, 11, 1), 36 | 2006: date(2006, 10, 21), 37 | 2007: date(2007, 11, 8), 38 | 2008: date(2008, 10, 27), 39 | 2009: date(2009, 10, 17), 40 | 2010: date(2010, 11, 5), 41 | 2011: date(2011, 10, 26), 42 | 2012: date(2012, 11, 13), 43 | 2013: date(2013, 11, 3), 44 | 2014: date(2014, 10, 22), 45 | 2015: date(2015, 11, 10), 46 | 2016: date(2016, 10, 29), 47 | 2017: date(2017, 10, 18), 48 | 2018: date(2018, 11, 6) # This might change 49 | } 50 | chinese_new_year_label = "Chinese Lunar New Year's Day" 51 | include_chinese_second_day = True 52 | chinese_second_day_label = "Second day of Chinese Lunar New Year" 53 | shift_sunday_holidays = True 54 | 55 | def get_variable_days(self, year): 56 | """ 57 | Singapore variable days 58 | """ 59 | days = super(Singapore, self).get_variable_days(year) 60 | 61 | # Vesak Day 62 | days.append( 63 | (ChineseNewYearCalendar.lunar(year, 4, 15), "Vesak Day"), 64 | ) 65 | 66 | # Add in Deepavali (hardcoded dates, so no need to shift) 67 | deepavali = self.DEEPAVALI.get(year) 68 | if not deepavali: 69 | msg = 'Missing date for Singapore Deepavali for year: %s' % year 70 | raise KeyError(msg) 71 | days.append((deepavali, 'Deepavali')) 72 | return days 73 | -------------------------------------------------------------------------------- /workalendar/europe/united_kingdom.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | from __future__ import unicode_literals 3 | from datetime import date 4 | from workalendar.core import WesternCalendar, ChristianMixin 5 | from workalendar.core import MON 6 | from workalendar.registry import iso_register 7 | 8 | 9 | @iso_register('GB') 10 | class UnitedKingdom(WesternCalendar, ChristianMixin): 11 | 'United Kingdom' 12 | 13 | include_good_friday = True 14 | include_easter_sunday = True 15 | include_easter_monday = True 16 | include_boxing_day = True 17 | shift_new_years_day = True 18 | 19 | def get_early_may_bank_holiday(self, year): 20 | return ( 21 | UnitedKingdom.get_nth_weekday_in_month(year, 5, MON), 22 | "Early May Bank Holiday" 23 | ) 24 | 25 | def get_spring_bank_holiday(self, year): 26 | return ( 27 | UnitedKingdom.get_last_weekday_in_month(year, 5, MON), 28 | "Spring Bank Holiday" 29 | ) 30 | 31 | def get_late_summer_bank_holiday(self, year): 32 | return ( 33 | UnitedKingdom.get_last_weekday_in_month(year, 8, MON), 34 | "Late Summer Bank Holiday" 35 | ) 36 | 37 | def get_variable_days(self, year): 38 | days = super(UnitedKingdom, self).get_variable_days(year) 39 | days.append(self.get_early_may_bank_holiday(year)) 40 | days.append(self.get_spring_bank_holiday(year)) 41 | days.append(self.get_late_summer_bank_holiday(year)) 42 | # Boxing day & XMas shift 43 | shifts = self.shift_christmas_boxing_days(year=year) 44 | days.extend(shifts) 45 | return days 46 | 47 | 48 | @iso_register('GB-NIR') 49 | class UnitedKingdomNorthernIreland(UnitedKingdom): 50 | 'Northern Ireland' 51 | 52 | def get_variable_days(self, year): 53 | days = super(UnitedKingdomNorthernIreland, self) \ 54 | .get_variable_days(year) 55 | # St Patrick's day 56 | st_patrick = date(year, 3, 17) 57 | days.append((st_patrick, "Saint Patrick's Day")) 58 | if st_patrick.weekday() in self.get_weekend_days(): 59 | days.append(( 60 | self.find_following_working_day(st_patrick), 61 | "Saint Patrick substitute")) 62 | 63 | # Battle of boyne 64 | battle_of_boyne = date(year, 7, 12) 65 | days.append((battle_of_boyne, "Battle of the Boyne")) 66 | if battle_of_boyne.weekday() in self.get_weekend_days(): 67 | days.append(( 68 | self.find_following_working_day(battle_of_boyne), 69 | "Battle of the Boyne substitute")) 70 | return days 71 | -------------------------------------------------------------------------------- /workalendar/asia/hong_kong.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | from __future__ import (absolute_import, division, print_function, 3 | unicode_literals) 4 | 5 | from datetime import timedelta 6 | 7 | from ..core import ChineseNewYearCalendar, WesternCalendar 8 | from ..core import ChristianMixin, EphemMixin 9 | from ..registry import iso_register 10 | 11 | 12 | @iso_register('HK') 13 | class HongKong(EphemMixin, WesternCalendar, 14 | ChineseNewYearCalendar, ChristianMixin): 15 | "Hong Kong" 16 | include_good_friday = True 17 | include_easter_saturday = True 18 | include_easter_monday = True 19 | include_boxing_day = True 20 | 21 | FIXED_HOLIDAYS = WesternCalendar.FIXED_HOLIDAYS + ( 22 | (5, 1, "Labour Day"), 23 | (7, 1, "SAR Establishment Day"), 24 | (10, 1, "National Day"), 25 | ) 26 | 27 | chinese_new_year_label = "Chinese Lunar New Year's Day" 28 | include_chinese_second_day = True 29 | chinese_second_day_label = "Second day of Chinese Lunar New Year" 30 | include_chinese_third_day = True 31 | chinese_third_day_label = "Third day of Chinese Lunar New Year" 32 | shift_sunday_holidays = True # Except CNY which rolls to Saturday 33 | shift_start_cny_sunday = False # Prior to 2011 this was True 34 | 35 | def get_variable_days(self, year): 36 | """ 37 | Hong Kong variable days 38 | """ 39 | 40 | # Prior to the "General Holidays and Employment Legislation 41 | # (Substitution of Holidays)(Amendment) Ordinance 2011", the first day 42 | # of CNY rolled to a Sat if it was on a Sun. After the Amendment, it 43 | # rolls to the following Wed 44 | if year < 2011: 45 | self.shift_start_cny_sunday = True 46 | 47 | days = super(HongKong, self).get_variable_days(year) 48 | chingming = EphemMixin.solar_term(self, year, 15, 'Asia/Hong_Kong') 49 | dupe_holiday = [chingming for day in days if chingming == day[0]] 50 | if dupe_holiday: 51 | # Roll Chingming forward a day as it clashes with another holiday 52 | chingming = chingming + timedelta(days=1) 53 | mid_autumn_label = "Day After Mid-Autumn Festival" 54 | days.extend([ 55 | (ChineseNewYearCalendar.lunar(year, 4, 8), "Buddha's Birthday"), 56 | (chingming, "Ching Ming Festival"), 57 | (ChineseNewYearCalendar.lunar(year, 5, 5), "Tuen Ng Festival"), 58 | (ChineseNewYearCalendar.lunar(year, 8, 16), mid_autumn_label), 59 | (ChineseNewYearCalendar.lunar(year, 9, 9), "Chung Yeung Festival"), 60 | ]) 61 | 62 | # Boxing day & XMas shift 63 | shifts = self.shift_christmas_boxing_days(year=year) 64 | days.extend(shifts) 65 | 66 | return days 67 | -------------------------------------------------------------------------------- /workalendar/usa/indiana.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | from __future__ import (absolute_import, division, print_function, 3 | unicode_literals) 4 | 5 | import warnings 6 | from datetime import date 7 | from ..core import MON, TUE, WED, THU, FRI, SAT 8 | from ..registry import iso_register 9 | 10 | from .core import UnitedStates 11 | 12 | 13 | @iso_register('US-IN') 14 | class Indiana(UnitedStates): 15 | """Indiana""" 16 | include_good_friday = True 17 | include_thanksgiving_friday = True 18 | thanksgiving_friday_label = "Lincoln's Birthday" 19 | include_federal_presidents_day = False 20 | label_washington_birthday_december = "Washington's Birthday (Observed)" 21 | include_election_day_even = True 22 | election_day_label = "General Election Day" 23 | 24 | def get_washington_birthday_december(self, year): 25 | """ 26 | Washington birthday observance 27 | Similar to Christmas Eve, but with special rules. 28 | It's only observed in Georgia. 29 | """ 30 | warnings.warn( 31 | "Washington birthday rules for Indiana State are confusing. " 32 | "Use this calendar with care") 33 | christmas_day = date(year, 12, 25).weekday() 34 | if christmas_day == MON: 35 | day = date(year, 12, 26) # TUE 36 | elif christmas_day == TUE: 37 | day = date(year, 12, 24) # MON 38 | elif christmas_day == WED: 39 | day = date(year, 12, 26) # TUE 40 | elif christmas_day == THU: 41 | day = date(year, 12, 26) # FRI 42 | elif christmas_day == FRI: 43 | day = date(year, 12, 24) # THU 44 | elif christmas_day == SAT: 45 | day = date(year, 12, 23) # THU 46 | else: # christmas_day == SUN: 47 | day = date(year, 12, 23) # FRI 48 | return (day, self.label_washington_birthday_december) 49 | 50 | def get_primary_election_day(self, year): 51 | """ 52 | Return the Primary Election Day 53 | 54 | FIXME: Wikipedia says it's a floating MON, but other sources say it's 55 | "the first Tuesday after the first Monday of May and every two years 56 | thereafter". 57 | """ 58 | first_monday_may = self.get_nth_weekday_in_month(year, 5, MON) 59 | tuesday_after = self.get_nth_weekday_in_month( 60 | year, 5, TUE, start=first_monday_may) 61 | return ( 62 | tuesday_after, 63 | "Primary Election Day" 64 | ) 65 | 66 | def get_variable_days(self, year): 67 | days = super(Indiana, self).get_variable_days(year) 68 | days.extend([ 69 | self.get_washington_birthday_december(year), 70 | ]) 71 | if (year % 2) == 0: 72 | days.append(self.get_primary_election_day(year)) 73 | return days 74 | -------------------------------------------------------------------------------- /workalendar/america/colombia.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | from __future__ import (absolute_import, division, print_function, 3 | unicode_literals) 4 | 5 | from datetime import timedelta, date 6 | 7 | from ..core import WesternCalendar, ChristianMixin, MON 8 | from ..registry import iso_register 9 | 10 | 11 | @iso_register('CO') 12 | class Colombia(WesternCalendar, ChristianMixin): 13 | "Colombia" 14 | FIXED_HOLIDAYS = WesternCalendar.FIXED_HOLIDAYS + ( 15 | (5, 1, "Labour Day"), 16 | (7, 20, "Independence Day"), 17 | (8, 7, "Boyacá Battle"), 18 | ) 19 | include_palm_sunday = True 20 | include_holy_thursday = True 21 | include_good_friday = True 22 | include_easter_sunday = True 23 | include_corpus_christi = True 24 | include_immaculate_conception = True 25 | 26 | def get_epiphany(self, year): 27 | base_day = date(year, 1, 6) 28 | return Colombia.get_first_weekday_after(base_day, MON) 29 | 30 | def get_saint_joseph(self, year): 31 | base_day = date(year, 3, 19) 32 | return Colombia.get_first_weekday_after(base_day, MON) 33 | 34 | def get_ascension(self, year): 35 | return self.get_easter_sunday(year) + timedelta(days=43) 36 | 37 | def get_corpus_christi(self, year): 38 | return self.get_easter_sunday(year) + timedelta(days=64) 39 | 40 | def get_sacred_heart(self, year): 41 | return self.get_easter_sunday(year) + timedelta(days=71) 42 | 43 | def get_saint_peter_and_saint_paul(self, year): 44 | base_day = date(year, 6, 29) 45 | return Colombia.get_first_weekday_after(base_day, MON) 46 | 47 | def get_assumption(self, year): 48 | base_day = date(year, 8, 15) 49 | return Colombia.get_first_weekday_after(base_day, MON) 50 | 51 | def get_race_day(self, year): 52 | base_day = date(year, 10, 12) 53 | return Colombia.get_first_weekday_after(base_day, MON) 54 | 55 | def get_all_saints(self, year): 56 | base_day = date(year, 11, 1) 57 | return Colombia.get_first_weekday_after(base_day, MON) 58 | 59 | def get_cartagena_independence(self, year): 60 | base_day = date(year, 11, 11) 61 | return Colombia.get_first_weekday_after(base_day, MON) 62 | 63 | def get_variable_days(self, year): 64 | days = super(Colombia, self).get_variable_days(year) 65 | days.extend([ 66 | (self.get_epiphany(year), "Epiphany"), 67 | (self.get_saint_joseph(year), "Saint Joseph"), 68 | (self.get_ascension(year), "Ascension"), 69 | (self.get_sacred_heart(year), "Sacred Heart"), 70 | (self.get_saint_peter_and_saint_paul(year), 71 | "Saint Peter and Saint Paul"), 72 | (self.get_assumption(year), "Assumption of Mary to Heaven"), 73 | (self.get_race_day(year), "Race Day"), 74 | (self.get_all_saints(year), "All Saints"), 75 | (self.get_cartagena_independence(year), 76 | "Cartagena's Independence"), 77 | ]) 78 | 79 | return days 80 | -------------------------------------------------------------------------------- /workalendar/asia/malaysia.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | from __future__ import (absolute_import, division, print_function, 3 | unicode_literals) 4 | from datetime import date 5 | 6 | from ..core import ChineseNewYearCalendar, WesternCalendar 7 | from ..core import IslamicMixin 8 | from ..registry import iso_register 9 | 10 | 11 | @iso_register('MY') 12 | class Malaysia(ChineseNewYearCalendar, WesternCalendar, IslamicMixin): 13 | "Malaysia" 14 | include_nuzul_al_quran = True 15 | include_eid_al_fitr = True 16 | length_eid_al_fitr = 2 17 | eid_al_fitr_label = "Hari Raya Puasa" 18 | include_day_of_sacrifice = True 19 | day_of_sacrifice_label = "Hari Raya Haji" 20 | include_islamic_new_year = True 21 | include_prophet_birthday = True 22 | 23 | FIXED_HOLIDAYS = WesternCalendar.FIXED_HOLIDAYS + ( 24 | (2, 1, "Federal Territory Day"), 25 | (5, 1, "Workers' Day"), 26 | (8, 31, "National Day"), 27 | (9, 16, "Malaysia Day"), 28 | (12, 25, "Christmas Day"), 29 | ) 30 | 31 | MSIA_DEEPAVALI = { 32 | 2010: date(2010, 11, 5), 33 | 2011: date(2011, 10, 26), 34 | 2012: date(2012, 11, 13), 35 | 2013: date(2013, 11, 2), 36 | 2014: date(2014, 10, 22), 37 | 2015: date(2015, 11, 10), 38 | 2016: date(2016, 10, 29), 39 | 2017: date(2017, 10, 18), 40 | 2018: date(2018, 11, 7), # This might change 41 | 2019: date(2019, 10, 27), # This might change 42 | 2020: date(2020, 11, 14), # This might change 43 | } 44 | 45 | MSIA_THAIPUSAM = { 46 | 2010: date(2010, 1, 30), 47 | 2011: date(2011, 1, 20), 48 | 2012: date(2012, 2, 7), 49 | 2013: date(2013, 1, 28), 50 | 2014: date(2014, 1, 17), 51 | 2015: date(2015, 2, 3), 52 | 2016: date(2016, 1, 25), 53 | 2017: date(2017, 2, 9), 54 | 2018: date(2018, 1, 31), # This might change 55 | } 56 | chinese_new_year_label = "First Day of Lunar New Year" 57 | include_chinese_second_day = True 58 | chinese_second_day_label = "Second Day of Lunar New Year" 59 | shift_sunday_holidays = True 60 | 61 | def get_variable_days(self, year): 62 | """ 63 | Malaysia variable days 64 | """ 65 | days = super(Malaysia, self).get_variable_days(year) 66 | 67 | # Vesak Day 68 | days.append( 69 | (ChineseNewYearCalendar.lunar(year, 4, 15), "Vesak Day"), 70 | ) 71 | 72 | # Add in Deepavali and Thaipusam (hardcoded dates, so no need to shift) 73 | msia_deepavali = self.MSIA_DEEPAVALI.get(year) 74 | if not msia_deepavali: 75 | mdmsg = 'Missing date for Malaysia Deepavali for year: %s' % year 76 | raise KeyError(mdmsg) 77 | days.append((msia_deepavali, 'Deepavali')) 78 | 79 | msia_thaipusam = self.MSIA_THAIPUSAM.get(year) 80 | if not msia_thaipusam: 81 | mtmsg = 'Missing date for Malaysia Thaipusam for year: %s' % year 82 | raise KeyError(mtmsg) 83 | days.append((msia_thaipusam, 'Thaipusam')) 84 | return days 85 | -------------------------------------------------------------------------------- /workalendar/tests/test_registry_europe.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | from unittest import TestCase 3 | from workalendar.europe import ( 4 | Austria, Belgium, Bulgaria, Croatia, Cyprus, CzechRepublic, Estonia, 5 | Denmark, Finland, France, 6 | # FranceAlsaceMoselle, # TODO: Should we add it to the registry? 7 | Greece, Hungary, Iceland, Ireland, Italy, Latvia, Lithuania, Luxembourg, 8 | Malta, Netherlands, Norway, Poland, Portugal, Romania, Russia, Slovakia, 9 | Slovenia, Spain, 10 | # Catalonia, # TODO: Add it to registry 11 | Sweden, Switzerland, Vaud, UnitedKingdom, UnitedKingdomNorthernIreland, 12 | ) 13 | 14 | # Germany 15 | from workalendar.europe import ( 16 | Germany, BadenWurttemberg, Bavaria, Berlin, Brandenburg, Bremen, 17 | Hamburg, Hesse, MecklenburgVorpommern, LowerSaxony, 18 | NorthRhineWestphalia, RhinelandPalatinate, Saarland, Saxony, 19 | SaxonyAnhalt, SchleswigHolstein, Thuringia 20 | ) 21 | 22 | from workalendar.registry import registry 23 | 24 | classes = (v for k, v in registry.region_registry.items()) 25 | classes = list(classes) 26 | 27 | GERMANY_REGION_CLASSES = ( 28 | BadenWurttemberg, Bavaria, Berlin, Brandenburg, Bremen, 29 | Hamburg, Hesse, MecklenburgVorpommern, LowerSaxony, 30 | NorthRhineWestphalia, RhinelandPalatinate, Saarland, Saxony, 31 | SaxonyAnhalt, SchleswigHolstein, Thuringia 32 | ) 33 | 34 | 35 | class RegistryEurope(TestCase): 36 | def test_europe(self): 37 | self.assertIn(Austria, classes) 38 | self.assertIn(Belgium, classes) 39 | self.assertIn(Bulgaria, classes) 40 | self.assertIn(Croatia, classes) 41 | self.assertIn(Cyprus, classes) 42 | self.assertIn(CzechRepublic, classes) 43 | self.assertIn(Estonia, classes) 44 | self.assertIn(Denmark, classes) 45 | self.assertIn(Finland, classes) 46 | self.assertIn(France, classes) 47 | self.assertIn(Greece, classes) 48 | self.assertIn(Hungary, classes) 49 | self.assertIn(Iceland, classes) 50 | self.assertIn(Ireland, classes) 51 | self.assertIn(Italy, classes) 52 | self.assertIn(Latvia, classes) 53 | self.assertIn(Lithuania, classes) 54 | self.assertIn(Luxembourg, classes) 55 | self.assertIn(Malta, classes) 56 | self.assertIn(Netherlands, classes) 57 | self.assertIn(Norway, classes) 58 | self.assertIn(Poland, classes) 59 | self.assertIn(Portugal, classes) 60 | self.assertIn(Romania, classes) 61 | self.assertIn(Russia, classes) 62 | self.assertIn(Slovakia, classes) 63 | self.assertIn(Slovenia, classes) 64 | self.assertIn(Spain, classes) 65 | self.assertIn(Sweden, classes) 66 | self.assertIn(Switzerland, classes) 67 | self.assertIn(Vaud, classes) 68 | self.assertIn(UnitedKingdom, classes) 69 | self.assertIn(UnitedKingdomNorthernIreland, classes) 70 | # Germany & Länders 71 | self.assertIn(Germany, classes) 72 | for klass in GERMANY_REGION_CLASSES: 73 | self.assertIn(klass, classes) 74 | 75 | def test_germany_subregion(self): 76 | # Get all the subregions 77 | classes = (v for k, v in registry.get_subregions('DE').items()) 78 | classes = list(classes) 79 | for klass in GERMANY_REGION_CLASSES: 80 | self.assertIn(klass, classes) 81 | -------------------------------------------------------------------------------- /workalendar/usa/__init__.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | from __future__ import absolute_import 3 | 4 | from .core import UnitedStates 5 | 6 | from .alabama import ( 7 | Alabama, AlabamaBaldwinCounty, AlabamaMobileCounty, AlabamaPerryCounty) 8 | from .alaska import Alaska 9 | from .arizona import Arizona 10 | from .arkansas import Arkansas 11 | from .california import California 12 | from .colorado import Colorado 13 | from .connecticut import Connecticut 14 | from .delaware import Delaware 15 | from .district_columbia import DistrictOfColumbia 16 | from .florida import Florida 17 | from .georgia import Georgia 18 | from .hawaii import Hawaii 19 | from .idaho import Idaho 20 | from .illinois import Illinois 21 | from .indiana import Indiana 22 | from .iowa import Iowa 23 | from .kansas import Kansas 24 | from .kentucky import Kentucky 25 | from .louisiana import Louisiana 26 | from .maine import Maine 27 | from .maryland import Maryland 28 | from .massachusetts import Massachusetts 29 | from .michigan import Michigan 30 | from .minnesota import Minnesota 31 | from .mississippi import Mississippi 32 | from .missouri import Missouri 33 | from .montana import Montana 34 | from .nebraska import Nebraska 35 | from .nevada import Nevada 36 | from .new_hampshire import NewHampshire 37 | from .new_jersey import NewJersey 38 | from .new_mexico import NewMexico 39 | from .new_york import NewYork 40 | from .north_carolina import NorthCarolina 41 | from .north_dakota import NorthDakota 42 | from .ohio import Ohio 43 | from .oklahoma import Oklahoma 44 | from .oregon import Oregon 45 | from .pennsylvania import Pennsylvania 46 | from .rhode_island import RhodeIsland 47 | from .south_carolina import SouthCarolina 48 | from .south_dakota import SouthDakota 49 | from .tennessee import Tennessee 50 | from .texas import TexasBase, Texas 51 | from .utah import Utah 52 | from .vermont import Vermont 53 | from .virginia import Virginia 54 | from .washington import Washington 55 | from .west_virginia import WestVirginia 56 | from .wisconsin import Wisconsin 57 | from .wyoming import Wyoming 58 | 59 | NONE, NEAREST_WEEKDAY, MONDAY = range(3) 60 | 61 | __all__ = [ 62 | 'UnitedStates', # Generic federal calendar 63 | 'Alabama', 64 | 'AlabamaBaldwinCounty', 'AlabamaMobileCounty', 'AlabamaPerryCounty', 65 | 'Alaska', 66 | 'Arizona', 67 | 'Arkansas', 68 | 'California', 69 | 'Colorado', 70 | 'Connecticut', 71 | 'Delaware', 72 | 'DistrictOfColumbia', 73 | 'Florida', 74 | 'Georgia', 75 | 'Hawaii', 76 | 'Idaho', 77 | 'Illinois', 78 | 'Indiana', 79 | 'Iowa', 80 | 'Kansas', 81 | 'Kentucky', 82 | 'Louisiana', 83 | 'Maine', 84 | 'Maryland', 85 | 'Massachusetts', 86 | 'Michigan', 87 | 'Minnesota', 88 | 'Mississippi', 89 | 'Missouri', 90 | 'Montana', 91 | 'Nebraska', 92 | 'Nevada', 93 | 'NewHampshire', 94 | 'NewJersey', 95 | 'NewMexico', 96 | 'NewYork', 97 | 'NorthCarolina', 98 | 'NorthDakota', 99 | 'Ohio', 100 | 'Oklahoma', 101 | 'Oregon', 102 | 'Pennsylvania', 103 | 'RhodeIsland', 104 | 'SouthCarolina', 105 | 'SouthDakota', 106 | 'Tennessee', 107 | 'TexasBase', 108 | 'Texas', 109 | 'Utah', 110 | 'Vermont', 111 | 'Virginia', 112 | 'Washington', 113 | 'WestVirginia', 114 | 'Wisconsin', 115 | 'Wyoming', 116 | ] 117 | -------------------------------------------------------------------------------- /docs/advanced.md: -------------------------------------------------------------------------------- 1 | # Advanced usage 2 | 3 | [Home](index.md) / [Basic usage](basic.md) / [ISO Registry](iso-registry.md) 4 | 5 | The following examples will better suit people willing to contribute to Workalendar or building their custom calendars. They use primitives and methods attached to the core Calendar class to enable computation of complex holidays, that is to say dates that are not fixed, not related to religious calendars (Christmas always happens on December 25th, right?). 6 | 7 | ## Find the following working day after a date 8 | 9 | It's a bit like a shortcut function to find the next working day after the given date. 10 | 11 | Please note that if the day you're entering is already a working day, that'll be the returned result. 12 | 13 | ```python 14 | >>> from datetime import date, datetime 15 | >>> from workalendar.europe import France 16 | >>> cal = France() 17 | >>> cal.find_following_working_day(date(2018, 7, 6)) # It's FRI 18 | datetime.date(2018, 7, 6) 19 | >>> cal.find_following_working_day(date(2018, 7, 7)) # SAT => next MON 20 | datetime.date(2018, 7, 9) 21 | ``` 22 | 23 | **WARNING**: this function doesn't take into account the existing holidays in the calendar. If you need this, use the ``add_working_days()`` function as described in the [Basic usage document](basic.md). 24 | 25 | ## Find the 4th Friday in November 26 | 27 | That's a puzzling question that we needed to address when we had to implement United States of America holidays calendars. Thanksgiving day, for example, which is on the 4th Friday in November... and many others, are defined as: 28 | 29 | > the ``nth`` ``Weekday name`` of the month of ``Month name`` 30 | 31 | Or even better for Election day, which is: 32 | 33 | > the Tuesday next after the first Monday in the month of November. 34 | 35 | We've got you covered with static methods in the core ``Calendar`` class. 36 | 37 | ```python 38 | >>> from workalendar.core import Calendar, FRI 39 | >>> Calendar.get_nth_weekday_in_month(2018, 11, FRI) # by default, find the first 40 | datetime.date(2018, 11, 2) 41 | >>> Calendar.get_nth_weekday_in_month(2018, 11, FRI, 4) # Thanksgiving 42 | datetime.date(2018, 11, 23) 43 | ``` 44 | 45 | If you want to find the 2nd Monday after the 4th of July, you can use the ``start`` argument: 46 | 47 | ```python 48 | >>> from datetime import date 49 | >>> from workalendar.core import Calendar, MON 50 | >>> Calendar.get_nth_weekday_in_month( 51 | 2018, # Year 52 | 7, # Month 53 | MON, # Monday 54 | 2, # The 2nd one 55 | start=date(2018, 7, 9)) 56 | datetime.date(2018, 7, 16) 57 | ``` 58 | 59 | ## Find the last Monday in the Month 60 | 61 | This one was a bit trickier, because it may happen that you have 4 or 5 `weekday name` in the same month. e.g: during the month of July 2018, you have 5 Mondays (2nd, 9th, 16th, 23rd, 30th), but only 4 Saturdays (7th, 14th, 21st, 28th). 62 | 63 | Same as above, there's a static method in the ``Calendar`` class: 64 | 65 | ```python 66 | >>> from workalendar.core import Calendar, MON, FRI 67 | >>> Calendar.get_last_weekday_in_month(2018, 7, MON) 68 | datetime.date(2018, 7, 30) 69 | >>> Calendar.get_last_weekday_in_month(2018, 7, FRI) 70 | datetime.date(2018, 7, 27) 71 | ``` 72 | 73 | ## Find the first Monday after a given date 74 | 75 | Colombia, as an example, states that the Epiphany Day is set to the "First Monday after the 6th of January". You may use the following function to find this specific formula: 76 | 77 | ```python 78 | >>> from workalendar.core import Calendar, MON 79 | >>> from datetime import date 80 | >>> jan_6th = date(2018, 1, 6) # It's a Saturday 81 | >>> Calendar.get_first_weekday_after(jan_6th, MON) 82 | datetime.date(2018, 1, 8) 83 | ``` 84 | 85 | [Home](index.md) / [Basic usage](basic.md) / [ISO Registry](iso-registry.md) 86 | -------------------------------------------------------------------------------- /workalendar/usa/texas.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | """ 3 | Texas module 4 | ============ 5 | 6 | This module presents two classes to handle the way state holidays are managed 7 | in Texas. 8 | 9 | The :class:`TexasBase` class gathers all available holidays for Texas, 10 | according to this document: 11 | http://www.statutes.legis.state.tx.us/Docs/GV/htm/GV.662.htm 12 | 13 | The :class:`Texas` class includes all national and state holidays, as described 14 | in the said document. This should be the "default" Texas calendar class, to be 15 | used in most cases. 16 | 17 | But if state holidays are supposed to be observed by most of the workforces, 18 | any employee can chose to skip one of these days and replace it by another. 19 | 20 | If at some point you need to create a specific calendar class based on Texas 21 | calendar, you can either use the :class:`TexasBase` class or directly the 22 | :class:`Texas` class and overwrite/override the :method:`get_fixed_holidays()` 23 | and/or :method:`get_variable_days()` to fit your needs. 24 | 25 | Example: 26 | 27 | .. code:: 28 | 29 | class TexasCustom(TexasBase): 30 | # This will include the confederate heroes day 31 | texas_include_confederate_heroes = True 32 | 33 | FIXED_HOLIDAYS = TexasBase.FIXED_HOLIDAYS + ( 34 | (7, 14, "Bastille Day!"), 35 | ) 36 | 37 | def get_variable_days(self, year): 38 | days = super(TexasCustom, self).get_variable_days(year) 39 | days.append( 40 | (self.get_nth_weekday_in_month(year, 1, 15), "Special Day") 41 | ) 42 | return days 43 | 44 | """ 45 | from __future__ import (absolute_import, division, print_function, 46 | unicode_literals) 47 | 48 | from datetime import date 49 | 50 | from ..registry import iso_register 51 | from .core import UnitedStates 52 | 53 | 54 | class TexasBase(UnitedStates): 55 | """Texas Base (w/o State holidays)""" 56 | include_columbus_day = False 57 | texas_include_confederate_heroes = False 58 | texas_include_independance_day = False 59 | texas_san_jacinto_day = False 60 | texas_emancipation_day = False 61 | texas_lyndon_johnson_day = False 62 | # Non-Texas-specific state holidays 63 | include_thanksgiving_friday = False 64 | include_christmas_eve = False 65 | include_boxing_day = False 66 | 67 | def get_fixed_holidays(self, year): 68 | days = super(TexasBase, self).get_fixed_holidays(year) 69 | if self.texas_include_confederate_heroes: 70 | days.append( 71 | (date(year, 1, 19), "Confederate Heroes Day") 72 | ) 73 | 74 | if self.texas_include_independance_day: 75 | days.append( 76 | (date(year, 3, 2), "Texas Independence Day") 77 | ) 78 | 79 | if self.texas_san_jacinto_day: 80 | days.append( 81 | (date(year, 4, 21), "San Jacinto Day") 82 | ) 83 | 84 | if self.texas_emancipation_day: 85 | days.append( 86 | (date(year, 6, 19), "Emancipation Day in Texas"), 87 | ) 88 | 89 | if self.texas_lyndon_johnson_day: 90 | days.append( 91 | (date(year, 8, 27), "Lyndon B. Jonhson Day"), 92 | ) 93 | return days 94 | 95 | 96 | @iso_register('US-TX') 97 | class Texas(TexasBase): 98 | """Texas""" 99 | texas_include_confederate_heroes = True 100 | texas_include_independance_day = True 101 | texas_san_jacinto_day = True 102 | texas_emancipation_day = True 103 | texas_lyndon_johnson_day = True 104 | include_thanksgiving_friday = True 105 | include_christmas_eve = True 106 | include_boxing_day = True 107 | -------------------------------------------------------------------------------- /workalendar/tests/test_registry_america.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | from unittest import TestCase 3 | 4 | from workalendar.america import ( 5 | Brazil, BrazilAcre, BrazilAlagoas, BrazilAmapa, BrazilAmazonas, 6 | BrazilBahia, BrazilCeara, BrazilDistritoFederal, BrazilEspiritoSanto, 7 | BrazilGoias, BrazilMaranhao, BrazilMatoGrosso, BrazilMatoGrossoDoSul, 8 | BrazilPara, BrazilParaiba, BrazilPernambuco, BrazilPiaui, 9 | BrazilRioDeJaneiro, BrazilRioGrandeDoNorte, BrazilRioGrandeDoSul, 10 | BrazilRondonia, BrazilRoraima, BrazilSantaCatarina, BrazilSaoPauloState, 11 | BrazilSergipe, BrazilTocantins, 12 | ) 13 | from workalendar.america import ( 14 | Canada, 15 | Ontario, 16 | Quebec, 17 | BritishColumbia, 18 | Alberta, 19 | Saskatchewan, 20 | Manitoba, 21 | NewBrunswick, 22 | NovaScotia, 23 | PrinceEdwardIsland, 24 | Newfoundland, 25 | Yukon, 26 | NorthwestTerritories, 27 | Nunavut, 28 | 29 | ) 30 | from workalendar.america import Chile, Colombia, Mexico, Panama 31 | 32 | from workalendar.registry import registry 33 | 34 | 35 | class RegistryAmerica(TestCase): 36 | 37 | def _check_brazil_states(self, classes): 38 | self.assertIn(BrazilAcre, classes) 39 | self.assertIn(BrazilAlagoas, classes) 40 | self.assertIn(BrazilAmapa, classes) 41 | self.assertIn(BrazilAmazonas, classes) 42 | self.assertIn(BrazilBahia, classes) 43 | self.assertIn(BrazilCeara, classes) 44 | self.assertIn(BrazilDistritoFederal, classes) 45 | self.assertIn(BrazilEspiritoSanto, classes) 46 | self.assertIn(BrazilGoias, classes) 47 | self.assertIn(BrazilMaranhao, classes) 48 | self.assertIn(BrazilMatoGrosso, classes) 49 | self.assertIn(BrazilMatoGrossoDoSul, classes) 50 | self.assertIn(BrazilPara, classes) 51 | self.assertIn(BrazilParaiba, classes) 52 | self.assertIn(BrazilPernambuco, classes) 53 | self.assertIn(BrazilPiaui, classes) 54 | self.assertIn(BrazilRioDeJaneiro, classes) 55 | self.assertIn(BrazilRioGrandeDoNorte, classes) 56 | self.assertIn(BrazilRioGrandeDoSul, classes) 57 | self.assertIn(BrazilRondonia, classes) 58 | self.assertIn(BrazilRoraima, classes) 59 | self.assertIn(BrazilSantaCatarina, classes) 60 | self.assertIn(BrazilSaoPauloState, classes) 61 | self.assertIn(BrazilSergipe, classes) 62 | self.assertIn(BrazilTocantins, classes) 63 | 64 | def _check_canada_provinces(self, classes): 65 | self.assertIn(Ontario, classes) 66 | self.assertIn(Quebec, classes) 67 | self.assertIn(BritishColumbia, classes) 68 | self.assertIn(Alberta, classes) 69 | self.assertIn(Saskatchewan, classes) 70 | self.assertIn(Manitoba, classes) 71 | self.assertIn(NewBrunswick, classes) 72 | self.assertIn(NovaScotia, classes) 73 | self.assertIn(PrinceEdwardIsland, classes) 74 | self.assertIn(Newfoundland, classes) 75 | self.assertIn(Yukon, classes) 76 | self.assertIn(NorthwestTerritories, classes) 77 | self.assertIn(Nunavut, classes) 78 | 79 | def test_america(self): 80 | classes = (v for k, v in registry.region_registry.items()) 81 | classes = list(classes) 82 | self.assertIn(Brazil, classes) 83 | self._check_brazil_states(classes) 84 | 85 | self.assertIn(Canada, classes) 86 | self._check_canada_provinces(classes) 87 | 88 | self.assertIn(Chile, classes) 89 | self.assertIn(Colombia, classes) 90 | self.assertIn(Mexico, classes) 91 | self.assertIn(Panama, classes) 92 | 93 | def test_brazil_subregion(self): 94 | classes = (v for k, v in registry.get_subregions('BR').items()) 95 | classes = list(classes) 96 | self._check_brazil_states(classes) 97 | 98 | def test_canada_subregion(self): 99 | classes = (v for k, v in registry.get_subregions('CA').items()) 100 | classes = list(classes) 101 | self._check_canada_provinces(classes) 102 | -------------------------------------------------------------------------------- /workalendar/tests/test_registry_usa.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | from unittest import TestCase 3 | from workalendar.usa import ( 4 | UnitedStates, 5 | Alabama, 6 | Alaska, 7 | Arizona, 8 | Arkansas, 9 | California, 10 | Colorado, 11 | Connecticut, 12 | Delaware, 13 | DistrictOfColumbia, 14 | Florida, 15 | Georgia, 16 | Hawaii, 17 | Idaho, 18 | Illinois, 19 | Indiana, 20 | Iowa, 21 | Kansas, 22 | Kentucky, 23 | Louisiana, 24 | Maine, 25 | Maryland, 26 | Massachusetts, 27 | Michigan, 28 | Minnesota, 29 | Mississippi, 30 | Missouri, 31 | Montana, 32 | Nebraska, 33 | Nevada, 34 | NewHampshire, 35 | NewJersey, 36 | NewMexico, 37 | NewYork, 38 | NorthCarolina, 39 | NorthDakota, 40 | Ohio, 41 | Oklahoma, 42 | Oregon, 43 | Pennsylvania, 44 | RhodeIsland, 45 | SouthCarolina, 46 | SouthDakota, 47 | Tennessee, 48 | Texas, 49 | Utah, 50 | Vermont, 51 | Virginia, 52 | Washington, 53 | WestVirginia, 54 | Wisconsin, 55 | Wyoming 56 | ) 57 | 58 | from workalendar.registry import registry 59 | 60 | 61 | class RegistryUsa(TestCase): 62 | 63 | def _check_all_states(self, classes): 64 | self.assertIn(Alabama, classes) 65 | self.assertIn(Alaska, classes) 66 | self.assertIn(Arizona, classes) 67 | self.assertIn(Arkansas, classes) 68 | self.assertIn(California, classes) 69 | self.assertIn(Colorado, classes) 70 | self.assertIn(Connecticut, classes) 71 | self.assertIn(Delaware, classes) 72 | self.assertIn(DistrictOfColumbia, classes) 73 | self.assertIn(Florida, classes) 74 | self.assertIn(Georgia, classes) 75 | self.assertIn(Hawaii, classes) 76 | self.assertIn(Idaho, classes) 77 | self.assertIn(Illinois, classes) 78 | self.assertIn(Indiana, classes) 79 | self.assertIn(Iowa, classes) 80 | self.assertIn(Kansas, classes) 81 | self.assertIn(Kentucky, classes) 82 | self.assertIn(Louisiana, classes) 83 | self.assertIn(Maine, classes) 84 | self.assertIn(Maryland, classes) 85 | self.assertIn(Massachusetts, classes) 86 | self.assertIn(Michigan, classes) 87 | self.assertIn(Minnesota, classes) 88 | self.assertIn(Mississippi, classes) 89 | self.assertIn(Missouri, classes) 90 | self.assertIn(Montana, classes) 91 | self.assertIn(Nebraska, classes) 92 | self.assertIn(Nevada, classes) 93 | self.assertIn(NewHampshire, classes) 94 | self.assertIn(NewJersey, classes) 95 | self.assertIn(NewMexico, classes) 96 | self.assertIn(NewYork, classes) 97 | self.assertIn(NorthCarolina, classes) 98 | self.assertIn(NorthDakota, classes) 99 | self.assertIn(Ohio, classes) 100 | self.assertIn(Oklahoma, classes) 101 | self.assertIn(Oregon, classes) 102 | self.assertIn(Pennsylvania, classes) 103 | self.assertIn(RhodeIsland, classes) 104 | self.assertIn(SouthCarolina, classes) 105 | self.assertIn(SouthDakota, classes) 106 | self.assertIn(Tennessee, classes) 107 | self.assertIn(Texas, classes) 108 | self.assertIn(Utah, classes) 109 | self.assertIn(Vermont, classes) 110 | self.assertIn(Virginia, classes) 111 | self.assertIn(Washington, classes) 112 | self.assertIn(WestVirginia, classes) 113 | self.assertIn(Wisconsin, classes) 114 | self.assertIn(Wyoming, classes) 115 | 116 | def test_usa_world(self): 117 | classes = (v for k, v in registry.region_registry.items()) 118 | classes = list(classes) 119 | self._check_all_states(classes) 120 | # On top of it, the core class 121 | self.assertIn(UnitedStates, classes) 122 | 123 | def test_usa_subregion(self): 124 | # Get all the subregions 125 | classes = (v for k, v in registry.get_subregions('US').items()) 126 | classes = list(classes) 127 | self._check_all_states(classes) 128 | -------------------------------------------------------------------------------- /workalendar/europe/germany.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | from __future__ import unicode_literals 3 | from datetime import date, timedelta 4 | from workalendar.core import WesternCalendar, ChristianMixin 5 | from workalendar.registry import iso_register 6 | 7 | 8 | @iso_register('DE') 9 | class Germany(WesternCalendar, ChristianMixin): 10 | 'Germany' 11 | 12 | FIXED_HOLIDAYS = WesternCalendar.FIXED_HOLIDAYS + ( 13 | (5, 1, "Labour Day"), 14 | (10, 3, "Day of German Unity"), 15 | ) 16 | 17 | include_easter_monday = True 18 | include_ascension = True 19 | include_whit_monday = True 20 | include_good_friday = True 21 | include_boxing_day = True 22 | boxing_day_label = "Second Christmas Day" 23 | # German-specific holiday 24 | include_reformation_day = False 25 | 26 | def get_reformation_day(self, year): 27 | """ 28 | Reformation Day is a fixed date. 29 | 30 | It's handled via the variable_days because it can be activated 31 | depending on the Länder or the year (see #150). 32 | """ 33 | day = date(year, 10, 31) 34 | return (day, "Reformation Day") 35 | 36 | def get_variable_days(self, year): 37 | days = super(Germany, self).get_variable_days(year) 38 | if self.include_reformation_day or year == 2017: 39 | days.append(self.get_reformation_day(year)) 40 | return days 41 | 42 | 43 | @iso_register('DE-BW') 44 | class BadenWurttemberg(Germany): 45 | "Baden-Wuerttemberg" 46 | 47 | include_epiphany = True 48 | include_corpus_christi = True 49 | include_all_saints = True 50 | 51 | 52 | @iso_register('DE-BY') 53 | class Bavaria(Germany): 54 | 'Bavaria' 55 | 56 | include_epiphany = True 57 | include_corpus_christi = True 58 | include_all_saints = True 59 | include_assumption = True 60 | 61 | 62 | @iso_register('DE-BE') 63 | class Berlin(Germany): 64 | 'Berlin' 65 | 66 | 67 | @iso_register('DE-BB') 68 | class Brandenburg(Germany): 69 | 'Brandenburg' 70 | 71 | include_easter_sunday = True 72 | include_reformation_day = True 73 | 74 | 75 | @iso_register('DE-HB') 76 | class Bremen(Germany): 77 | 'Bremen' 78 | 79 | 80 | @iso_register('DE-HH') 81 | class Hamburg(Germany): 82 | 'Hamburg' 83 | 84 | 85 | @iso_register('DE-HE') 86 | class Hesse(Germany): 87 | 'Hesse' 88 | 89 | include_corpus_christi = True 90 | 91 | 92 | @iso_register('DE-MV') 93 | class MecklenburgVorpommern(Germany): 94 | 'Mecklenburg-Western Pomerania' 95 | 96 | include_reformation_day = True 97 | 98 | 99 | @iso_register('DE-NI') 100 | class LowerSaxony(Germany): 101 | 'Lower Saxony' 102 | 103 | 104 | @iso_register('DE-NW') 105 | class NorthRhineWestphalia(Germany): 106 | 'North Rhine-Westphalia' 107 | 108 | include_corpus_christi = True 109 | include_all_saints = True 110 | 111 | 112 | @iso_register('DE-RP') 113 | class RhinelandPalatinate(Germany): 114 | 'Rhineland-Palatinate' 115 | 116 | include_corpus_christi = True 117 | include_all_saints = True 118 | 119 | 120 | @iso_register('DE-SL') 121 | class Saarland(Germany): 122 | 'Saarland' 123 | 124 | include_corpus_christi = True 125 | include_assumption = True 126 | include_all_saints = True 127 | 128 | 129 | @iso_register('DE-SN') 130 | class Saxony(Germany): 131 | 'Saxony' 132 | 133 | include_reformation_day = True 134 | 135 | def get_repentance_day(self, year): 136 | "Wednesday before November 23" 137 | day = date(year, 11, 22) 138 | while day.weekday() != 2: # 2=Wednesday 139 | day -= timedelta(days=1) 140 | return (day, "Repentance Day") 141 | 142 | def get_variable_days(self, year): 143 | days = super(Saxony, self).get_variable_days(year) 144 | days.append(self.get_repentance_day(year)) 145 | return days 146 | 147 | 148 | @iso_register('DE-ST') 149 | class SaxonyAnhalt(Germany): 150 | 'Saxony-Anhalt' 151 | 152 | include_epiphany = True 153 | include_reformation_day = True 154 | 155 | 156 | @iso_register('DE-SH') 157 | class SchleswigHolstein(Germany): 158 | 'Schleswig-Holstein' 159 | 160 | 161 | @iso_register('DE-TH') 162 | class Thuringia(Germany): 163 | 'Thuringia' 164 | 165 | include_reformation_day = True 166 | -------------------------------------------------------------------------------- /docs/basic.md: -------------------------------------------------------------------------------- 1 | # Basic usage 2 | 3 | [Home](index.md) / [Advanced usage](advanced.md) / [ISO Registry](iso-registry.md) 4 | 5 | Here are basic examples of what Workalendar can do for you. As an integrator or a simple Workalendar user, you will use these methods to retrieve calendars, and get basic outputs for a given date. 6 | 7 | ## Get holidays for a given country and year 8 | 9 | ```python 10 | >>> from workalendar.europe import France 11 | >>> cal = France() 12 | >>> cal.holidays(2012) 13 | [(datetime.date(2012, 1, 1), 'New year'), 14 | (datetime.date(2012, 4, 9), 'Easter Monday'), 15 | (datetime.date(2012, 5, 1), 'Labour Day'), 16 | (datetime.date(2012, 5, 8), 'Victory in Europe Day'), 17 | (datetime.date(2012, 5, 17), 'Ascension Day'), 18 | (datetime.date(2012, 5, 28), 'Whit Monday'), 19 | (datetime.date(2012, 7, 14), 'Bastille Day'), 20 | (datetime.date(2012, 8, 15), 'Assumption of Mary to Heaven'), 21 | (datetime.date(2012, 11, 1), "All Saints' Day"), 22 | (datetime.date(2012, 11, 11), 'Armistice Day'), 23 | (datetime.date(2012, 12, 25), 'Christmas')] 24 | ``` 25 | 26 | As you can see, the output is a simple list of tuples, with the first member being a `datetime.date` object, and the second one is the holiday label as a string. By design, we have chosen to stick to basic types so you can use them in your application and eventually compose your personal classes using them. 27 | 28 | ## Know if a day is a holiday or not 29 | 30 | ```python 31 | >>> from datetime import date 32 | >>> from workalendar.europe import France 33 | >>> cal = France() 34 | >>> cal.is_working_day(date(2012, 12, 25)) # it's Christmas 35 | False 36 | >>> cal.is_working_day(date(2012, 12, 30)) # it's a Sunday 37 | False 38 | >>> cal.is_working_day(date(2012, 12, 26)) # No Boxing day in France 39 | True 40 | ``` 41 | 42 | ## Compute the "nth" working day after a given date 43 | 44 | That was the basic use-case that started this library development: to answer to the following question: 45 | 46 | > This task is due in 5 working days, starting of today. Can we calculate that? 47 | 48 | In the following example, we want to calculate 5 working days after December the 23rd 2012. 49 | 50 | | Date | Weekday | Working Day? | Count | 51 | |:-----|:-------:|:------------:|:-----:| 52 | | 23rd | SUN | No | 0 | 53 | | 24th | MON | Yes | +1 | 54 | | 25th | TUE | No (XMas) | - | 55 | | 26th | WED | Yes | +2 | 56 | | 27th | THU | Yes | +3 | 57 | | 28th | FRI | Yes | +4 | 58 | | 29th | SAT | No (weekend) | - | 59 | | 30th | SUN | No (weekend) | - | 60 | | 31th | MON | Yes | +5 | 61 | 62 | ```python 63 | >>> from datetime import date 64 | >>> from workalendar.europe import France 65 | >>> cal = France() 66 | >>> cal.add_working_days(date(2012, 12, 23), 5) 67 | datetime.date(2012, 12, 31) 68 | ``` 69 | 70 | If we had requested the 6th day after the 23rd, it would have been January 2nd, 2013, because January 1st is New Year's Day. 71 | 72 | ## Calculate the number or working days between two given days 73 | 74 | Let's say you want to know how many working days there are between April 2nd and June 17th of 2018, in France. Use the following: 75 | 76 | ```python 77 | >>> from datetime import date 78 | >>> from workalendar.europe import France 79 | >>> cal = France() 80 | >>> cal.get_working_days_delta(date(2018, 4, 2), date(2018, 6, 17)) 81 | 50 82 | ``` 83 | 84 | 85 | ## Don't worry about the date types! 86 | 87 | For your convenience, we allow both `datetime.date` and `datetime.datetime` types when using the core functions. Example: 88 | 89 | ```python 90 | >>> from datetime import date, datetime 91 | >>> from workalendar.europe import France 92 | >>> cal = France() 93 | >>> cal.is_working_day(datetime(2012, 12, 25, 14, 0, 39)) 94 | False 95 | >>> cal.add_working_days(datetime(2012, 12, 23, 14, 0, 39), 5) 96 | datetime.datetime(2012, 12, 31) 97 | ``` 98 | 99 | If you really need it, you can use the ``add_working_days()`` with an option that will keep the ``datetime`` type: 100 | 101 | ```python 102 | >>> from datetime import date, datetime 103 | >>> from workalendar.europe import France 104 | >>> cal = France() 105 | >>> cal.add_working_days(datetime(2012, 12, 23, 14, 0, 39), 5, keep_datetime=True) 106 | datetime.datetime(2012, 12, 31, 14, 0, 39) 107 | ``` 108 | 109 | [Home](index.md) / [Advanced usage](advanced.md) / [ISO Registry](iso-registry.md) 110 | -------------------------------------------------------------------------------- /workalendar/registry.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | from __future__ import absolute_import, unicode_literals 3 | 4 | from collections import OrderedDict 5 | 6 | 7 | class IsoRegistry(object): 8 | """ 9 | Registry for all calendars retrievable 10 | by ISO 3166-2 codes associated with countries 11 | where they are used as official calendars. 12 | 13 | Two letter codes are favored for any subdivisions. 14 | """ 15 | 16 | def __init__(self): 17 | self.region_registry = OrderedDict() 18 | 19 | def _code_elements(self, iso_code): 20 | code_elements = iso_code.split('-') 21 | is_subregion = False 22 | if len(code_elements) > 1: 23 | is_subregion = True 24 | return code_elements, is_subregion 25 | 26 | def register(self, iso_code, cls): 27 | self.region_registry[iso_code] = cls 28 | 29 | def get_calendar_class(self, iso_code): 30 | """ 31 | Retrieves calendar class associated with given ``iso_code``. 32 | 33 | If calendar of subdivision is not registered 34 | (for subdivision like ISO codes, e.g. GB-ENG) 35 | returns calendar of containing region 36 | (e.g. United Kingdom for ISO code GB) if it's available. 37 | 38 | :rtype: Calendar 39 | """ 40 | code_elements, is_subregion = self._code_elements(iso_code) 41 | if is_subregion and iso_code not in self.region_registry: 42 | # subregion code not in region_registry 43 | code = code_elements[0] 44 | else: 45 | # subregion code in region_registry or is not a subregion 46 | code = iso_code 47 | return self.region_registry.get(code) 48 | 49 | def get_subregions(self, iso_code): 50 | """ 51 | Returns subregion calendar classes for given region iso_code. 52 | 53 | >>> registry = IsoRegistry() 54 | >>> # assuming calendars registered are: DE, DE-HH, DE-BE 55 | >>> registry.get_subregions('DE') 56 | {'DE-HH': , 57 | 'DE-BE': } 58 | :rtype dict 59 | :return dict where keys are ISO codes strings 60 | and values are calendar classes 61 | """ 62 | items = OrderedDict() 63 | for key, value in self.region_registry.items(): 64 | code_elements, is_subregion = self._code_elements(key) 65 | if is_subregion and code_elements[0] == iso_code: 66 | items[key] = value 67 | return items 68 | 69 | def items(self, region_codes, include_subregions=False): 70 | """ 71 | Returns calendar classes for regions 72 | 73 | :param region_codes list of ISO codes for selected regions 74 | :param include_subregions boolean if subregions 75 | of selected regions should be included in result 76 | :rtype dict 77 | :return dict where keys are ISO codes strings 78 | and values are calendar classes 79 | """ 80 | items = OrderedDict() 81 | for code in region_codes: 82 | try: 83 | items[code] = self.region_registry[code] 84 | except KeyError: 85 | continue 86 | if include_subregions: 87 | items.update(self.get_subregions(code)) 88 | return items 89 | 90 | 91 | registry = IsoRegistry() 92 | 93 | 94 | def iso_register(iso_code): 95 | """ 96 | Registers Calendar class as country or region in IsoRegistry. 97 | 98 | Registered country must set class variables ``iso`` using this decorator. 99 | 100 | >>> from workalendar.core import Calendar 101 | >>> @iso_register('MC-MR') 102 | >>> class MyRegion(Calendar): 103 | >>> 'My Region' 104 | 105 | Region calendar is then retrievable from registry: 106 | 107 | >>> calendar = registry.get_calendar_class('MC-MR') 108 | """ 109 | def wrapper(cls): 110 | registry.register(iso_code, cls) 111 | return cls 112 | return wrapper 113 | 114 | 115 | # Europe Countries 116 | from workalendar.europe import * # noqa 117 | # United States of America 118 | from workalendar.usa import * # noqa 119 | # American continent outside of USA 120 | from workalendar.america import * # noqa 121 | # African continent 122 | from workalendar.africa import * # noqa 123 | from workalendar.asia import * # noqa 124 | from workalendar.oceania import * # noqa 125 | -------------------------------------------------------------------------------- /README.rst: -------------------------------------------------------------------------------- 1 | =========== 2 | Workalendar 3 | =========== 4 | 5 | Overview 6 | ======== 7 | 8 | Workalendar is a Python module that offers classes able to handle calendars, 9 | list legal / religious holidays and gives working-day-related computation 10 | functions. 11 | 12 | Status 13 | ====== 14 | 15 | This library is ready for production, although we may warn eventual users: some calendars may not be up-to-date, and this library doesn't cover all the existing countries on earth (yet). 16 | 17 | If you spot any bug or wish to add a calendar, please refer to the `Contributing doc `_. 18 | 19 | Usage sample 20 | ============ 21 | 22 | .. code-block:: python 23 | 24 | >>> from datetime import date 25 | >>> from workalendar.europe import France 26 | >>> cal = France() 27 | >>> cal.holidays(2012) 28 | [(datetime.date(2012, 1, 1), 'New year'), 29 | (datetime.date(2012, 4, 9), 'Easter Monday'), 30 | (datetime.date(2012, 5, 1), 'Labour Day'), 31 | (datetime.date(2012, 5, 8), 'Victory in Europe Day'), 32 | (datetime.date(2012, 5, 17), 'Ascension Day'), 33 | (datetime.date(2012, 5, 28), 'Whit Monday'), 34 | (datetime.date(2012, 7, 14), 'Bastille Day'), 35 | (datetime.date(2012, 8, 15), 'Assumption of Mary to Heaven'), 36 | (datetime.date(2012, 11, 1), "All Saints' Day"), 37 | (datetime.date(2012, 11, 11), 'Armistice Day'), 38 | (datetime.date(2012, 12, 25), 'Christmas')] 39 | >>> cal.is_working_day(date(2012, 12, 25)) # it's Christmas 40 | False 41 | >>> cal.is_working_day(date(2012, 12, 30)) # it's Sunday 42 | False 43 | >>> cal.is_working_day(date(2012, 12, 26)) 44 | True 45 | >>> cal.add_working_days(date(2012, 12, 23), 5) # 5 working days after Xmas 46 | datetime.date(2012, 12, 31) 47 | 48 | For a more complete documentation and advanced usage, go to 49 | `the official workalendar documentation `_. 50 | 51 | External dependencies 52 | ===================== 53 | 54 | You may want to install ``python-dev`` and/or ``python3-dev`` on your machine to 55 | either run the installation or run tests via tox. 56 | 57 | Workalendar has been tested on Python 2.7, 3.4, 3.5, 3.6, 3.7. 58 | 59 | Tests 60 | ===== 61 | 62 | Travis status: 63 | 64 | .. image:: https://api.travis-ci.org/peopledoc/workalendar.png 65 | 66 | 67 | To run test, just install tox with ``pip install tox`` and run:: 68 | 69 | tox 70 | 71 | from the command line. 72 | 73 | 74 | Available Calendars 75 | =================== 76 | 77 | Europe 78 | ------ 79 | 80 | * Austria 81 | * Belgium 82 | * Bulgaria 83 | * Croatia 84 | * Cyprus 85 | * Czech Republic 86 | * Denmark 87 | * Estonia 88 | * European Central Bank 89 | * Finland 90 | * France 91 | * France (Alsace / Moselle) 92 | * Germany 93 | * Greece 94 | * Hungary 95 | * Iceland 96 | * Ireland 97 | * Italy 98 | * Latvia 99 | * Lithuania 100 | * Luxembourg 101 | * Malta 102 | * Netherlands 103 | * Norway 104 | * Poland 105 | * Portugal 106 | * Romania 107 | * Russia 108 | * Slovakia 109 | * Sweden 110 | * United Kingdom (incl. Northern Ireland) 111 | * Spain (incl. Catalonia) 112 | * Slovenia 113 | * Switzerland 114 | * Vaud 115 | 116 | America 117 | ------- 118 | 119 | * Brazil (all states, cities and for bank transactions, except the city of Viana) 120 | * Chile 121 | * Colombia 122 | * Mexico 123 | * Panama 124 | * United States of America (including state holidays) 125 | * Canada (including provincial and territory holidays) 126 | 127 | Asia 128 | ---- 129 | 130 | * Hong Kong 131 | * Japan 132 | * Malaysia 133 | * Qatar 134 | * Singapore 135 | * South Korea 136 | * Taiwan 137 | 138 | Oceania 139 | ------- 140 | 141 | * Australia (incl. its different states) 142 | * Marshall Islands 143 | 144 | Africa 145 | ------ 146 | 147 | * Algeria 148 | * Angola 149 | * Benin 150 | * Ivory Coast 151 | * Madagascar 152 | * São Tomé 153 | * South Africa 154 | 155 | And more to come (I hope!) 156 | 157 | Caveats 158 | ======= 159 | 160 | Please take note that some calendars are not 100% accurate. The most common 161 | example is the Islamic calendar, where some computed holidays are not exactly on 162 | the same official day decided by religious authorities, and this may vary 163 | country by country. Whenever it's possible, try to adjust your results with 164 | the official data provided by the adequate authorities. 165 | 166 | Contributing 167 | ============ 168 | 169 | Please read our `CONTRIBUTING.rst `_ 170 | document to discover how you can contribute to ``workalendar``. Pull-requests 171 | are very welcome. 172 | 173 | License 174 | ======= 175 | 176 | This library is published under the terms of the MIT License. Please check the 177 | LICENSE file for more details. 178 | -------------------------------------------------------------------------------- /docs/iso-registry.md: -------------------------------------------------------------------------------- 1 | # The ISO registry 2 | 3 | [Home](index.md) / [Basic usage](basic.md) / [Advanced usage](advanced.md) 4 | 5 | As of version 3.0 (August/September 2018), we have introduced a global calendar registry for calendars related to a country or a region that belongs to the [ISO 3166-1](https://en.wikipedia.org/wiki/ISO_3166-1) or the [ISO 3166-2](https://en.wikipedia.org/wiki/ISO_3166-2) for sub-regions (such as USA states, Australian territories or Canadian provinces and such). 6 | 7 | ## Iterate over the whole registry 8 | 9 | ```python 10 | >>> from workalendar.registry import registry 11 | >>> for code, calendar_class in registry.region_registry.items(): 12 | ... print(u"`{}` is code for '{}'".format(code, calendar_class.name)) 13 | `AT` is code for 'Austria' 14 | `BE` is code for 'Belgium' 15 | `BG` is code for 'Bulgaria' 16 | `HR` is code for 'Croatia' 17 | `CY` is code for 'Cyprus' 18 | `CZ` is code for 'Czech Republic' 19 | `EE` is code for 'Estonia' 20 | `DK` is code for 'Denmark' 21 | `FI` is code for 'Finland' 22 | `FR` is code for 'France' 23 | ... continued 24 | ``` 25 | 26 | The `registry.region_registry` is an `OrderedDict` object, with the ISO code as a key, and the calendar class as the value. As a "workalendar standard", **every** calendar in the registry has a `name` property (derived from the docstring), so you'd probably be able to build a user-friendly list of available calendars, for a dropdown list, for example. 27 | 28 | ## Retrieve a collection of regions 29 | 30 | Let's say you'd need only a subset of the ISO registry. For example, France, Switzerland and Canada calendars. You can use the method `items()` to filter only the calendars you want. 31 | 32 | ```python 33 | >>> registry.items(['FR', 'CH', 'CA']) 34 | OrderedDict([('FR', workalendar.europe.france.France), 35 | ('CH', workalendar.europe.switzerland.Switzerland), 36 | ('CA', workalendar.america.canada.Canada)]) 37 | ``` 38 | 39 | Also, if you want those regions **and** their subregions, you can use the `include_subregions` flag: 40 | 41 | ```python 42 | >>> registry.items(['FR', 'CH', 'CA'], include_subregions=True) 43 | OrderedDict([('FR', workalendar.europe.france.France), 44 | ('CH', workalendar.europe.switzerland.Switzerland), 45 | (u'CH-VD', workalendar.europe.switzerland.Vaud), 46 | ('CA', workalendar.america.canada.Canada), 47 | (u'CA-ON', workalendar.america.canada.Ontario), 48 | (u'CA-QC', workalendar.america.canada.Quebec), 49 | (u'CA-BC', workalendar.america.canada.BritishColumbia), 50 | (u'CA-AB', workalendar.america.canada.Alberta), 51 | (u'CA-SK', workalendar.america.canada.Saskatchewan), 52 | (u'CA-MB', workalendar.america.canada.Manitoba), 53 | (u'CA-NB', workalendar.america.canada.NewBrunswick), 54 | (u'CA-NS', workalendar.america.canada.NovaScotia), 55 | (u'CA-PE', workalendar.america.canada.PrinceEdwardIsland), 56 | (u'CA-NL', workalendar.america.canada.Newfoundland), 57 | (u'CA-YT', workalendar.america.canada.Yukon), 58 | (u'CA-NT', workalendar.america.canada.NorthwestTerritories), 59 | (u'CA-NU', workalendar.america.canada.Nunavut)]) 60 | ``` 61 | 62 | *Note*: if any of the codes is unknown, this function won't raise an error. 63 | 64 | ## Select only one calendar 65 | 66 | Let's say that we only know the ISO code for Switzerland (`CH`). If we want to compute holidays for Switzerland in 2018, we can do as follows: 67 | 68 | ```python 69 | >>> registry.get_calendar_class('CH') 70 | >>> CalendarClass = registry.get_calendar_class('CH') 71 | >>> calendar = CalendarClass() 72 | >>> calendar.holidays(2018) 73 | [(datetime.date(2018, 1, 1), 'New year'), 74 | (datetime.date(2018, 1, 2), u"Berchtold's Day"), 75 | (datetime.date(2018, 3, 30), 'Good Friday'), 76 | (datetime.date(2018, 4, 1), 'Easter Sunday'), 77 | (datetime.date(2018, 4, 2), 'Easter Monday'), 78 | (datetime.date(2018, 5, 1), u'Labour Day'), 79 | (datetime.date(2018, 5, 10), 'Ascension Thursday'), 80 | (datetime.date(2018, 5, 20), 'Whit Sunday'), 81 | (datetime.date(2018, 5, 21), 'Whit Monday'), 82 | (datetime.date(2018, 8, 1), u'National Holiday'), 83 | (datetime.date(2018, 12, 25), 'Christmas Day'), 84 | (datetime.date(2018, 12, 26), 'Boxing Day')] 85 | ``` 86 | 87 | *Note*: this function would return `None` if the code is unknown. 88 | 89 | 90 | ## Select only sub-regions 91 | 92 | As an example, the United States of America, or Australia and others are divided into "sub-regions" (states, provinces, territories, etc). There are usually Federal or State holidays, and variants depending on the region you're targeting. 93 | 94 | ```python 95 | >>> registry.get_subregions('AU') 96 | OrderedDict([(u'AU-ACT', 97 | workalendar.oceania.australia.AustralianCapitalTerritory), 98 | (u'AU-NSW', workalendar.oceania.australia.NewSouthWales), 99 | (u'AU-NT', workalendar.oceania.australia.NorthernTerritory), 100 | (u'AU-QLD', workalendar.oceania.australia.Queensland), 101 | (u'AU-SA', workalendar.oceania.australia.SouthAustralia), 102 | (u'AU-TAS', workalendar.oceania.australia.Tasmania), 103 | (u'AU-VIC', workalendar.oceania.australia.Victoria), 104 | (u'AU-WA', workalendar.oceania.australia.WesternAustralia)]) 105 | ``` 106 | 107 | *Note*: this function will return an empty `OrderedDict` if the code is unknown. 108 | 109 | [Home](index.md) / [Basic usage](basic.md) / [Advanced usage](advanced.md) 110 | -------------------------------------------------------------------------------- /workalendar/tests/test_america.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | from datetime import date 3 | from workalendar.tests import GenericCalendarTest 4 | from workalendar.america import Colombia 5 | from workalendar.america import Mexico, Chile, Panama 6 | 7 | 8 | class ChileTest(GenericCalendarTest): 9 | cal_class = Chile 10 | 11 | def test_holidays_2013(self): 12 | holidays = self.cal.holidays_set(2013) 13 | self.assertIn(date(2013, 1, 1), holidays) 14 | self.assertIn(date(2013, 3, 29), holidays) 15 | self.assertIn(date(2013, 3, 30), holidays) 16 | self.assertIn(date(2013, 5, 1), holidays) 17 | self.assertIn(date(2013, 5, 21), holidays) 18 | self.assertIn(date(2013, 6, 29), holidays) 19 | self.assertIn(date(2013, 7, 16), holidays) 20 | self.assertIn(date(2013, 8, 15), holidays) 21 | self.assertIn(date(2013, 9, 18), holidays) 22 | self.assertIn(date(2013, 9, 19), holidays) 23 | self.assertIn(date(2013, 9, 20), holidays) 24 | self.assertIn(date(2013, 10, 12), holidays) 25 | self.assertIn(date(2013, 10, 31), holidays) 26 | self.assertIn(date(2013, 11, 1), holidays) 27 | self.assertIn(date(2013, 12, 8), holidays) 28 | self.assertIn(date(2013, 12, 25), holidays) 29 | self.assertIn(date(2013, 12, 31), holidays) 30 | 31 | def test_reformation_day(self): 32 | holidays = self.cal.holidays_set(2012) 33 | self.assertNotIn(date(2012, 10, 31), holidays) 34 | self.assertIn(date(2012, 11, 2), holidays) 35 | # 36 | holidays = self.cal.holidays_set(2017) 37 | self.assertNotIn(date(2017, 10, 31), holidays) 38 | self.assertIn(date(2017, 10, 27), holidays) 39 | 40 | 41 | class ColombiaTest(GenericCalendarTest): 42 | cal_class = Colombia 43 | 44 | def test_holidays_2015(self): 45 | holidays = self.cal.holidays_set(2015) 46 | self.assertIn(date(2015, 1, 1), holidays) 47 | self.assertIn(date(2015, 1, 12), holidays) 48 | self.assertIn(date(2015, 3, 23), holidays) 49 | self.assertIn(date(2015, 3, 29), holidays) 50 | self.assertIn(date(2015, 4, 2), holidays) 51 | self.assertIn(date(2015, 4, 3), holidays) 52 | self.assertIn(date(2015, 4, 5), holidays) 53 | self.assertIn(date(2015, 5, 1), holidays) 54 | self.assertIn(date(2015, 5, 18), holidays) 55 | self.assertIn(date(2015, 6, 8), holidays) 56 | self.assertIn(date(2015, 6, 15), holidays) 57 | self.assertIn(date(2015, 6, 29), holidays) 58 | self.assertIn(date(2015, 7, 20), holidays) 59 | self.assertIn(date(2015, 8, 7), holidays) 60 | self.assertIn(date(2015, 8, 17), holidays) 61 | self.assertIn(date(2015, 10, 12), holidays) 62 | self.assertIn(date(2015, 11, 2), holidays) 63 | self.assertIn(date(2015, 11, 16), holidays) 64 | self.assertIn(date(2015, 12, 8), holidays) 65 | self.assertIn(date(2015, 12, 25), holidays) 66 | self.assertEqual(len(holidays), 20) 67 | 68 | 69 | class MexicoTest(GenericCalendarTest): 70 | cal_class = Mexico 71 | 72 | def test_holidays_2013(self): 73 | holidays = self.cal.holidays_set(2013) 74 | self.assertIn(date(2013, 1, 1), holidays) 75 | self.assertIn(date(2013, 2, 4), holidays) # Constitution day 76 | self.assertIn(date(2013, 3, 18), holidays) # Benito Juárez's birthday 77 | self.assertIn(date(2013, 5, 1), holidays) # Labour day 78 | self.assertIn(date(2013, 9, 16), holidays) # Independence day 79 | self.assertIn(date(2013, 11, 18), holidays) # Revolution day 80 | self.assertIn(date(2013, 12, 25), holidays) # XMas 81 | 82 | def test_shift_to_monday(self): 83 | holidays = self.cal.holidays_set(2017) 84 | # New year on Sunday -> shift 85 | self.assertIn(date(2017, 1, 2), holidays) 86 | holidays = self.cal.holidays_set(2016) 87 | # XMas on sunday -> shift to monday 88 | self.assertIn(date(2016, 12, 26), holidays) 89 | # Same for Labour day 90 | self.assertIn(date(2016, 5, 2), holidays) 91 | 92 | def test_shift_to_friday(self): 93 | holidays = self.cal.holidays_set(2021) 94 | # January 1st 2022 is a saturday, so we shift to friday 95 | self.assertIn(date(2021, 12, 31), holidays) 96 | # Same for Labour day 97 | self.assertIn(date(2021, 4, 30), holidays) 98 | holidays = self.cal.holidays_set(2021) 99 | # December 25th, 2022 is a saturday, so we shift to friday 100 | self.assertIn(date(2021, 12, 24), holidays) 101 | 102 | 103 | class PanamaTest(GenericCalendarTest): 104 | cal_class = Panama 105 | 106 | def test_holidays_2013(self): 107 | holidays = self.cal.holidays_set(2013) 108 | self.assertIn(date(2013, 1, 1), holidays) 109 | self.assertIn(date(2013, 1, 9), holidays) # Martyrs day 110 | self.assertIn(date(2013, 2, 12), holidays) # carnival tuesday 111 | self.assertIn(date(2013, 3, 29), holidays) # good friday 112 | self.assertIn(date(2013, 3, 30), holidays) # easter saturday 113 | self.assertIn(date(2013, 3, 31), holidays) # easter sunday 114 | self.assertIn(date(2013, 5, 1), holidays) # labour day 115 | self.assertIn(date(2013, 11, 3), holidays) # independence day 116 | self.assertIn(date(2013, 11, 5), holidays) # colon day 117 | # Shout in Villa de los Santos 118 | self.assertIn(date(2013, 11, 10), holidays) 119 | self.assertIn(date(2013, 12, 2), holidays) # Independence from spain 120 | self.assertIn(date(2013, 12, 8), holidays) # mother day 121 | self.assertIn(date(2013, 12, 25), holidays) # XMas 122 | -------------------------------------------------------------------------------- /workalendar/africa/south_africa.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | from __future__ import (absolute_import, division, print_function, 3 | unicode_literals) 4 | 5 | from datetime import timedelta, date 6 | 7 | from ..core import WesternCalendar 8 | from ..core import SUN, MON, FRI 9 | from ..core import ChristianMixin 10 | from ..exceptions import CalendarError 11 | from ..registry import iso_register 12 | 13 | 14 | @iso_register('ZA') 15 | class SouthAfrica(WesternCalendar, ChristianMixin): 16 | "South Africa" 17 | include_good_friday = True 18 | include_christmas = True 19 | 20 | def holidays(self, year=None): 21 | if year < 1910: 22 | raise CalendarError("It's not possible to compute holidays prior" 23 | " to 1910 for South Africa.") 24 | return super(SouthAfrica, self).holidays(year) 25 | 26 | def get_easter_monday_or_family_day(self, year): 27 | if year < 1980: 28 | label = "Easter Monday" 29 | else: 30 | label = "Family Day" 31 | return (self.get_easter_monday(year), label) 32 | 33 | def get_fixed_holidays(self, year): 34 | days = super(SouthAfrica, self).get_fixed_holidays(year) 35 | if year >= 1990: 36 | days.append((date(year, 3, 21), 'Human Rights Day')) 37 | 38 | # Van Riebeeck's day & Founder's day 39 | if year >= 1952 and year <= 1973: 40 | days.append((date(year, 4, 6), "Van Riebeeck's Day")) 41 | if year >= 1980 and year <= 1994: 42 | days.append((date(year, 4, 6), "Founder's Day")) 43 | 44 | if year >= 1994: 45 | days.append((date(year, 4, 27), "Freedom Day")) 46 | 47 | # Workers day established in 1995 to May 1st 48 | if year >= 1995: 49 | days.append((date(year, 5, 1), "Workers' Day")) 50 | 51 | if year <= 1951: 52 | days.append((date(year, 5, 24), "Victoria Day / Empire Day")) 53 | 54 | # May 31st: Union Day & Republic Day 55 | if year <= 1960: 56 | days.append((date(year, 5, 31), "Union Day")) 57 | elif year <= 1993: 58 | days.append((date(year, 5, 31), "Republic Day")) 59 | 60 | if year >= 1995: 61 | days.append((date(year, 6, 16), "Youth Day")) 62 | 63 | if year > 1960 and year <= 1973: 64 | days.append((date(year, 7, 10), "Family Day")) 65 | 66 | if year >= 1995: 67 | days.append((date(year, 8, 9), "National Women's Day")) 68 | days.append((date(year, 9, 24), "Heritage Day")) 69 | 70 | if year >= 1952 and year <= 1993: 71 | days.append((date(year, 10, 10), "Kruger Day")) 72 | 73 | if year <= 1951: 74 | december_16th_label = "Dingaan's Day" 75 | elif 1952 <= year <= 1979: 76 | december_16th_label = "Day of the Covenant" 77 | elif 1980 <= year <= 1994: 78 | december_16th_label = "Day of the Vow" 79 | else: 80 | december_16th_label = "Day of Reconciliation" 81 | days.append((date(year, 12, 16), december_16th_label)) 82 | 83 | # Boxing day renamed 84 | boxing_day_label = "Boxing Day" 85 | if year >= 1980: 86 | boxing_day_label = "Day of Goodwill" 87 | days.append((date(year, 12, 26), boxing_day_label)) 88 | 89 | return days 90 | 91 | def get_variable_days(self, year): 92 | days = super(SouthAfrica, self).get_variable_days(year) 93 | 94 | days.append(self.get_easter_monday_or_family_day(year)) 95 | 96 | # Workers day was first friday of may 1987-1989 97 | if 1987 <= year <= 1989: 98 | days.append( 99 | (self.get_nth_weekday_in_month(year, 5, FRI), "Workers' Day") 100 | ) 101 | 102 | if year <= 1993: 103 | days.append((self.get_ascension_thursday(year), "Ascension Day")) 104 | 105 | # Queen's Birthday on the 2nd Monday of july 1952-1960 106 | if 1952 <= year <= 1960: 107 | days.append(( 108 | self.get_nth_weekday_in_month(year, 7, MON, 2), 109 | "Queen's Birthday" 110 | )) 111 | 112 | # King's Birthday on the first Monday of August 1910-1951 113 | if 1910 <= year <= 1951: 114 | days.append(( 115 | self.get_nth_weekday_in_month(year, 8, MON), 116 | "King's Birthday" 117 | )) 118 | 119 | if year >= 1952 and year <= 1979: 120 | days.append((self.get_nth_weekday_in_month(year, 9, MON), 121 | "Settlers' Day")) 122 | return days 123 | 124 | def get_calendar_holidays(self, year): 125 | days = super(SouthAfrica, self).get_calendar_holidays(year) 126 | # compute shifting days 127 | for holiday, label in days: 128 | if holiday.weekday() == SUN: 129 | days.append(( 130 | holiday + timedelta(days=1), 131 | "%s substitute" % label 132 | )) 133 | 134 | # Other one-offs. Don't shift these 135 | if year == 1999: 136 | days.append((date(year, 6, 2), "National Elections")) 137 | days.append((date(year, 12, 31), "Y2K")) 138 | if year == 2000: 139 | # 2 January 2000 public holidays to accommodate the Y2K changeover, 140 | # 3 January 2000 because the previous holiday was a Sunday 141 | days.append((date(year, 1, 2), "Y2K")) 142 | days.append((date(year, 1, 3), "Y2K")) 143 | if year == 2001: 144 | days.append((date(year, 1, 2), "Y2K")) 145 | if year == 2004: 146 | days.append((date(year, 4, 14), "National Elections")) 147 | if year == 2006: 148 | days.append((date(year, 3, 1), "Local Elections")) 149 | if year == 2008: 150 | # 2 May 2008 was declared a public holiday when Human Rights Day 151 | # and Good Friday coincided on 21 March 2008 152 | days.append((date(year, 5, 2), "Special Human Rights")) 153 | if year == 2009: 154 | days.append((date(year, 4, 22), "National Elections")) 155 | if year == 2011: 156 | days.append((date(year, 5, 18), "Local Elections")) 157 | days.append((date(year, 12, 27), "Special Day of Goodwill")) 158 | if year == 2014: 159 | days.append((date(year, 5, 7), "National Elections")) 160 | if year == 2016: 161 | days.append((date(year, 8, 3), "Local Elections")) 162 | 163 | return days 164 | -------------------------------------------------------------------------------- /CONTRIBUTING.rst: -------------------------------------------------------------------------------- 1 | ========================= 2 | Contribute to Workalendar 3 | ========================= 4 | 5 | Use it (and test it) 6 | ==================== 7 | 8 | If you are using ``workalendar``, you are already contributing to it. As long 9 | as you are able to check its result, compare the designated working days and 10 | holidays to the reality, and make sure these are right, you're helping. 11 | 12 | If any of the computed holidays for the country / area your are using is 13 | **wrong**, please report 14 | `it using the Github issues `_. 15 | 16 | Report an issue 17 | =============== 18 | 19 | If you think you've found a bug you can report an issue. In order to help us 20 | sort this out, please follow the guidelines: 21 | 22 | * Tell us which ``workalendar`` version (master, PyPI release) you are using. 23 | * Tell us which Python version you are using, and your platform. 24 | * Give us extensive details on the country / area Calendar, the exact date(s) that was (were) computed and the one(s) that should have been the correct result. 25 | * If possible, please provide us a reliable source about the designated country / area calendar, where we could effectively check that we were wrong about a date, and giving us a way to patch our code properly so we can fix the bug. 26 | 27 | 28 | Adding new calendars 29 | ==================== 30 | 31 | Since ``workalendar`` is mainly built around configuration variables and generic 32 | methods, it's not that difficult to add a calendar to our codebase. A few 33 | **mandatory** steps should be observed: 34 | 35 | 1. Fork the repository and create a new branch named after the calendar you want to implement, 36 | 2. Add a test class to the workalendar test suite that checks holidays, 37 | 3. Implement the class using the core class APIs as much as possible. Test it until all tests pass. 38 | 4. Make a nice pull-request we'll be glad to review and merge when it's perfect. 39 | 40 | .. note:: 41 | 42 | Please respect the PEP8 convention, otherwise your PR won't be accepted. 43 | 44 | Example 45 | ------- 46 | 47 | Let's assume you want to include the holidays of the magic (fictional) kingdom 48 | of *"Zhraa"*, which has a few holidays of different kind. 49 | 50 | For the sake of the example, it has the following specs: 51 | 52 | * it's a Gregorian-based Calendar (i.e. the Western European / American one), 53 | * even if the King is not versed into religions, the kingdom includes a few Christian holidays, 54 | * even if you never knew about it, it is set in Europe, 55 | 56 | Here is a list of the holidays in *Zhraa*: 57 | 58 | * January 1st, New year's Day, 59 | * May 1st, Labour day, 60 | * Easter Monday, which is variable (from March to May), 61 | * The first monday in June, to celebrate the birth of the Founder of the Kingdom, Zhraa (nobody knows the exact day he was born, so this day was chosen as a convention), 62 | * The birthday of the King, August 2nd. 63 | * Christmas Day, Dec 25th. 64 | 65 | 66 | Getting ready 67 | ############# 68 | 69 | You'll need to install ``workalendar`` dependencies beforehand. What's great is 70 | that you'll use virtualenv to set it up. Or even better: ``virtualenvwrapper``. 71 | Just go in your working copy (cloned from github) of workalendar and type, for 72 | example:: 73 | 74 | mkvirtualenv WORKALENDAR 75 | pip install -e ./ 76 | 77 | 78 | Test-driven start 79 | ################# 80 | 81 | 82 | Let's prepare the Zhraa class. Edit the ``workalendar/europe/zhraa.py`` file and 83 | add a class like this:: 84 | 85 | from workalendar.core import WesternCalendar 86 | 87 | class Zhraa(WesternCalendar): 88 | "Kingdom of Zhraa" 89 | 90 | .. note:: 91 | 92 | The docstring is not mandatory, but if you omit it, the ``name`` property of 93 | your class will be the name of your class. For example, using upper 94 | CamelCase, ``KingdomOfZhraa``. For a more human-readable label, use your 95 | docstring. 96 | 97 | Meanwhile, in the ``workalendar/europe/__init__.py`` file, add these snippets 98 | where needed: 99 | 100 | .. code-block:: python 101 | 102 | from .zhraa import Zhraa 103 | # ... 104 | __all__ = ( 105 | Belgium, 106 | CzechRepublic, 107 | # ... 108 | Zhraa, 109 | ) 110 | 111 | Now, we're building a test class. Edit the ``workalendar/tests/test_europe.py`` 112 | file and add the following code:: 113 | 114 | from workalendar.europe import Zhraa 115 | # snip... 116 | 117 | class ZhraaTest(GenericCalendarTest): 118 | cal_class = Zhraa 119 | 120 | def test_year_2014(self): 121 | holidays = self.cal.holidays_set(2014) 122 | self.assertIn(date(2014, 1, 1), holidays) # new year 123 | self.assertIn(date(2014, 5, 1), holidays) # labour day 124 | self.assertIn(date(2014, 8, 2), holidays) # king birthday 125 | self.assertIn(date(2014, 12, 25), holidays) # Xmas 126 | # variable days 127 | self.assertIn(date(2014, 4, 21), holidays) # easter monday 128 | self.assertIn(date(2014, 6, 2), holidays) # First MON in June 129 | 130 | of course, if you run the test using the ``tox`` or ``py.test`` command, 131 | this will fail, since we haven't implemented anything yet. 132 | 133 | Install tox using the following command:: 134 | 135 | workon WORKALENDAR 136 | pip install tox 137 | 138 | With the ``WesternCalendar`` base class you have at least one holiday as a 139 | bonus: the New year's day, which is commonly a holiday. 140 | 141 | Add fixed days 142 | ############## 143 | 144 | :: 145 | 146 | class Zhraa(WesternCalendar): 147 | FIXED_HOLIDAYS = WesternCalendar.FIXED_HOLIDAYS + ( 148 | (5, 1, "Labour Day"), 149 | (8, 2, "King Birthday"), 150 | ) 151 | 152 | Now we've got 3 holidays out of 6. 153 | 154 | Add religious holidays 155 | ###################### 156 | 157 | Using ChristianMixin as a base to our Zhraa class will instantly add Christmas 158 | Day as a holiday. Now we can add Easter monday just by triggering the correct 159 | flag. 160 | 161 | :: 162 | 163 | from workalendar.core import WesternCalendar, ChristianMixin 164 | 165 | class Zhraa(WesternCalendar, ChristianMixin): 166 | include_easter_monday = True 167 | FIXED_HOLIDAYS = WesternCalendar.FIXED_HOLIDAYS + ( 168 | (5, 1, "Labour Day"), 169 | (8, 2, "King Birthday"), 170 | ) 171 | 172 | Almost there, 5 holidays out of 6. 173 | 174 | Add variable "non-usual" holidays 175 | ################################# 176 | 177 | There are many static methods that will grant you a clean access to variable 178 | days computation. It's very easy to add days like the "Birthday of the Founder":: 179 | 180 | 181 | class Zhraa(WesternCalendar, ChristianMixin): 182 | include_easter_monday = True 183 | FIXED_HOLIDAYS = WesternCalendar.FIXED_HOLIDAYS + ( 184 | (5, 1, "Labour Day"), 185 | (8, 2, "King Birthday"), 186 | ) 187 | 188 | def get_variable_days(self, year): 189 | # usual variable days 190 | days = super(Zhraa, self).get_variable_days(year) 191 | 192 | days.append( 193 | (Zhraa.get_nth_weekday_in_month(year, 6, MON), 194 | 'Day of the Founder'), 195 | ) 196 | return days 197 | 198 | .. note:: 199 | 200 | Please mind that the returned "variable_days" is a list of tuples. The first 201 | item being a date object (in the Python ``datetime.date`` sense) and the 202 | second one is the label string. 203 | 204 | Add you calendar to the global registry 205 | ####################################### 206 | 207 | If you're adding a Country calendar that has an ISO code, you may want to add 208 | it to our global registry. 209 | 210 | Workalendar is providing a registry that you can use to query and fetch calendar 211 | based on their ISO code. For the current example, let's pretend that the Zhraa 212 | Kingdom ISO code is ``ZK``. 213 | 214 | To register, add the following:: 215 | 216 | from workalendar.registry import iso_register 217 | 218 | @iso_register('ZK') 219 | class Zhraa(WesternCalendar, ChristianMixin): 220 | # The rest of your code... 221 | 222 | You're done for the code! 223 | ######################### 224 | 225 | There you are. Commit with a nice commit message, test, make sure it works for 226 | the other years as well and you're almost there. 227 | 228 | The final steps 229 | ############### 230 | 231 | Do not forget to: 232 | 233 | 1. put the appropriate doctring in the Calendar class. 234 | 2. add your calendar in the ``README.rst`` file, included in the appropriate continent. 235 | 3. add your calendar to the ``CHANGELOG`` file. 236 | 237 | .. note:: 238 | 239 | We're planning to build a complete documentation for the other cases 240 | (special holiday rules, other calendar types, other religions, etc). But 241 | with this tutorial you're sorted for a lot of other calendars. 242 | 243 | 244 | Other code contributions 245 | ======================== 246 | 247 | There are dozens of calendars all over the world. We'd appreciate you to 248 | contribute to the core of the library by adding some new Mixins or Calendars. 249 | 250 | Bear in mind that the code you'd provide **must** be tested using unittests 251 | before you submit your pull-request. 252 | -------------------------------------------------------------------------------- /workalendar/tests/test_oceania.py: -------------------------------------------------------------------------------- 1 | from datetime import date 2 | 3 | from workalendar.tests import GenericCalendarTest 4 | from workalendar.oceania import ( 5 | Australia, 6 | AustralianCapitalTerritory, 7 | NewSouthWales, 8 | NorthernTerritory, 9 | Queensland, 10 | SouthAustralia, 11 | Tasmania, 12 | Hobart, 13 | Victoria, 14 | WesternAustralia, 15 | MarshallIslands, 16 | ) 17 | 18 | 19 | class AustraliaTest(GenericCalendarTest): 20 | cal_class = Australia 21 | 22 | def test_year_2013(self): 23 | holidays = self.cal.holidays_set(2013) 24 | self.assertIn(date(2013, 1, 26), holidays) 25 | self.assertIn(date(2013, 1, 28), holidays) # Australia day shift 26 | self.assertIn(date(2013, 1, 26), holidays) 27 | self.assertIn(date(2013, 3, 29), holidays) # Good Friday 28 | self.assertIn(date(2013, 4, 25), holidays) 29 | self.assertIn(date(2013, 12, 25), holidays) 30 | 31 | def test_new_year_shift(self): 32 | holidays = self.cal.holidays_set(2012) 33 | self.assertIn(date(2012, 1, 2), holidays) # 1st was a sunday 34 | 35 | def test_anzac_shift(self): 36 | holidays = self.cal.holidays_set(2010) 37 | self.assertIn(date(2010, 4, 26), holidays) 38 | 39 | def test_oceania_shift_2016(self): 40 | holidays = self.cal.holidays_set(2016) 41 | # Christmas day is on sunday in 2016 42 | # Boxing day is on 26th 43 | self.assertIn(date(2016, 12, 26), holidays) 44 | # Boxing day shift on 27th 45 | self.assertIn(date(2016, 12, 27), holidays) 46 | 47 | def test_oceania_shift_2009(self): 48 | holidays = self.cal.holidays_set(2009) 49 | # Boxing day is on saturday in 2009 50 | # Boxing day is on 26th 51 | self.assertIn(date(2009, 12, 26), holidays) 52 | # Boxing day shift on 28th 53 | self.assertIn(date(2009, 12, 28), holidays) 54 | 55 | 56 | class AustraliaCapitalTerritoryTest(AustraliaTest): 57 | cal_class = AustralianCapitalTerritory 58 | 59 | def test_regional_specific_2013(self): 60 | holidays = self.cal.holidays_set(2013) 61 | self.assertIn(date(2013, 3, 11), holidays) 62 | self.assertIn(date(2013, 3, 30), holidays) # Easter Saturday 63 | self.assertIn(date(2013, 4, 1), holidays) # Easter Monday 64 | self.assertIn(date(2013, 6, 10), holidays) # Queen's Bday 65 | self.assertIn(date(2013, 9, 30), holidays) 66 | self.assertIn(date(2013, 10, 7), holidays) # Labour day october 67 | self.assertIn(date(2013, 12, 26), holidays) # Boxing day 68 | 69 | def test_reconciliation_day(self): 70 | reconciliation_day = self.cal.get_reconciliation_day(2017) 71 | self.assertIsNone(reconciliation_day) 72 | 73 | reconciliation_day = self.cal.get_reconciliation_day(2018) 74 | self.assertEqual(reconciliation_day, (date(2018, 5, 28), 75 | "Reconciliation Day Shift")) 76 | 77 | reconciliation_day = self.cal.get_reconciliation_day(2019) 78 | self.assertEqual(reconciliation_day, (date(2019, 5, 27), 79 | "Reconciliation Day")) 80 | 81 | 82 | class NewSouthWalesTest(AustraliaTest): 83 | cal_class = NewSouthWales 84 | 85 | def test_regional_specific_2013(self): 86 | holidays = self.cal.holidays_set(2013) 87 | self.assertIn(date(2013, 3, 30), holidays) # Good friday 88 | self.assertIn(date(2013, 3, 31), holidays) # Easter Sunday 89 | self.assertIn(date(2013, 6, 10), holidays) # Queen's Bday 90 | self.assertIn(date(2013, 10, 7), holidays) # Labour day october 91 | self.assertIn(date(2013, 12, 26), holidays) # Boxing day 92 | 93 | def test_anzac_shift(self): 94 | holidays = self.cal.holidays_set(2010) 95 | self.assertIn(date(2010, 4, 26), holidays) 96 | 97 | # We don't shift if ANZAC day falls on a Saturday 98 | holidays = self.cal.holidays_set(2015) 99 | self.assertIn(date(2015, 4, 25), holidays) 100 | 101 | 102 | class NorthernTerritoryTest(AustraliaTest): 103 | cal_class = NorthernTerritory 104 | 105 | def test_regional_specific_2013(self): 106 | holidays = self.cal.holidays_set(2013) 107 | self.assertIn(date(2013, 3, 30), holidays) # Easter Saturday 108 | self.assertIn(date(2013, 5, 6), holidays) # May Day 109 | self.assertIn(date(2013, 6, 10), holidays) # Queen's Bday 110 | self.assertIn(date(2013, 8, 5), holidays) # Picnic day 111 | self.assertIn(date(2013, 12, 26), holidays) # Boxing day 112 | 113 | def test_anzac_shift(self): 114 | holidays = self.cal.holidays_set(2010) 115 | self.assertIn(date(2010, 4, 26), holidays) 116 | 117 | # We don't shift if ANZAC day falls on a Saturday 118 | holidays = self.cal.holidays_set(2015) 119 | self.assertIn(date(2015, 4, 25), holidays) 120 | 121 | 122 | class QueenslandTest(AustraliaTest): 123 | cal_class = Queensland 124 | 125 | def test_regional_specific_2013(self): 126 | holidays = self.cal.holidays_set(2013) 127 | self.assertIn(date(2013, 3, 30), holidays) # Easter Saturday 128 | self.assertIn(date(2013, 5, 6), holidays) # May's labour day 129 | self.assertIn(date(2013, 6, 10), holidays) # Queen's Bday 130 | self.assertIn(date(2013, 12, 26), holidays) # Boxing day 131 | 132 | def test_anzac_shift(self): 133 | holidays = self.cal.holidays_set(2010) 134 | self.assertIn(date(2010, 4, 26), holidays) 135 | 136 | # We don't shift if ANZAC day falls on a Saturday 137 | holidays = self.cal.holidays_set(2015) 138 | self.assertIn(date(2015, 4, 25), holidays) 139 | 140 | 141 | class SouthAustraliaTest(AustraliaTest): 142 | cal_class = SouthAustralia 143 | 144 | def test_regional_specific_2013(self): 145 | holidays = self.cal.holidays_set(2013) 146 | self.assertIn(date(2013, 3, 11), holidays) # Adelaide's cup 147 | self.assertIn(date(2013, 3, 30), holidays) # Easter Saturday 148 | self.assertIn(date(2013, 6, 10), holidays) # Queen's Bday 149 | self.assertIn(date(2013, 10, 7), holidays) # Labour day october 150 | self.assertIn(date(2013, 12, 26), holidays) # Proclamation day 151 | 152 | def test_anzac_shift(self): 153 | holidays = self.cal.holidays_set(2010) 154 | self.assertIn(date(2010, 4, 26), holidays) 155 | 156 | # We don't shift if ANZAC day falls on a Saturday 157 | holidays = self.cal.holidays_set(2015) 158 | self.assertIn(date(2015, 4, 25), holidays) 159 | 160 | 161 | class TasmaniaTest(AustraliaTest): 162 | cal_class = Tasmania 163 | 164 | def test_regional_specific_2013(self): 165 | holidays = self.cal.holidays_set(2013) 166 | self.assertIn(date(2013, 3, 11), holidays) # Eight hours day 167 | self.assertIn(date(2013, 6, 10), holidays) # Queen's Bday 168 | self.assertIn(date(2013, 12, 26), holidays) # Boxing day 169 | self.assertIn(date(2013, 4, 25), holidays) # ANZAC day 170 | 171 | def test_anzac_shift(self): 172 | # We don't shift 173 | holidays = self.cal.holidays_set(2010) 174 | self.assertNotIn(date(2010, 4, 26), holidays) 175 | 176 | 177 | class NonHobartTest(TasmaniaTest): 178 | def test_tasmania_2013(self): 179 | holidays = self.cal.holidays_set(2013) 180 | self.assertIn(date(2013, 11, 4), holidays) # Recreation Day 181 | 182 | 183 | class HobartTest(TasmaniaTest): 184 | cal_class = Hobart 185 | 186 | def test_hobart_specific_2013(self): 187 | holidays = self.cal.holidays_set(2013) 188 | self.assertIn(date(2013, 2, 11), holidays) # Royal Hobart Regatta 189 | # Recreation day not in Hobart 190 | self.assertNotIn(date(2013, 11, 4), holidays) # Recreation Day 191 | 192 | 193 | class VictoriaTest(AustraliaTest): 194 | cal_class = Victoria 195 | 196 | def test_regional_specific_2013(self): 197 | holidays = self.cal.holidays_set(2013) 198 | self.assertIn(date(2013, 3, 11), holidays) # Labours day in march 199 | self.assertIn(date(2013, 3, 30), holidays) # Easter Saturday 200 | self.assertIn(date(2013, 6, 10), holidays) # Queen's Bday 201 | self.assertIn(date(2013, 11, 5), holidays) # Melbourne's cup 202 | self.assertIn(date(2013, 12, 26), holidays) # Boxing day 203 | self.assertIn(date(2013, 4, 25), holidays) # ANZAC day 204 | 205 | def test_anzac_shift(self): 206 | # We don't shift 207 | holidays = self.cal.holidays_set(2010) 208 | self.assertNotIn(date(2010, 4, 26), holidays) 209 | 210 | 211 | class WesternAustraliaTest(AustraliaTest): 212 | cal_class = WesternAustralia 213 | 214 | def test_regional_specific_2013(self): 215 | holidays = self.cal.holidays_set(2013) 216 | self.assertIn(date(2013, 3, 4), holidays) # Labours day in march 217 | self.assertIn(date(2013, 6, 3), holidays) # Western Australia Day 218 | self.assertIn(date(2013, 12, 26), holidays) # Boxing day 219 | # It is not possible to surely compute Queen's Birthday holiday in 220 | # The western Australia territory, since it's based on the Governor 221 | # Decision (it is typically the last Monday of September or the first 222 | # Monday of October) 223 | 224 | 225 | class MarshallIslandsTest(GenericCalendarTest): 226 | cal_class = MarshallIslands 227 | 228 | def test_year_2013(self): 229 | holidays = self.cal.holidays_set(2013) 230 | self.assertIn(date(2013, 1, 1), holidays) # new year 231 | self.assertIn(date(2013, 3, 3), holidays) # Remembrance day 232 | self.assertIn(date(2013, 3, 29), holidays) # (good friday) 233 | self.assertIn(date(2013, 5, 1), holidays) # constitution day 234 | self.assertIn(date(2013, 7, 5), holidays) # Fishermens 235 | self.assertIn(date(2013, 9, 6), holidays) # labour day 236 | self.assertIn(date(2013, 9, 27), holidays) # Manit Day 237 | self.assertIn(date(2013, 11, 17), holidays) # presidents day 238 | self.assertIn(date(2013, 12, 6), holidays) # gospel day 239 | self.assertIn(date(2013, 12, 25), holidays) # Xmas 240 | self.assertIn(date(2013, 12, 31), holidays) # new year's eve 241 | --------------------------------------------------------------------------------