2010-12-02 23:34:41 +00:00
< ? php
/**
* class . dates . php
*
* ProcessMaker Open Source Edition
* Copyright ( C ) 2004 - 2008 Colosa Inc . 23
*
* This program is free software : you can redistribute it and / or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation , either version 3 of the
* License , or ( at your option ) any later version .
*
* This program 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 Affero General Public License for more details .
*
* You should have received a copy of the GNU Affero General Public License
* along with this program . If not , see < http :// www . gnu . org / licenses />.
*
* For more information , contact Colosa Inc , 2566 Le Jeune Rd . ,
* Coral Gables , FL , 33134 , USA , or email info @ colosa . com .
2011-01-22 12:20:08 +00:00
* /**
* @ package workflow . classes
2010-12-02 23:34:41 +00:00
*/
/*
* Created on 21 / 01 / 2008
* This class is used for handling dates
*
* @ author David Callizaya < davidsantos @ colosa . com >
*/
require_once ( " classes/model/TaskPeer.php " );
require_once ( " classes/model/HolidayPeer.php " );
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 < hugo @ colosa . com >
* @ 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 )
{
2010-12-09 14:00:55 +00:00
//$oldDate=$this->calculateDate_noCalendar( $sInitDate, $iDuration, $sTimeUnit, $iTypeDay, $UsrUid, $ProUid, $TasUid);
2010-12-02 23:34:41 +00:00
//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 " ;
2010-12-09 14:00:55 +00:00
$startTime = ( float ) array_sum ( explode ( ' ' , microtime ()));
$calendarObj -> addCalendarLog ( " * Starting at: $startTime " );
2010-12-02 23:34:41 +00:00
$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 );
2010-12-09 14:00:55 +00:00
//$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 " );
2010-12-02 23:34:41 +00:00
$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 does not exists .
* @ 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 does not exists .
* @ 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 ;
}
}
?>