Repository: peopledoc/workalendar (python)

Issue:
Add working days delta method
It would be interesting to implment a methon that gets working days between two given dates. Ie:

```python
def get_working_days_delta(begin, end):
    '''
    Get working days between two dates
    '''
    days = 0
    temp_day = begin
    while temp_day <= end:
        if cal.is_working_day(temp_day):
            days += 1
        temp_day = temp_day + td(days=1)
    return days
```

Pull request:
Add the `get_working_days_delta` method to the core calendar class.
closes #260

- [x] Tests with a significant number of use-cases.
- [x] Changelog amended with a mention describing your changes.


Patch:
diff --git a/CHANGELOG b/CHANGELOG
index ab3384a..c99d748 100644
--- a/CHANGELOG
+++ b/CHANGELOG
@@ -4,7 +4,8 @@ CHANGELOG
 master (unreleased)
 -------------------
 
-* Bugfix: deduplicate South Africa holidays that were emitted as duplicates (#265).
+- Bugfix: deduplicate South Africa holidays that were emitted as duplicates (#265).
+- Add the `get_working_days_delta` method to the core calendar class (#260).
 
 2.4.0 (2018-03-28)
 ------------------
diff --git a/workalendar/core.py b/workalendar/core.py
index c8b9ca1..9121d31 100644
--- a/workalendar/core.py
+++ b/workalendar/core.py
@@ -246,6 +246,47 @@ class Calendar(object):
         day = day + timedelta(days=day_delta)
         return day
 
+    def get_working_days_delta(self, start, end):
+        """
+        Return the number of working day between two given dates.
+        The order of the dates provided doesn't matter.
+
+        In the following example, there are 5 days, because of the week-end:
+
+        >>> cal = WesternCalendar()  # does not include easter monday
+        >>> day1 = date(2018, 3, 29)
+        >>> day2 = date(2018, 4, 5)
+        >>> cal.get_working_days_delta(day1, day2)
+        5
+
+        In France, April 1st 2018 is a holiday because it's Easter monday:
+
+        >>> cal = France()
+        >>> cal.get_working_days_delta(day1, day2)
+        4
+
+        This method should even work if your ``start`` and ``end`` arguments
+        are datetimes.
+        """
+        # Sanitize date types first.
+        if type(start) is datetime:
+            start = start.date()
+        if type(end) is datetime:
+            end = end.date()
+
+        if start == end:
+            return 0
+
+        if start > end:
+            start, end = end, start
+        # Starting count here
+        count = 0
+        while start < end:
+            start += timedelta(days=1)
+            if self.is_working_day(start):
+                count += 1
+        return count
+
 
 class ChristianMixin(Calendar):
     EASTER_METHOD = None  # to be assigned in the inherited mixin


Test patch:
diff --git a/workalendar/tests/test_core.py b/workalendar/tests/test_core.py
index 183aa8c..57aa7f8 100644
--- a/workalendar/tests/test_core.py
+++ b/workalendar/tests/test_core.py
@@ -1,7 +1,9 @@
 from datetime import date
 from datetime import datetime
+from unittest import TestCase
+
 from workalendar.tests import GenericCalendarTest
-from workalendar.core import MON, TUE, THU, FRI, WED
+from workalendar.core import MON, TUE, THU, FRI, WED, SAT, SUN
 from workalendar.core import Calendar, LunarCalendar, WesternCalendar
 from workalendar.core import IslamicMixin, JalaliMixin, ChristianMixin
 from workalendar.core import EphemMixin
@@ -395,3 +397,82 @@ class OverwriteGetWeekendDaysCalendarTest(GenericCalendarTest):
         self.assertTrue(self.cal.is_working_day(day))
         day = date(2017, 5, 17)  # This is a Wednesday
         self.assertFalse(self.cal.is_working_day(day))
+
+
+class NoHolidayCalendar(Calendar):
+    WEEKEND_DAYS = (SAT, SUN)
+
+
+class WorkingDaysDeltatest(TestCase):
+
+    def test_zero(self):
+        days = (
+            date(2018, 12, 21),  # a Thursday
+            date(2018, 12, 23),  # a Sunday
+            date(2018, 12, 25),  # a holiday in Christian calendars
+        )
+        for day in days:
+            cal = NoHolidayCalendar()
+            self.assertEqual(cal.get_working_days_delta(day, day), 0)
+            cal = MockChristianCalendar()
+            self.assertEqual(cal.get_working_days_delta(day, day), 0)
+
+    def test_no_holidays_simple(self):
+        cal = NoHolidayCalendar()
+        day1 = date(2018, 12, 21)
+        day2 = date(2018, 12, 26)
+        delta = cal.get_working_days_delta(day1, day2)
+        # there are 3 days, because of the week-ends
+        self.assertEqual(delta, 3)
+
+        # No difference if you swap the two dates
+        delta = cal.get_working_days_delta(day2, day1)
+        self.assertEqual(delta, 3)
+
+    def test_no_holidays_over_2_years(self):
+        cal = NoHolidayCalendar()
+        day1 = date(2018, 12, 21)
+        day2 = date(2019, 1, 4)
+        delta = cal.get_working_days_delta(day1, day2)
+        # there are 10 days, because of the week-ends
+        self.assertEqual(delta, 10)
+
+        # No difference if you swap the two dates
+        delta = cal.get_working_days_delta(day2, day1)
+        self.assertEqual(delta, 10)
+
+    def test_christian_simple(self):
+        cal = MockChristianCalendar()
+        day1 = date(2018, 12, 21)
+        day2 = date(2018, 12, 26)
+        delta = cal.get_working_days_delta(day1, day2)
+        # there are 2 days, because of the week-end + Christmas Day
+        self.assertEqual(delta, 2)
+
+        # No difference if you swap the two dates
+        delta = cal.get_working_days_delta(day2, day1)
+        self.assertEqual(delta, 2)
+
+    def test_christian_over_2_years(self):
+        cal = MockChristianCalendar()
+        day1 = date(2018, 12, 21)
+        day2 = date(2019, 1, 4)
+        delta = cal.get_working_days_delta(day1, day2)
+        # there are 8 days, because of the week-ends + Xmas day + New Year
+        self.assertEqual(delta, 8)
+
+        # No difference if you swap the two dates
+        delta = cal.get_working_days_delta(day2, day1)
+        self.assertEqual(delta, 8)
+
+    def test_with_datetimes(self):
+        cal = MockChristianCalendar()
+        day1 = datetime(2018, 12, 21)
+        day2 = date(2018, 12, 26)
+        delta = cal.get_working_days_delta(day1, day2)
+        # there are 2 days, because of the week-end + Christmas Day
+        self.assertEqual(delta, 2)
+
+        # No difference if you swap the two dates
+        delta = cal.get_working_days_delta(day2, day1)
+        self.assertEqual(delta, 2)