Repository: peopledoc/workalendar (python)

Issue:
Turkey calendar
As a follow up to @tayyipgoren's work and information. We need to:

- [x] Implement the ability to add a delta to islamic days, depending on the country context, or even the year (since the beginning of the Ramadan is subject to arbitrary decisions for example). By default, no delta is added.
- [x] Turkey's calendar is subject to substract one day to each Islamic holidays.
- [x] Rebase @tayyipgoren branch to align with the latest master, squash extra commits etc,
- [x] Add tests for the shifting New Year's Day if it happens on Sunday.
- [ ] Question for @tayyipgoren : could you please confirm that the "WEEKEND_DAYS" are Saturday and Sunday?

Pull request description:
Added Turkey calendar
closes #371 ; superseeds and thus, closes #344

This branch is based on @tayyipgoren original work.

It adds an optional islamic delta to apply on-demand, by custom or specifically per year.
Also added tests for New Years shift, which happens on Sunday.

- [x] Tests with a significant number of years to be tested for your calendar.
- [x] Docstrings for the Calendar class and specific methods.
- [x] 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`` (#)"


Patch:
diff --git a/Changelog.md b/Changelog.md
index e51710c..88b6132 100644
--- a/Changelog.md
+++ b/Changelog.md
@@ -4,6 +4,12 @@
 
 **Deprecation Warning:** *Currently the registry returns `OrderedDict` objects when you're querying for regions or subregions. Expect that the next release will preferrably return plain'ol' `dict` objects. If your scripts rely on the order of the objects returned, you'll have to sort them yourself.*
 
+### New Calendar
+
+- Added Turkey by @tayyipgoren (#371).
+
+### Other changes
+
 - Change registry mechanism to avoid circular imports (#288).
 - Internal: Added a "Release" section to the Pull Request template.
 - Internal: Added advices on the Changelog entry in the Contributing document.
diff --git a/README.rst b/README.rst
index 71b36ee..b418aed 100644
--- a/README.rst
+++ b/README.rst
@@ -114,6 +114,7 @@ Europe
 
   * Vaud
 
+* Turkey
 * United Kingdom (incl. Northern Ireland, Scotland and all its territories)
 
 America
diff --git a/workalendar/core.py b/workalendar/core.py
index 5ca8973..ac5ca91 100644
--- a/workalendar/core.py
+++ b/workalendar/core.py
@@ -753,6 +753,14 @@ class CalverterMixin(Calendar):
     def get_islamic_holidays(self):
         return self.ISLAMIC_HOLIDAYS
 
+    def get_delta_islamic_holidays(self, year):
+        """
+        Return the delta to add/substract according to the year or customs.
+
+        By default, to return None or timedelta(days=0)
+        """
+        return None
+
     def get_variable_days(self, year):
         warnings.warn('Please take note that, due to arbitrary decisions, '
                       'this Islamic calendar computation may be wrong.')
@@ -764,8 +772,14 @@ class CalverterMixin(Calendar):
             for y in years:
                 jd = conversion_method(y, month, day)
                 g_year, g_month, g_day = self.calverter.jd_to_gregorian(jd)
-                if g_year == year:
-                    holiday = date(g_year, g_month, g_day)
+                holiday = date(g_year, g_month, g_day)
+
+                # Only add a delta if necessary
+                delta = self.get_delta_islamic_holidays(year)
+                if delta:
+                    holiday += delta
+
+                if holiday.year == year:
                     days.append((holiday, label))
         return days
 
diff --git a/workalendar/europe/__init__.py b/workalendar/europe/__init__.py
index 4fef3c8..a127df1 100644
--- a/workalendar/europe/__init__.py
+++ b/workalendar/europe/__init__.py
@@ -32,6 +32,7 @@ from .spain import Spain, Catalonia
 from .sweden import Sweden
 from .switzerland import Switzerland, Vaud
 from .united_kingdom import UnitedKingdom, UnitedKingdomNorthernIreland
+from .turkey import Turkey
 
 # Germany
 from .germany import (
@@ -88,6 +89,7 @@ __all__ = (
     'Vaud',
     'UnitedKingdom',
     'UnitedKingdomNorthernIreland',
+    'Turkey',
 
     # Germany
     'Germany', 'BadenWurttemberg', 'Bavaria', 'Berlin', 'Brandenburg',
diff --git a/workalendar/europe/turkey.py b/workalendar/europe/turkey.py
new file mode 100644
index 0000000..9fbe3c5
--- /dev/null
+++ b/workalendar/europe/turkey.py
@@ -0,0 +1,33 @@
+# -*- coding: utf-8 -*-
+from __future__ import unicode_literals
+from datetime import timedelta
+from ..core import WesternCalendar, IslamicMixin
+from ..registry_tools import iso_register
+
+
+@iso_register('TR')
+class Turkey(WesternCalendar, IslamicMixin):
+    'Turkey'
+
+    shift_new_years_day = True
+
+    # Islamic Holidays
+    include_eid_al_fitr = True
+    length_eid_al_fitr = 3
+    include_eid_al_adha = True
+    length_eid_al_adha = 4
+
+    FIXED_HOLIDAYS = WesternCalendar.FIXED_HOLIDAYS + (
+        (4, 23, "National Sovereignty and Children's Day"),
+        (5, 1, "Labor and Solidarity Day"),
+        (5, 19, "Commemoration of Atatürk, Youth and Sports Day"),
+        (7, 15, "Democracy and National Unity Day"),
+        (8, 30, "Victory Day"),
+        (9, 29, "Republic Day"),
+    )
+
+    def get_delta_islamic_holidays(self, year):
+        """
+        Turkey Islamic holidays are shifted by one day every year.
+        """
+        return timedelta(days=-1)


Test patch:
diff --git a/workalendar/tests/test_turkey.py b/workalendar/tests/test_turkey.py
new file mode 100644
index 0000000..e5c3c85
--- /dev/null
+++ b/workalendar/tests/test_turkey.py
@@ -0,0 +1,82 @@
+# -*- coding: utf-8 -*-
+from datetime import date
+
+from workalendar.tests import GenericCalendarTest
+from workalendar.europe.turkey import Turkey
+
+
+class TurkeyTest(GenericCalendarTest):
+    cal_class = Turkey
+
+    def test_year_new_year_shift(self):
+        holidays = self.cal.holidays_set(2012)
+        self.assertIn(date(2012, 1, 1), holidays)
+        self.assertIn(date(2012, 1, 2), holidays)
+        holidays = self.cal.holidays_set(2013)
+        self.assertIn(date(2013, 1, 1), holidays)
+        self.assertNotIn(date(2013, 1, 2), holidays)
+
+    def test_year_2014(self):
+        holidays = self.cal.holidays_set(2014)
+        # Fixed days section:
+        # 1. New years Day
+        self.assertIn(date(2014, 1, 1), holidays)
+        # 2. National Sovereignty and Children's Day
+        self.assertIn(date(2014, 4, 23), holidays)
+        # 3. Labor and Solidarity Day
+        self.assertIn(date(2014, 5, 1), holidays)
+        # 4. Commemoration of Atatürk, Youth and Sports Day
+        self.assertIn(date(2014, 5, 19), holidays)
+        # 5. Democracy and National Unity Day
+        self.assertIn(date(2014, 7, 15), holidays)
+        # 6. Victory Day
+        self.assertIn(date(2014, 8, 30), holidays)
+        # 7. Republic Day
+        self.assertIn(date(2014, 9, 29), holidays)
+
+    def test_year_2015(self):
+        holidays = self.cal.holidays_set(2015)
+        # Fixed days section:
+        # 1. New years Day
+        self.assertIn(date(2015, 1, 1), holidays)
+        # 2. National Sovereignty and Children's Day
+        self.assertIn(date(2015, 4, 23), holidays)
+        # 3. Labor and Solidarity Day
+        self.assertIn(date(2015, 5, 1), holidays)
+        # 4. Commemoration of Atatürk, Youth and Sports Day
+        self.assertIn(date(2015, 5, 19), holidays)
+        # 5. Democracy and National Unity Day
+        self.assertIn(date(2015, 7, 15), holidays)
+        # 6. Victory Day
+        self.assertIn(date(2015, 8, 30), holidays)
+        # 7. Republic Day
+        self.assertIn(date(2015, 9, 29), holidays)
+
+    def test_year_2019(self):
+        holidays = self.cal.holidays_set(2019)
+        # Fixed days section:
+        # 1. New years Day
+        self.assertIn(date(2019, 1, 1), holidays)
+        # 2. National Sovereignty and Children's Day
+        self.assertIn(date(2019, 4, 23), holidays)
+        # 3. Labor and Solidarity Day
+        self.assertIn(date(2019, 5, 1), holidays)
+        # 4. Commemoration of Atatürk, Youth and Sports Day
+        self.assertIn(date(2019, 5, 19), holidays)
+        # 5. Democracy and National Unity Day
+        self.assertIn(date(2019, 7, 15), holidays)
+        # 6. Victory Day
+        self.assertIn(date(2019, 8, 30), holidays)
+        # 7. Republic Day
+        self.assertIn(date(2019, 9, 29), holidays)
+
+        # Religious days
+        # Ramadan Feast - 3 days
+        self.assertIn(date(2019, 6, 4), holidays)
+        self.assertIn(date(2019, 6, 5), holidays)
+        self.assertIn(date(2019, 6, 6), holidays)
+        # Ramadan Feast - 4 days
+        self.assertIn(date(2019, 8, 11), holidays)
+        self.assertIn(date(2019, 8, 12), holidays)
+        self.assertIn(date(2019, 8, 13), holidays)
+        self.assertIn(date(2019, 8, 14), holidays)