Repository: peopledoc/workalendar
Pull request #571 (2020-10-23 11:12:26)

Netherlands with carnival
refs #

<!-- if your contribution is a new calendar -->

For information, read and make sure you're okay with the [Contributing guidelines](https://github.com/novafloss/workalendar/blob/master/contributing.md#adding-new-calendars).

- [x] Tests with a significant number of years to be tested for your calendar.
- [ ] Docstrings for the Calendar class and specific methods.
- [ ] Use the ``workalendar.registry_tools.iso_register`` decorator to register your new calendar using ISO codes (optional).
- [x] Calendar country / label added to the README.rst file.
- [x] Changelog amended with a mention like: "Added ``<country>`` by ``@pseudo`` (#)". **Note** *Please do NOT change the version number here. It's the project maintainers' duty.*

<!-- Release management

- Commit for the tag:
    - [ ] Edit version in setup.py
    - [ ] Add version in Changelog.md ; trim things
    - [ ] Push & wait for the tests to be green
    - [ ] tag me.
    - [ ] build sdist + wheel packages (``make package``)
- Back to dev commit:
    - [ ] Edit version in setup.py
    - [ ] Add the "master / nothing to see here" in Changelog.md
    - [ ] Push & wait for the tests to be green
- [ ] Merge --ff
- Github stuff
    - [ ] Push tag in Github
    - [ ] Edit release on Github using the changelog.
    - [ ] Delete branch
- [ ] upload release on PyPI using ``twine``
- [ ] (*optional*) Make feeback on the various PR or issues.

 -->



Patch:
diff --git a/Changelog.md b/Changelog.md
index 8f5312e..21dce2b 100644
--- a/Changelog.md
+++ b/Changelog.md
@@ -3,6 +3,7 @@
 ## master (unreleased)
 
 - Add optional school holidays to Netherlands calendar, by @Flix6x (#556).
+- Add optional carnival to Netherlands calendar.
 
 ## v12.1.0 (2020-10-16)
 
diff --git a/README.rst b/README.rst
index ac61dec..d80c05a 100644
--- a/README.rst
+++ b/README.rst
@@ -104,7 +104,7 @@ Europe
 * Luxembourg
 * Malta
 * Monaco
-* Netherlands (optionally with school holidays)
+* Netherlands (optionally with school holidays and carnival)
 * Norway
 * Poland
 * Portugal
diff --git a/workalendar/europe/netherlands.py b/workalendar/europe/netherlands.py
index db65782..3ba6aaa 100644
--- a/workalendar/europe/netherlands.py
+++ b/workalendar/europe/netherlands.py
@@ -19,6 +19,10 @@ class Netherlands(WesternCalendar):
         (5, 5, "Liberation Day"),
     )
 
+    def __init__(self, include_carnival=False):
+        self.include_carnival = include_carnival
+        super().__init__()
+
     def get_king_queen_day(self, year):
         """27 April unless this is a Sunday in which case it is the 26th
 
@@ -36,9 +40,22 @@ class Netherlands(WesternCalendar):
                 return (queen_day, "Queen's day")
             return (queen_day - timedelta(days=1), "Queen's day")
 
+    def get_carnival_days(self, year):
+        """Carnival starts 7 weeks before Easter Sunday and lasts 3 days."""
+        n_days = 3
+        start = self.get_easter_sunday(year) - timedelta(weeks=7)
+
+        return [
+            (
+                start + timedelta(days=i), "Carnival"
+            ) for i in range(n_days)
+        ]
+
     def get_variable_days(self, year):
         days = super().get_variable_days(year)
         days.append(self.get_king_queen_day(year))
+        if self.include_carnival:
+            days.extend(self.get_carnival_days(year))
         return days
 
 
@@ -102,7 +119,12 @@ class NetherlandsWithSchoolHolidays(Netherlands):
     https://www.rijksoverheid.nl/onderwerpen/schoolvakanties/overzicht-schoolvakanties-per-schooljaar
     """
 
