From 0a41fa3e4d24dce5eb6a3a144d4c58abe0f1064f Mon Sep 17 00:00:00 2001 From: Andrew Williams Date: Wed, 19 Feb 2014 22:45:10 +0000 Subject: [PATCH] Initial import. --- .gitignore | 4 +++ LICENSE | 0 README.md | 0 businesshours/__init__.py | 1 + businesshours/core.py | 50 ++++++++++++++++++++++++++++++++ businesshours/tests/__init__.py | 1 + businesshours/tests/test_core.py | 36 +++++++++++++++++++++++ setup.py | 0 8 files changed, 92 insertions(+) create mode 100644 .gitignore create mode 100644 LICENSE create mode 100644 README.md create mode 100644 businesshours/__init__.py create mode 100644 businesshours/core.py create mode 100644 businesshours/tests/__init__.py create mode 100644 businesshours/tests/test_core.py create mode 100644 setup.py diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..71eb871 --- /dev/null +++ b/.gitignore @@ -0,0 +1,4 @@ +.idea +.pyc +build/ +dist/ \ No newline at end of file diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..e69de29 diff --git a/README.md b/README.md new file mode 100644 index 0000000..e69de29 diff --git a/businesshours/__init__.py b/businesshours/__init__.py new file mode 100644 index 0000000..ff6de8c --- /dev/null +++ b/businesshours/__init__.py @@ -0,0 +1 @@ +from .core import calc_business_hours \ No newline at end of file diff --git a/businesshours/core.py b/businesshours/core.py new file mode 100644 index 0000000..52a8d6d --- /dev/null +++ b/businesshours/core.py @@ -0,0 +1,50 @@ +from datetime import datetime, timedelta, time + + +def calc_business_hours(start, end, weekdays=(1, 2, 3, 4, 5), hours=range(8, 18)): + """Calculates the number of seconds hours between two dates""" + + def date_range(start_date, end_date): + """Converts two datetimes into a list of dates between them""" + if isinstance(start_date, datetime): + start_date = start_date.date() + if isinstance(end_date, datetime): + end_date = end_date.date() + if start_date > end_date: + raise ValueError('You provided a start_date that comes after the end_date.') + while True: + yield start_date + start_date = start_date + timedelta(days=1) + if start_date > end_date: + break + + if start.date() == end.date(): + if not start.date().isoweekday() in weekdays: + return 0 + if start.time().hour in hours: + actual_start = start + else: + if start.time().hour >= hours[-1] + 1: + return 0 + actual_start = datetime.combine(start.date(), time(hours[0], 0, 0)) + if end.time().hour in hours: + actual_end = end + else: + if end.time().hour <= hours[0]: + return 0 + actual_end = datetime.combine(end.date(), time(hours[-1] + 1, 0, 0)) + secs = (actual_end - actual_start).total_seconds() + return secs + else: + total_seconds = 0 + dates = [dt for dt in date_range(start, end)] + + for idx, dt in enumerate(dates): + day_start = datetime.combine(dt, time(0, 0, 0)) + day_end = datetime.combine(dt, time(23, 59, 59)) + if idx == 0: + day_start = start + if idx == len(dates) - 1: + day_end = end + total_seconds += calc_business_hours(day_start, day_end, weekdays, hours) + return total_seconds diff --git a/businesshours/tests/__init__.py b/businesshours/tests/__init__.py new file mode 100644 index 0000000..87eaf32 --- /dev/null +++ b/businesshours/tests/__init__.py @@ -0,0 +1 @@ +__author__ = 'nikdoof' diff --git a/businesshours/tests/test_core.py b/businesshours/tests/test_core.py new file mode 100644 index 0000000..b637f72 --- /dev/null +++ b/businesshours/tests/test_core.py @@ -0,0 +1,36 @@ +from datetime import datetime +import unittest + +from businesshours import calc_business_hours + + +class TestCalcBusinessHours(unittest.TestCase): + + def run_tests(self, tests): + for res, dt1, dt2 in tests: + self.assertEqual(calc_business_hours(dt1, dt2,), res) + + def test_invalid_arguments(self): + self.assertRaises(ValueError, calc_business_hours, datetime(2014,1,2), datetime(2014,1,1)) + + def test_simple_day(self): + self.run_tests([ + (60, datetime(2013, 8, 1, 9, 0, 0), datetime(2013, 8, 1, 9, 1, 0)), + (600, datetime(2013, 8, 1, 9, 0, 0), datetime(2013, 8, 1, 9, 10, 0)), # Simple multiday + (36000, datetime(2013, 8, 1, 9, 0, 0), datetime(2013, 8, 2, 9, 0, 0)), + (72000, datetime(2013, 8, 5, 9, 0, 0), datetime(2013, 8, 7, 9, 0, 0)), + ]) + + def test_multiday_out_of_hours(self): + self.run_tests([ + (68400, datetime(2013, 8, 5, 9, 0, 0), datetime(2013, 8, 7, 0, 1, 0)), + (72000, datetime(2013, 8, 5, 7, 0, 0), datetime(2013, 8, 7, 8, 0, 0)), + (72000, datetime(2013, 8, 5, 6, 0, 0), datetime(2013, 8, 7, 8, 0, 0)), + ]) + + def test_weekend_single_day(self): + self.run_tests([ + (0, datetime(2013, 8, 24, 9, 0, 0), datetime(2013, 8, 24, 9, 1, 0)), + (0, datetime(2013, 8, 25, 9, 0, 0), datetime(2013, 8, 25, 9, 10, 0)), # Multiday during weekend + (0, datetime(2013, 8, 3, 7, 0, 0), datetime(2013, 8, 4, 9, 0, 0)), + ]) \ No newline at end of file diff --git a/setup.py b/setup.py new file mode 100644 index 0000000..e69de29