I've created a function to calculate the working minutes between two timestamps.
I class working minutes as those between 9am - 5pm and not on weekends or national holidays.
The holidays are those in the UK.
I think the function itself is quite quick, although generating the initial series probably takes a bit of time.
Because the working dates are fixed, I might just put them into a database table and load them each time the function is called, probably a bit faster.
import pandas as pd
import datetime
from pandas.tseries.holiday import Holiday, AbstractHolidayCalendar
from pandas.tseries.offsets import CDay
class HolidayCalendar(AbstractHolidayCalendar):
rules =[Holiday('Xmas Day generic',month=12,day=25),
Holiday('NYD generic',month=1,day=1),
Holiday('Boxing Day generic',month=12,day=26),
Holiday('Good Friday 2015',year=2015,month=4,day=3),
Holiday('Easter Monday 2015',year=2015,month=4,day=6),
Holiday('May Bank Holiday 2015',year=2015,month=5,day=4),
Holiday('Spring Bank Holiday 2015',year=2015,month=5,day=25),
Holiday('Summer Bank Holiday 2015',year=2015,month=8,day=31),
Holiday('Boxing Day 2015',year=2015,month=12,day=28),
Holiday('Good Friday 2016',year=2016,month=3,day=25),
Holiday('Easter Monday 2016',year=2016,month=3,day=28),
Holiday('May Bank 2016',year=2016,month=5,day=2),
Holiday('Spring Bank 2016',year=2016,month=5,day=30),
Holiday('Summer Bank 2016',year=2016,month=8,day=29),
Holiday('Boxing Day 2016',year=2016,month=12,day=26),
Holiday('Xmas Day 2016',year=2016,month=12,day=27),
Holiday('NYD 2017',year=2017,month=1,day=2),
Holiday('Good Friday 2017',year=2017,month=4,day=14),
Holiday('Easter Monday 2017',year=2017,month=4,day=17),
Holiday('May Bank 2017',year=2017,month=5,day=1),
Holiday('Spring Bank 2017',year=2017,month=5,day=29),
Holiday('Summer Bank 2017',year=2017,month=8,day=28)]
cal = HolidayCalendar()
dayindex = pd.bdate_range(datetime.date(2015,1,1),datetime.date.today(),freq=CDay(calendar=cal))
day_series = dayindex.to_series()
def count_mins(start,end):
starttime = datetime.datetime.fromtimestamp(int(start)/1000)
endtime = datetime.datetime.fromtimestamp(int(end)/1000)
days = day_series[starttime.date():endtime.date()]
daycount = len(days)
if daycount == 0:
return daycount
else:
startday = datetime.datetime(days[0].year,
days[0].month,
days[0].day,
hour=9,
minute=0)
endday = datetime.datetime(days[-1].year,
days[-1].month,
days[-1].day,
hour=17,
minute=0)
if daycount == 1:
if starttime < startday:
periodstart = startday
else:
periodstart = starttime
if endtime > endday:
periodend = endday
else:
periodend = endtime
return (periodend - periodstart).seconds/60
if daycount == 2:
if starttime < startday:
first_day_mins = 480
else:
first_day_mins = (startday.replace(hour=17)-starttime).seconds/60
if endtime > endday:
second_day_mins = 480
else:
second_day_mins = (endtime-endday.replace(hour=9)).seconds/60
return (first_day_mins + second_day_mins)
else:
if starttime < startday:
first_day_mins = 480
else:
first_day_mins = (startday.replace(hour=17)-starttime).seconds/60
if endtime > endday:
second_day_mins = 480
else:
second_day_mins = (endtime-endday.replace(hour=9)).seconds/60
return (first_day_mins + second_day_mins + ((daycount-2)*480))
Building on the advice from Graipher, I've settled on the below:
def count_mins(start,end):
starttime = datetime.datetime.fromtimestamp(int(start)/1000)
endtime = datetime.datetime.fromtimestamp(int(end)/1000)
days = day_series[starttime.date():endtime.date()]
daycount = len(days)
if not days:
return 0
else:
startday = days[0].replace(hour=9, minute=0)
endday = days[-1].replace(hour=17, minute=0)
if daycount == 1:
periodstart = max(startday, starttime)
periodend = min(endday, endtime)
return (periodend - periodstart).seconds/60
if daycount == 2:
first_day_mins = min(480, ((startday.replace(hour=17)-starttime).seconds/60))
second_day_mins = min(480, ((endtime-endday.replace(hour=9)).seconds/60))
return (first_day_mins + second_day_mins)
else:
first_day_mins = min(480, ((startday.replace(hour=17)-starttime).seconds/60))
last_day_mins = min(480, ((endtime-endday.replace(hour=9)).seconds/60))
return (first_day_mins + last_day_mins + ((daycount-2)*480))