-    def __init__(self, region):
+    def __init__(
+            self,
+            region,
+            carnival_instead_of_spring=False,
+            **kwargs,
+    ):
         """ Set up a calendar incl. school holidays for a specific region
 
         :param region: either "north", "middle" or "south"
@@ -110,7 +132,10 @@ class NetherlandsWithSchoolHolidays(Netherlands):
         if region not in ("north", "middle", "south"):
             raise ValueError("Set region to 'north', 'middle' or 'south'.")
         self.region = region
-        super().__init__()
+        self.carnival_instead_of_spring = carnival_instead_of_spring
+        if carnival_instead_of_spring and "include_carnival" not in kwargs:
+            kwargs["include_carnival"] = True
+        super().__init__(**kwargs)
 
     def get_fall_holidays(self, year):
         n_days = 9
@@ -233,6 +258,19 @@ class NetherlandsWithSchoolHolidays(Netherlands):
             ) for i in range(n_days)
         ]
 
+    def get_carnival_holidays(self, year):
+        """Carnival holiday starts 7 weeks and 1 day before Easter Sunday
+        and lasts 9 days.
+        """
+        n_days = 9
+        start = self.get_easter_sunday(year) - timedelta(weeks=7, days=1)
+
+        return [
+            (
+                start + timedelta(days=i), "Carnival holiday"
+            ) for i in range(n_days)
+        ]
+
     def get_may_holidays(self, year):
         n_days = 9
         week = 18
@@ -295,7 +333,10 @@ class NetherlandsWithSchoolHolidays(Netherlands):
         days = super().get_variable_days(year)
         days.extend(self.get_fall_holidays(year))
         days.extend(self.get_christmas_holidays(year))
-        days.extend(self.get_spring_holidays(year))
+        if self.carnival_instead_of_spring:
+            days.extend(self.get_carnival_holidays(year))
+        else:
+            days.extend(self.get_spring_holidays(year))
         days.extend(self.get_may_holidays(year))
         days.extend(self.get_summer_holidays(year))
         return days


Test patch:
diff --git a/workalendar/tests/test_europe.py b/workalendar/tests/test_europe.py
index 1c196b1..04c5220 100644
--- a/workalendar/tests/test_europe.py
+++ b/workalendar/tests/test_europe.py
@@ -318,7 +318,7 @@ class CroatiaTest(GenericCalendarTest):
             holidays[date(2020, 5, 1)], "International Workers' Day")
 
 
-class Cyprus(GenericCalendarTest):
+class CyprusTest(GenericCalendarTest):
     cal_class = Cyprus
 
     def test_year_2017(self):
@@ -1290,7 +1290,58 @@ class NetherlandsSouthWithSchoolHolidaysTest(GenericCalendarTest):
             )
 
 
-class Romania(GenericCalendarTest):
+class NetherlandsWithCarnivalTest(GenericCalendarTest):
+
+    cal_class = Netherlands
+    kwargs = dict(include_carnival=True)
+
+    def test_year_2020(self):
+        holidays = dict(self.cal.holidays(2020))
+        self.assertEqual(
+            holidays[date(2020, 2, 23)], "Carnival"
+        )
+        self.assertEqual(
+            holidays[date(2020, 2, 24)], "Carnival"
+        )
+        self.assertEqual(
+            holidays[date(2020, 2, 25)], "Carnival"
+        )
+
+    def test_year_2021(self):
+        holidays = dict(self.cal.holidays(2021))
+        self.assertEqual(
+            holidays[date(2021, 2, 14)], "Carnival"
+        )
+        self.assertEqual(
+            holidays[date(2021, 2, 15)], "Carnival"
+        )
+        self.assertEqual(
+            holidays[date(2021, 2, 16)], "Carnival"
+        )
+
+
+class NetherlandsWithSchoolHolidaysAndCarnivalTest(GenericCalendarTest):
+    cal_class = NetherlandsWithSchoolHolidays
+    kwargs = dict(region="south", carnival_instead_of_spring=True)
+
+    def test_year_2020(self):
+        holidays = dict(self.cal.holidays(2020))
+        for d in range(9):
+            self.assertIn(
+                "Carnival holiday",
+                holidays[date(2020, 2, 22) + timedelta(days=d)],
+            )
+
+    def test_year_2021(self):
+        holidays = dict(self.cal.holidays(2021))
+        for d in range(9):
+            self.assertIn(
+                "Carnival holiday",
+                holidays[date(2021, 2, 13) + timedelta(days=d)],
+            )
+
+
+class RomaniaTest(GenericCalendarTest):
     cal_class = Romania
 
     def test_year_2017(self):
@@ -1344,7 +1395,7 @@ class Romania(GenericCalendarTest):
         self.assertNotIn(liberation_day_1991, holidays)
 
 
-class Russia(GenericCalendarTest):
+class RussiaTest(GenericCalendarTest):
     cal_class = Russia
 
     def test_year_2018(self):