diff --git a/README.rst b/README.rst index 1573b7a4be..3cf782a448 100644 --- a/README.rst +++ b/README.rst @@ -92,6 +92,10 @@ Czech CZ None Denmark DK None England None EuropeanCentralBank ECB,TAR Trans-European Automated Real-time Gross Settlement (TARGET2) +France FRA **Métropole** (default), Alsace-Moselle, Guadeloupe, Guyane, + Martinique, Mayotte, Nouvelle-Calédonie, La Réunion, + Polynésie Française, Saint-Barthélémy, Saint-Martin, + Wallis-et-Futuna Germany DE BW, BY, BE, BB, HB, HH, HE, MV, NI, NW, RP, SL, SN, ST, SH, TH Ireland None diff --git a/holidays.py b/holidays.py index ecb463a07a..af9ab8cda4 100644 --- a/holidays.py +++ b/holidays.py @@ -2224,3 +2224,110 @@ def _populate(self, year): class NO(Norway): pass + + +class France(HolidayBase): + """Official French holidays. + + Some provinces have specific holidays, only those are included in the + PROVINCES, because these provinces have different administrative status, + which makes it difficult to enumerate. + + For religious holidays usually happening on Sundays (Easter, Pentecost), + only the following Monday is considered a holiday. + + Primary sources: + https://fr.wikipedia.org/wiki/Fêtes_et_jours_fériés_en_France + https://www.service-public.fr/particuliers/vosdroits/F2405 + """ + + PROVINCES = ['Métropole', 'Alsace-Moselle', 'Guadeloupe', 'Guyane', + 'Martinique', 'Mayotte', 'Nouvelle-Calédonie', 'La Réunion', + 'Polynésie Française', 'Saint-Barthélémy', 'Saint-Martin', + 'Wallis-et-Futuna'] + + def __init__(self, **kwargs): + self.country = 'FR' + self.prov = kwargs.pop('prov', 'Métropole') + HolidayBase.__init__(self, **kwargs) + + def _populate(self, year): + # Civil holidays + if year > 1810: + self[date(year, 1, 1)] = "Jour de l'an" + + if year > 1919: + name = 'Fête du Travail' + if year <= 1948: + name += ' et de la Concorde sociale' + self[date(year, 5, 1)] = name + + if (year >= 1953 and year <= 1959) or year > 1981: + self[date(year, 5, 8)] = 'Armistice 1945' + + if year >= 1880: + self[date(year, 7, 14)] = 'Fête nationale' + + if year >= 1918: + self[date(year, 11, 11)] = 'Armistice 1918' + + # Religious holidays + if self.prov in ['Alsace-Moselle', 'Guadeloupe', 'Guyane', + 'Martinique', 'Polynésie Française']: + self[easter(year) - rd(days=2)] = 'Vendredi saint' + + if self.prov == 'Alsace-Moselle': + self[date(year, 12, 26)] = 'Deuxième jour de Noël' + + if year >= 1886: + self[easter(year) + rd(days=1)] = 'Lundi de Pâques' + self[easter(year) + rd(days=50)] = 'Lundi de Pentecôte' + + if year >= 1802: + self[easter(year) + rd(days=39)] = 'Ascension' + self[date(year, 8, 15)] = 'Assomption' + self[date(year, 11, 1)] = 'Toussaint' + + name = 'Noël' + if self.prov == 'Alsace-Moselle': + name = 'Premier jour de ' + name + self[date(year, 12, 25)] = name + + # Non-metropolitan holidays (starting dates missing) + if self.prov == 'Mayotte': + self[date(year, 4, 27)] = "Abolition de l'esclavage" + + if self.prov == 'Wallis-et-Futuna': + self[date(year, 4, 28)] = 'Saint Pierre Chanel' + + if self.prov == 'Martinique': + self[date(year, 5, 22)] = "Abolition de l'esclavage" + + if self.prov in ['Guadeloupe', 'Saint-Martin']: + self[date(year, 5, 27)] = "Abolition de l'esclavage" + + if self.prov == 'Guyane': + self[date(year, 6, 10)] = "Abolition de l'esclavage" + + if self.prov == 'Polynésie Française': + self[date(year, 6, 29)] = "Fête de l'autonomie" + + if self.prov in ['Guadeloupe', 'Martinique']: + self[date(year, 7, 21)] = 'Fête Victor Schoelcher' + + if self.prov == 'Wallis-et-Futuna': + self[date(year, 7, 29)] = 'Fête du Territoire' + + if self.prov == 'Nouvelle-Calédonie': + self[date(year, 9, 24)] = 'Fête de la Citoyenneté' + + if self.prov == 'Saint-Barthélémy': + self[date(year, 10, 9)] = "Abolition de l'esclavage" + + if self.prov == 'La Réunion' and year >= 1981: + self[date(year, 12, 20)] = "Abolition de l'esclavage" + + +# FR already exists (Friday), we don't want to mess it up +class FRA(France): + pass diff --git a/tests.py b/tests.py index ab7167998e..f21edb2542 100644 --- a/tests.py +++ b/tests.py @@ -3000,5 +3000,31 @@ def test_not_holiday(self): self.assertFalse('2016-12-28' in self.holidays_with_sundays) +class TestFR(unittest.TestCase): + + def setUp(self): + self.holidays = holidays.France() + self.prov_holidays = dict((prov, holidays.France(prov=prov)) + for prov in holidays.France.PROVINCES) + + def test_2017(self): + self.assertTrue(date(2017, 1, 1) in self.holidays) + self.assertTrue(date(2017, 4, 17) in self.holidays) + self.assertTrue(date(2017, 5, 1) in self.holidays) + self.assertTrue(date(2017, 5, 8) in self.holidays) + self.assertTrue(date(2017, 5, 25) in self.holidays) + self.assertTrue(date(2017, 6, 5) in self.holidays) + self.assertTrue(date(2017, 7, 14) in self.holidays) + self.assertTrue(date(2017, 8, 15) in self.holidays) + self.assertTrue(date(2017, 11, 1) in self.holidays) + self.assertTrue(date(2017, 11, 11) in self.holidays) + self.assertTrue(date(2017, 12, 25) in self.holidays) + + def test_alsace_moselle(self): + am_holidays = self.prov_holidays['Alsace-Moselle'] + self.assertTrue(date(2017, 4, 14) in am_holidays) + self.assertTrue(date(2017, 12, 26) in am_holidays) + + if __name__ == "__main__": unittest.main()