/*********************************************************************************************************
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;
}