/********************************************************************************************************* bizMins - created by Doug Boude (dougboude@gmail.com) Returns the total number of business minutes between two datetimes. Return unit can be changed using parameters. Parameters: begin_dt - required - the datetime of the start of the calculation period. If no time provided, default is 8 am. end_dt - required - the datetime of the end of the calculation period. If no time is provided, default is 5 pm. startofday - optional - the hour that the workday begins. must be from 0 to 24, must be less than endofday. endofday - optional - the hour the workday ends. Must be 0 to 24, must be greater than startofday. lunchhour - optional - when lunch hour begins. must be 0 to 24, must be between startofday and endofday. Default is 12 noon. returnUnit - optional - the unit that the calculation results should be returned in. Valid choices are n(minutes), s(seconds), h(hours), d(days). Default return unit is in minutes. *********************************************************************************************************/ function bizMins(begin_dt,end_dt){//only those parameters required are included in the parentheses) //init optional params with default values var startofday=8; var endofday=17; var lunchhour=12; var returnUnit="n"; //init other variables to be used throughout the UDF var useStartEndTimes=false; var totMins=0; //variable to hold running total of business minutes var datelist=""; //list variable to hold a generated list of date times; start date, end date, and every date in between. var tmpdate=""; //working variable var i=1;//just a generic counter var eodate="";//temp variable to hold the created datetime used in calculating minutes between a given datetime and end of that day var tmpMins=0;//temp variable to hold calculated minutes to be added in var workdayminutes=0;//constant that will be calculated after validating options passed in. //validate that our start and end dates are valid and in the correct order, otherwise toss the caller an error and halt everything now. if(not isDate(begin_dt) OR not isDate(end_dt)){//if either one is not a real date throw("customError", "bizMins requires two valid datetimes. You passed in:" & begin_dt & " and " & end_dt); return; } if(DateCompare(begin_dt,end_dt,"n") eq 0){//if they are the same date and time throw("customError","bizMins requires two valid datetimes that are not the same. You passed in:" & begin_dt & " and " & end_dt); return; } if(DateCompare(begin_dt,end_dt,"n") eq 1){//meaning our begin_dt is greater than our end_dt throw("customError","bizMins requires that your start datetime be less than your end datetime. You passed in:" & begin_dt & " and " & end_dt); return; } //Okay, made it through that gauntlet. Now work with any optional parameters that were passed in and use their values where //we're able to do so. //if we received optional parameters startofday and endofday, make sure the latter is greater than the former if(ArrayLen(Arguments) gte 4){ if(validHour(Arguments[4]) and validHour(Arguments[3])){ if(Arguments[4] gt Arguments[3]){ useStartEndTimes=true; //both parameters were valid numeric hours, and the end time was greater than the start time } } } //final check and validation of optional parameters switch(ArrayLen(Arguments)) { case 6: if (listfind("s,n,h,d",Arguments[6]) gt 0){ returnUnit=Arguments[6]; } case 5: if (validHour(Arguments[5])){ if(useStartEndTimes){//if we're using passed in start and end hours, make sure lunch is between the two if (Arguments[5] gt Arguments[3] and Arguments[5] lt Arguments[4]){ lunchhour=int(Arguments[5]); } } else{//not using the passed in start and end times due to the fact they failed validation, so compare to default values. if (Arguments[5] gt startofday and Arguments[5] lt endofday){ lunchhour=int(Arguments[5]); } } } case 4: if(useStartEndTimes){ endofday=Arguments[4]; } case 3: if(useStartEndTimes){ startofday=Arguments[3]; } case 2: break; }//end of switch statement //End of evaluating optional parameters. Ready to rock and roll. Let's do some kalkulashuns //Calculate the number of hours in a workday for this invocation...(dates used in these next steps are insignificant...don't worry about changing them) tmpdate=createdatetime(2004,1,30,startofday,0,0); eodate=createdatetime(2004,1,30,endofday,0,0); workdayminutes=abs(datediff("n",tmpdate,eodate)); workdayminutes=workdayminutes-60;//gotta take lunch outa there... //reset the temp vars for later use tmpdate=""; eodate=""; /* build list of datetimes to feed our main iteration...every date between startdate and end date, inclusive */ datelist=listappend(datelist,begin_dt); //add in the start date tmpdate=begin_dt; for(i=1; i lt abs(datediff("d",begin_dt,end_dt)); i=i+1){//add in dates in between... tmpdate=dateadd("d",1,createdatetime(year(tmpdate),month(tmpdate),day(tmpdate),endofday,0,0)); datelist=listappend(datelist,tmpdate); } datelist=listappend(datelist,end_dt);//tack on the end date // writeoutput("list of dates:" & datelist & "
"); //list of dates built, including startdate, all dates in between, and end date. //It is possible that the two dates submitted are for the same date, with different times. Evaluate that possibility here if(listlen(datelist) eq 2 and dateformat(begin_dt,"mm/dd/yy") IS dateformat(end_dt,"mm/dd/yy")){ if (not IsHoliday(begin_dt) and not IsWeekend(begin_dt)){ if (hour(listgetat(datelist,2)) gt startofday){//make sure that we have some minutes that actually extend into the workday if (hour(listgetat(datelist,1)) gt startofday){ totMins=abs(datediff("n",begin_dt,end_dt)); } else { eodate=createdatetime(year(tmpdate),month(tmpdate),day(tmpdate),startofday,0,0); totMins=abs(datediff("n",eodate,end_dt)); } } } } else{//not the same date...we have minutes to calculate /* Now, Loop through each datetime, ask a few questions of it, add in any appropriate biz mins to the running total */ for (i=1; i lte listlen(datelist); i=i+1){ //grab this iteration's date... tmpdate=listgetat(datelist,i); //run this date through the first gauntlet of questions //first, we make sure the date is not a holiday and is not a weekend if (not IsHoliday(tmpdate) AND not IsWeekend(tmpdate)){ //writeoutput(" is not a weekend or holiday
"); //Is this date the starting or ending date? IF so, we'll need to calculate partial work hours. if (tmpdate IS begin_dt OR tmpdate IS end_dt){//go through the logic to calculate partial work hours if (tmpdate IS begin_dt){//if it's the start date, we look at things different than if its the end date //writeoutput(tmpdate & " is the start date
"); if(hour(tmpdate) lt startofday){//hour is earlier than start of day. Add in a full day's minutes. totmins=totmins + workdayminutes; } else if (hour(tmpdate) lt endofday and hour(tmpdate) gte startofday){//hour is in the midst of the workday, so figure out how many minutes to add in //now calculate the number of minutes between tmpdate hour and endofday hour eodate=createdatetime(year(tmpdate),month(tmpdate),day(tmpdate),endofday,0,0); tmpMins=abs(datediff("n",tmpdate,eodate)); //Now, if our timespan contains the lunch hour, we need to subtract 60 minutes if(hour(tmpdate) lte lunchhour){//we need to subtract lunch time tmpMins=tmpMins-60; } //NOW, add in this day's minutes to the running total totmins=totmins+tmpMins; } //end of else if } else {//it's the end date. evaluate things in that light. //writeoutput(tmpdate & " is the end date
"); //if hour(tmpdate) lt startofday, then this datetime ended before the work day began...we'll be adding nothing if this is the case. There is no if or else looking for this situation. if(hour(tmpdate) gte endofday){//hour is greater than end of day(meaning they worked past hours on the last date). Add in a full day's minutes. totmins=totmins + workdayminutes; } else if (hour(tmpdate) lt endofday and hour(tmpdate) gte startofday){//hour is in the midst of the workday, so figure out how many minutes to add in //now calculate the number of minutes between tmpdate hour and endofday hour eodate=createdatetime(year(tmpdate),month(tmpdate),day(tmpdate),startofday,0,0); tmpMins=abs(datediff("n",tmpdate,eodate)); //Now, if our timespan contains the lunch hour, we need to subtract 60 minutes if(hour(tmpdate) gte lunchhour){//we need to subtract lunch time tmpMins=tmpMins-60; } //NOW, add in this day's minutes to the running total totmins=totmins+tmpMins; } //end of else if }//end of else section evaluating an end date's minutes }//close of IF this date is start or end date else {//this date is an in between date, so we just add in a full day's minutes to our total totmins=totmins + workdayminutes; } } else {//tmpdate is either a weekend, holiday, or has a timestamp after working hours, so we're skipping this date's minutes //writeoutput(" IS a weekend or holiday
"); } }//close of for loop through list of dates }//close of the big IF that looked to see if the incoming dates were the same //Got total minutes. Now to convert to desired "return units" as specified in var returnunits, and return the value switch(returnUnit){ case "n": return totMins; break; case "h": return (totMins/60); break; case "s": return (totMins*60); break; default: return (totMins/workdayminutes); break; } }//end of function /********************************************************************************************************* *********** Supporting UDFS used within the BizMins ****************************************************** **********************************************************************************************************/ /***************************************************************** Doug Boude 9/19/04 ISHOLIDAY(datein...valid incoming date) Function returning true or false to determine if date is a company holiday ******************************************************************/ function isholiday(datein){ var holidaylist=""; var thisdate=""; var j=1; if(not isDate(datein)){//if either one is not a real date throw("customError", "IsHoliday requires a valid date."); return; } holidaylist=GetCompanyHolidays(year(datein)); for(j=1; j lte listlen(holidaylist); j=j+1){ thisdate=listgetat(holidaylist,j); if (dateformat(thisdate,"mm/dd/yy") IS dateformat(datein,"mm/dd/yy")){ return true; } } return false; } /******************************************************************** Doug Boude 9/19/04 ISWEEKEND(datein...valid incoming date) Function returning true or false to determine if date is a Saturday or Sunday *********************************************************************/ function isweekend(datein){ if(not isDate(datein)){//if datein is not a real date throw("customError", "IsWeekend requires a valid date."); return; } if (dayofweek(datein) lt 7 and dayofweek(datein) gt 1){ return false; } else{ return true; } } /******************************************************************** Doug Boude 2/27/07 validHour(hour to validate...valid incoming date) Function returning true or false to determine if hour is a valid integer between 0 and 2359 *********************************************************************/ function validHour(hourIn){ if(isnumeric(hourIn) AND (int(hourIn) IS hourIn) AND (hourIn gte 0 AND hourIn lte 2359)){ return true; } else { return false; } } /* * Returns a list of the current calendar year Company-observed holidays. * * @param calendar_year year to get holidays for... * @return Returns a list of dates... */ function getCompanyHolidays(calendar_year) { var thisYear=calendar_year; var NewYearsDay=""; var MemorialDay=""; var IndependenceDay=""; var LaborDay=""; var ThanksgivingDay=""; var ChristmasDay=""; var NextNewYearsDay=""; var HolidayList=""; // if next new year's day is on a saturday, we have to put the holiday on the current ay=""; ChristmasDay=""; NextNewYearsDay=""; HolidayList=""; // if next new year's day is on a saturday, we have to put the holiday on the current calendar year Dec 31st. - screwy... if(dayofweek("1-1-" & thisYear+1) eq 7) NextNewYearsDay="12-31-" & thisYear; else if(dayofweek("1-1-" & thisYear+1) eq 1) NextNewYearsDay="1-2-" & thisYear+1; else NextNewYearsDay="1-1-" & thisYear+1; if(dayofweek("1-1-" & thisYear) eq 1) NewYearsDay="1-2-" & thisYear; else NewYearsDay="1-1-" & thisYear; MemorialDay="5-" & GetLastOccOfDayInMonth(2,5,thisYear) & "-" & thisYear; if(dayofweek("7-4-" & thisYear) eq 7) IndependenceDay="7-3-" & thisYear; else if(dayofweek("7-4-" & thisYear) eq 1) IndependenceDay="7-5-" & thisYear; else IndependenceDay="7-4-" & thisYear; LaborDay="9-" & GetNthOccOfDayInMonth(1,2,9,thisYear) & "-" & thisYear; ThanksgivingDay="11-" & GetNthOccOfDayInMonth(4,5,11,thisYear) & "-" & thisYear; if(dayofweek("12-25-" & thisYear) eq 7) ChristmasDay="12-24-" & thisYear; else if(dayofweek("7-4-" & thisYear) eq 1) ChristmasDay="12-26-" & thisYear; else ChristmasDay="12-25-" & thisYear; HolidayList=createodbcdatetime(NewYearsDay) & ","; HolidayList=HolidayList & createodbcdatetime(MemorialDay) & ","; HolidayList=HolidayList & createodbcdatetime(IndependenceDay) & ","; HolidayList=HolidayList & createodbcdatetime(LaborDay) & ","; HolidayList=HolidayList & createodbcdatetime(ThanksgivingDay) & ","; HolidayList=HolidayList & createodbcdatetime(ChristmasDay) & ","; HolidayList=HolidayList & createodbcdatetime(NextNewYearsDay); //return returnStruct; return HolidayList; } /** * Returns the day of the month(1-31) of an Nth Occurrence of a day (1-sunday,2-monday etc.)in a given month. * * @param NthOccurrence A number representing the nth occurrence.1-5. * @param TheDayOfWeek A number representing the day of the week (1=Sunday, 2=Monday, etc.). * @param TheMonth A number representing the Month (1=January, 2=February, etc.). * @param TheYear The year. * @return Returns a numeric value. * @author Ken McCafferty (mccjdk@yahoo.com) * @version 1, August 28, 2001 */ function GetNthOccOfDayInMonth(NthOccurrence,TheDayOfWeek,TheMonth,TheYear) { Var TheDayInMonth=0; if(TheDayOfWeek lt DayOfWeek(CreateDate(TheYear,TheMonth,1))){ TheDayInMonth= 1 + NthOccurrence*7 + (TheDayOfWeek - DayOfWeek(CreateDate(TheYear,TheMonth,1))) MOD 7; } else{ TheDayInMonth= 1 + (NthOccurrence-1)*7 + (TheDayOfWeek - DayOfWeek(CreateDate(TheYear,TheMonth,1))) MOD 7; } //If the result is greater than days in month or less than 1, return -1 if(TheDayInMonth gt DaysInMonth(CreateDate(TheYear,TheMonth,1)) OR TheDayInMonth lt 1){ return -1; } else{ return TheDayInMonth; } } /** * Returns the day of the month(1-31) of Last Occurrence of a day (1-sunday,2-monday etc.) in a given month. * * @param TheDayOfWeek Ordinal value representing the desired day of the week (1-sunday,2-monday etc.) * @param TheMonth Ordinal value representing the month (1-January, 2-February, etc.) * @param TheYear The year. * @return Returns a numeric value. * @author Ken McCafferty (mccjdk@yahoo.com) * @version 1.0, August 22, 2001 */ function GetLastOccOfDayInMonth(TheDayOfWeek,TheMonth,TheYear) { //Find The Number of Days in Month var TheDaysInMonth=DaysInMonth(CreateDate(TheYear,TheMonth,1)); //find the day of week of Last Day var DayOfWeekOfLastDay=DayOfWeek(CreateDate(TheYear,TheMonth,TheDaysInMonth)); //subtract DayOfWeek var DaysDifference=DayOfWeekOfLastDay - TheDayOfWeek; //Add a week if it is negative if(DaysDifference lt 0) { DaysDifference=DaysDifference + 7; } return TheDaysInMonth-DaysDifference; }