. * * For more information, contact Colosa Inc, 2566 Le Jeune Rd., * Coral Gables, FL, 33134, USA, or email info@colosa.com. * / /* * Created on 21/01/2008 * This class is used for handling dates * * @author David Callizaya */ require_once ( "classes/model/TaskPeer.php" ); require_once ( "classes/model/HolidayPeer.php" ); /** * @package workflow.engine.classes */ class dates { private $holidays = array(); private $weekends = array(); private $range = array(); private $skipEveryYear = true; private $calendarDays = false; //by default we are using working days private $hoursPerDay = 8; //you should change this /** * Function that calculate a final date based on $sInitDate and $iDuration * This function also uses a Calendar component (class.calendar.php) where all the definition of * a User, task, Process or default calendar is defined. base on that information is possible to setup different calendars * and apply them to a task, process or user. Each calendar have Working Days, Business Hours and Holidays * * @name calculateDate * @access public * @author Hugo Loza * @param date $sInitDate * @param double $iDuration * @param string $sTimeUnit * @param string $iTypeDay * @param string $UsrUid * @param string $ProUid * @param string $TasUid * @return array('DUE_DATE'=>'Final calculated date formatted as Y-m-d H:i:s','DUE_DATE_SECONDS'=>'Final calculated date in seconds','OLD_DUE_DATE'=>'Using deprecate4d function','OLD_DUE_DATE_SECONDS'=>'Using deprecated function','DUE_DATE_LOG'=>'Log of all the calculations made') * @todo test this function with negative durations (for events) * * */ function calculateDate( $sInitDate, $iDuration, $sTimeUnit, $iTypeDay, $UsrUid = NULL, $ProUid = NULL, $TasUid =NULL ) { //$oldDate=$this->calculateDate_noCalendar( $sInitDate, $iDuration, $sTimeUnit, $iTypeDay, $UsrUid, $ProUid, $TasUid); //Set Calendar when the object is instanced in this order/priority (Task, User, Process, Default) G::LoadClass('calendar'); $calendarObj=new calendar($UsrUid,$ProUid,$TasUid); //Get next Business Hours/Range based on : switch(strtoupper($sTimeUnit)){ case 'DAYS': $hoursToProcess=$iDuration*8; break;//In Hours default: $hoursToProcess=$iDuration; break;//In Hours } $dateArray = explode(" ",$sInitDate); $currentDate = $dateArray[0]; $currentTime = isset($dateArray[1])? $dateArray[1]: "00:00:00"; $startTime=(float) array_sum(explode(' ',microtime())); $calendarObj->addCalendarLog("* Starting at: $startTime"); $calendarObj->addCalendarLog(">>>>> Hours to Process: $hoursToProcess"); $calendarObj->addCalendarLog(">>>>> Current Date: $currentDate"); $calendarObj->addCalendarLog(">>>>> Current Time: $currentTime"); $array_hours = explode(":",$currentTime); $seconds2 = $array_hours[2]; $minutes2 = 0; while($hoursToProcess>0){ $validBusinessHour=$calendarObj->getNextValidBusinessHoursRange($currentDate,$currentTime); //For Date/Time operations $currentDateA = explode("-",$validBusinessHour['DATE']); $currentTimeA = explode(":",$validBusinessHour['TIME']); $hour = $currentTimeA[0]; $minute = $currentTimeA[1]; $second = isset($currentTimeA[2])?$currentTimeA[2]:0; $month = $currentDateA[1]; $day = $currentDateA[2]; $year = $currentDateA[0]; $normalizedDate = date("Y-m-d H:i:s",mktime($hour,$minute,$second,$month,$day,$year)); $normalizedDateInt = mktime($hour,$minute,$second,$month,$day,$year); $normalizedDateSeconds = ($hour*60*60)+($minute*60); $arrayHour = explode(".",$hoursToProcess); if(isset($arrayHour[1])){ $minutes1 = $arrayHour[1]; $cadm = strlen($minutes1); $minutes2 = (($minutes1/pow(10,$cadm))*60); } $possibleTime=date("Y-m-d H:i:s",mktime($hour+$hoursToProcess,$minute+$minutes2,$second+$seconds2,$month,$day,$year)); $possibleTimeInt=mktime($hour+$hoursToProcess,$minute+$minutes2,$second+$seconds2,$month,$day,$year); $offsetPermitedMinutes="0"; $calendarBusinessEndA=explode(":",$validBusinessHour['BUSINESS_HOURS']['CALENDAR_BUSINESS_END']); $calendarBusinessEndNormalized=date("Y-m-d H:i:s",mktime($calendarBusinessEndA[0],$calendarBusinessEndA[1]+$offsetPermitedMinutes,0,$month,$day,$year)); $calendarBusinessEndInt=mktime($calendarBusinessEndA[0],$calendarBusinessEndA[1]+$offsetPermitedMinutes,0,$month,$day,$year); $calendarBusinessEndSeconds=($calendarBusinessEndA[0]*60*60)+($calendarBusinessEndA[1]*60); $calendarObj->addCalendarLog("Possible time: $possibleTime"); $calendarObj->addCalendarLog("Current Start Date/Time: $normalizedDate"); $calendarObj->addCalendarLog("Calendar Business End: $calendarBusinessEndNormalized"); if($possibleTimeInt>$calendarBusinessEndInt){ $currentDateTimeB = explode(" ",$calendarBusinessEndNormalized); $currentDate = $currentDateTimeB[0]; $currentTime = $currentDateTimeB[1]; $diff = abs($normalizedDateSeconds-$calendarBusinessEndSeconds); $diffHours = $diff/3600; $hoursToProcess = $hoursToProcess-$diffHours; } else{ $currentDateTimeA = explode(" ",$possibleTime); $currentDate = $currentDateTimeA[0]; $currentTime = $currentDateTimeA[1]; $hoursToProcess = 0; } $calendarObj->addCalendarLog("** Hours to Process: $hoursToProcess"); } $calendarObj->addCalendarLog("+++++++++++ Calculated Due Date $currentDate $currentTime"); $result['DUE_DATE'] = $currentDate." ".$currentTime; $result['DUE_DATE_SECONDS'] = strtotime($currentDate." ".$currentTime); //$result['OLD_DUE_DATE'] = date("Y-m-d H:i:s",$oldDate); //$result['OLD_DUE_DATE_SECONDS']= $oldDate; $endTime=(float) array_sum(explode(' ',microtime())); $calendarObj->addCalendarLog("* Ending at: $endTime"); $calcTime=round($endTime-$startTime,3); $calendarObj->addCalendarLog("** Processing time: ". sprintf("%.4f", ($endTime-$startTime))." seconds"); $result['DUE_DATE_LOG'] = $calendarObj->calendarLog; return $result; } /** * Calculate $sInitDate + $iDaysCount, skipping non laborable days. * Input: Any valid strtotime function type input. * Returns: Integer timestamp of the result. * Warning: It will hangs if there is no possible days to count as * "laborable". * @param date $sInitDate * @param double $iDuration * @param string $sTimeUnit * @param string $iTypeDay * @param string $UsrUid * @param string $ProUid * @param string $TasUid * @return integer timestamp of the result * @deprecated renamed by Hugo Loza (see calculateDate new function) */ function calculateDate_noCalendar( $sInitDate, $iDuration, $sTimeUnit, $iTypeDay, $UsrUid = NULL, $ProUid = NULL, $TasUid =NULL ) { //load in class variables the config of working days, holidays etc.. $this->prepareInformation( $UsrUid , $ProUid , $TasUid ); $iHours = 0; $iDays = 0; //convert the $iDuration and $sTimeUnit in hours and days, take in mind 8 hours = 1 day. and then we will have similar for 5 days = 1 weekends if ( strtolower ( $sTimeUnit ) == 'hours' ) { $iAux = intval(abs($iDuration)); $iHours = $iAux % $this->hoursPerDay; $iDays = intval( $iAux / $this->hoursPerDay ); } if ( strtolower ( $sTimeUnit ) == 'days' ) { $iAux = intval(abs($iDuration * $this->hoursPerDay)); $iHours = $iAux % 8; $iDays = intval( $iAux / 8 ); } $addSign = ( $iDuration >= 0 ) ? '+' : '-'; $iInitDate = strtotime( $sInitDate ); if ( $iTypeDay == 1 ) { // working days // if there are days calculate the days, $iEndDate = $this->addDays( $iInitDate , $iDays, $addSign ); // if there are hours calculate the hours, and probably add a day if the quantity of hours for last day > 8 hours $iEndDate = $this->addHours( $iEndDate , $iHours, $addSign ); } else { // $task->getTasTypeDay() == 2 // calendar days $iEndDate = strtotime( $addSign . $iDays . ' days ' , $iInitDate ); $iEndDate = strtotime( $addSign . $iHours . ' hours ' , $iEndDate ); } return $iEndDate; } /** * Calculate duration of the $sInitDate - $sEndDate. * @param date $sInitDate * @param date $sEndDate * @param string $UsrUid * @param string $ProUid * @param string $TasUid * @return int * */ function calculateDuration( $sInitDate, $sEndDate = '', $UsrUid = NULL, $ProUid = NULL, $TasUid = NULL) { $this->prepareInformation($UsrUid, $ProUid, $TasUid); if ((string)$sEndDate == '') { $sEndDate = date('Y-m-d H:i:s'); } if (strtotime($sInitDate) > strtotime($sEndDate)) { $sAux = $sInitDate; $sInitDate = $sEndDate; $sEndDate = $sAux; } $aAux1 = explode(' ', $sInitDate); $aAux2 = explode(' ', $sEndDate); $aInitDate = explode('-', $aAux1[0]); $aEndDate = explode('-', $aAux2[0]); $i = 1; $iWorkedDays = 0; $bFinished = false; $fHours1 = 0.0; $fHours2 = 0.0; if (count($aInitDate) != 3) { $aInitDate = array(0, 0, 0); } if (count($aEndDate) != 3) { $aEndDate = array(0, 0, 0); } if ($aInitDate !== $aEndDate) { while (!$bFinished && ($i < 10000)) { $sAux = date('Y-m-d', mktime(0, 0, 0, $aInitDate[1], $aInitDate[2] + $i, $aInitDate[0])); if ($sAux != implode('-', $aEndDate)) { if (!in_array($sAux, $this->holidays)) { if (!in_array(date('w', mktime(0, 0, 0, $aInitDate[1], $aInitDate[2] + $i, $aInitDate[0])), $this->weekends)) { $iWorkedDays++; } } $i++; } else { $bFinished = true; } } if (isset($aAux1[1])) { $aAux1[1] = explode(':', $aAux1[1]); $fHours1 = 24 - ($aAux1[1][0] + ($aAux1[1][1] / 60) + ($aAux1[1][2] / 3600)); } if (isset($aAux2[1])) { $aAux2[1] = explode(':', $aAux2[1]); $fHours2 = $aAux2[1][0] + ($aAux2[1][1] / 60) + ($aAux2[1][2] / 3600); } $fDuration = ($iWorkedDays * 24) + $fHours1 + $fHours2; } else { $fDuration = (strtotime($sEndDate) - strtotime($sInitDate)) / 3600; } return $fDuration; } /** * Configuration functions * @param string $UsrUid * @param string $ProUid * @param string $TasUid * @return void */ function prepareInformation( $UsrUid = NULL , $ProUid = NULL , $TasUid =NULL ) { // setup calendarDays according the task if (isset($TasUid)) { $task = TaskPeer::retrieveByPK( $TasUid ); if (!is_null($task)) { $this->calendarDays = ($task->getTasTypeDay()==2); } } //get an array with all holidays. $aoHolidays=HolidayPeer::doSelect(new Criteria()); $holidays=array(); foreach($aoHolidays as $holiday) $holidays[] = strtotime($holiday->getHldDate()); // by default the weekdays are from monday to friday $this->weekends = array(0,6); $this->holidays = $holidays; return ; } /** * Set to repeat for every year all dates defined in $this->holiday * @param $bSkipEveryYear * @return void */ function setSkipEveryYear( $bSkipEveryYear ) { $this->skipEveryYear = $bSkipEveryYear===true; } /** * Add a single date to holidays * @param data $sDate * @return void */ function addHoliday( $sDate ) { if ($date=strtotime( $sDate )) $this->holidays[]=self::truncateTime($date); else throw new Exception("Invalid date: $sDate."); } /** * Set all the holidays * @param date/array $aDate must be an array of (strtotime type) dates * @return void */ function setHolidays( $aDates ) { foreach($aDates as $sDate) $this->holidays = $aDates; } /** * Set all the weekends * @param array/integers $aWeekends must be an array of integers [1,7] * 1=Sunday * 7=Saturday * @return void */ function setWeekends( $aWeekends ) { $this->weekends = $aWeekends; } /** * Add one day of week to the weekends list * @param $iDayNumber must be an array of integers [1,7] * 1=Sunday * 7=Saturday * @return void */ function skipDayOfWeek( $iDayNumber ) { if ($iDayNumber<1 || $iDayNumber>7) throw new Exception("The day of week must be a number from 1 to 7."); $this->weekends[]=$iDayNumber; } /** * Add a range of non working dates * @param date $sDateA must be a (strtotime type) dates * @param date $sDateB must be a (strtotime type) dates * @return void */ function addNonWorkingRange( $sDateA , $sDateB ) { if ($date=strtotime( $sDateA )) $iDateA=self::truncateTime($date); else throw new Exception("Invalid date: $sDateA."); if ($date=strtotime( $sDateB )) $iDateB=self::truncateTime($date); else throw new Exception("Invalid date: $sDateB."); if ($iDateA>$iDateB) { $s=$iDateA;$iDateA=$iDateB;$iDateB=$s; }; $this->range[]=array( $iDateA , $iDateB ); } /** * PRIVATE UTILITARY FUNCTIONS * Add days to the date * @param date $iInitDate * @param int $iDaysCount * @param string $addSign * @return date $iEndDate */ private function addDays( $iInitDate , $iDaysCount, $addSign = '+' ) { $iEndDate = $iInitDate; $aList = $this->holidays; for( $r=1; $r <= $iDaysCount ; $r++) { $iEndDate = strtotime( $addSign . "1 day", $iEndDate ); $dayOfWeek = idate('w',$iEndDate); //now sunday=0 if ( array_search( $dayOfWeek, $this->weekends )!== false ) $r--; //continue loop, but we are adding one more day. } return $iEndDate; } /** * Add hours to the date * @param date $iInitDate * @param int $iHoursCount * @param string $addSign * @return $iEndDate */ private function addHours( $sInitDate , $iHoursCount, $addSign = '+' ) { $iEndDate = strtotime( $addSign . $iHoursCount ." hours", $sInitDate ); return $iEndDate; } /** * Compare if the date is in range * @param $iDate = valid timestamp * @return true if it is within any of the ranges defined. */ private function inRange( $iDate ) { $aRange = $this->range; $iYear = idate( 'Y', $iDate ); foreach($aRange as $key => $rang) { if ($this->skipEveryYear) { $deltaYears = idate( 'Y', $rang[1] ) - idate( 'Y', $rang[0] ); $rang[0]=self::changeYear( $rang[0] , $iYear ); $rang[1]=self::changeYear( $rang[1] , $iYear + $deltaYears ); } if (($iDate>=$rang[0]) && ($iDate<=$rang[1])) return true; } return false; } /** * Truncate a date * @param $iDate = valid timestamp * @return date */ private function truncateTime( $iDate ) { return mktime(0,0,0,idate('m',$iDate),idate('d',$iDate),idate('Y',$iDate)); } /** * Get time * @param timestamp $iDate * @return date */ private function getTime( $iDate ) { return array(idate('H',$iDate),idate('m',$iDate),idate('s',$iDate)); } /** * Set time * @param timestamp $iDate * @param timestamp $aTime * @return date */ private function setTime( $iDate , $aTime ) { return mktime($aTime[0],$aTime[1],$aTime[2],idate('m',$iDate),idate('d',$iDate),idate('Y',$iDate)); } /** * Returns an array with all the dates of $this->skip['List'] with its * year changed to $iYear. * Warning: Don't know what to do if change a 29-02-2004 to 29-02-2005 * the last one doesn't exist. * @param List $iYear * @return array */ private function listForYear( $iYear ) { $aList = $this->holidays; foreach($aList as $k => $v) { $aList[$k] = self::changeYear( $v , $iYear ); } return $aList; } /** * Returns an array with all the dates of $this->skip['List'] with its * year changed to $iYear. * Warning: Don't know what to do if change a 29-02-2004 to 29-02-2005 * the last one doesn't exist. * @param array $iYear * @param date $iDate * @return array */ private function changeYear( $iDate , $iYear ) { if ($delta = ( $iYear - idate('Y',$iDate) ) ) { $iDate = strtotime( "$delta year" , $iDate ); } return $iDate; } } ?>