Skip to content

Instantly share code, notes, and snippets.

@jayme-github
Created December 15, 2015 08:35
Show Gist options
  • Save jayme-github/3bd6e8e5614a52f2e608 to your computer and use it in GitHub Desktop.
Save jayme-github/3bd6e8e5614a52f2e608 to your computer and use it in GitHub Desktop.
BusinessHours
# Based on BusinessHours written by
# Antano Solar John "solar_ant" <solar345@gmail.com>
# Sponsored by Ma Foi
#
# (C)2006-2007 All Rights Reserved,
#
# This library is free software; you can redistribute it and/or
# modify it under the terms of the GNU Lesser General Public
# License as published by the Free Software Foundation; either
# version 2.1 of the License, or (at your option) any later version.
#
# This library is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
# Lesser General Public License for more details.
#
# You should have received a copy of the GNU Lesser General Public
# License along with this library; if not, write to the Free Software
# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
import datetime
from dateutil.easter import easter
def get_public_holiday(year):
easterDay = easter(year)
publicHoliday = (datetime.date(year, 01, 01), # Neujahr
easterDay + datetime.timedelta(-2), # Karfreitag
easterDay + datetime.timedelta(1), # Ostermontag
datetime.date(year, 05, 01), # Tag der Arbeit
easterDay + datetime.timedelta(39), # Christi Himmelfahrt
easterDay + datetime.timedelta(50), # Pfingstmontag
datetime.date(year, 10, 03), # Tag der Deutschen Einheit
datetime.date(year, 12, 25), # 1. Weihnachtstag
datetime.date(year, 12, 26) # 2. Weihnachtstag
)
return publicHoliday
def get_half_holiday(year):
halfDays = (datetime.date(year, 12, 24), # Heiligabend
datetime.date(year, 12, 31) # Sylvester
)
return halfDays
def daterange(start_date, end_date):
if isinstance(start_date, datetime.datetime):
start_date = start_date.date()
if isinstance(end_date, datetime.datetime):
end_date = end_date.date()
# yield even if this is no range
day_count = (end_date - start_date).days
for n in range(day_count if day_count else 1):
yield start_date + datetime.timedelta(n)
def business_daterange(start_date, end_date, weekend_days=[6, 7]):
"""
Iterate over a date range yielding only business days
"""
for day in daterange(start_date, end_date):
if day in get_public_holiday(day.year):
# Skip holiday
continue
if day.isoweekday() in weekend_days:
# Skip weekend_days
continue
yield day
class BusinessHours(object):
def __init__(self, start, end, worktime_start=9, worktime_end=17, weekend_days=[6, 7]):
if not end >= start:
raise ValueError('End should end after start')
self.start = start
self.end = end
self.worktime_start = worktime_start
self.worktime_end = worktime_end
self.weekend_days = weekend_days
def __repr__(self):
return u'<BusinessHours %s - %s>' % (self.start, self.end)
def get_worktime_for(self, day):
# FIXME: Returns full worktime for weekends and holiday
if isinstance(day, datetime.datetime):
day = day.date()
worktime = (self.worktime_start, self.worktime_end)
if day in get_half_holiday(day.year):
worktime = (self.worktime_start, self.worktime_start + ((self.worktime_end - self.worktime_start) / 2))
return worktime
def get_days(self):
"""
Return the difference in days.
"""
days = 0.0
for day in business_daterange(self.start, self.end, self.weekend_days):
if day in get_half_holiday(day.year):
days += 0.5
else:
days += 1
return days
def get_hours(self):
"""
Return the difference in hours
"""
return self.get_minutes() / 60
def get_minutes(self):
"""
Return the difference in minutes
"""
return self.get_seconds() / 60
def get_seconds(self):
"""
Return the difference in seconds
"""
dt_start = self.start
dt_end = self.end
if dt_start.date() == dt_end.date():
# Range is on the same day
if self.is_free_day(dt_start):
return 0
else:
wt_start, wt_end = self.get_worktime_for(dt_start)
if dt_start.hour >= wt_end or \
dt_end.hour < wt_start:
# Range is not within work hours
return 0
if dt_start.hour < wt_start:
# starts prior to work hours
# so overwrite start with work hours
dt_start = dt_start.replace(hour=wt_start, minute=0, second=0)
if dt_end.hour >= wt_end:
# Ends after work hours
# so oberwrite end with work hours
dt_end = dt_end.replace(hour=wt_end, minute=0, second=0)
return (dt_end-dt_start).total_seconds()
# Start and end are on differnt days
total_worktime = 0
current_day = dt_start # marker for counting workdays
for current_day in business_daterange(dt_start, dt_end, self.weekend_days):
# Get work hour start and end for the current day
wt_start, wt_end = self.get_worktime_for(current_day)
# Worktime (in seconds) for this day
worktime = datetime.timedelta(hours=(wt_end - wt_start)).total_seconds()
if current_day == dt_start.date():
# The first day
if dt_start.hour < wt_start:
# starts prior to work hours
# add the complete day
total_worktime += worktime
elif dt_start.hour >= wt_end:
# Starts after work hours,
# no worktime for this day
continue
else:
# Fix end time to wt_end
dt_currentday_close = dt_start.replace(hour=wt_end,
minute=0,
second=0)
total_worktime += (dt_currentday_close - dt_start).total_seconds()
elif current_day == dt_end.date():
# The last day
if dt_end.hour >= wt_end:
# ends after work hours
# add the complete day
total_worktime += worktime
elif dt_end.hour < wt_start:
# ends before work hours
# no work time for this day
continue
else:
# Fix sart time ti wt_start
dt_currentday_open = dt_end.replace(hour=wt_start,
minute=0,
second=0)
total_worktime += (dt_end - dt_currentday_open).total_seconds()
else:
# increment one full day
total_worktime += worktime
return total_worktime
def is_free_day(self, day):
if self.is_weekend(day):
return True
if day.date() in get_public_holiday(day.year):
return True
return False
def is_weekend(self, day):
"""
Returns True if day lands on a weekend.
"""
for weekend in self.weekend_days:
if day.isoweekday() == weekend:
return True
return False
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment