How do you actually perform datetime operations such as adding date, finding difference, find out how many days excluding weekends in an interval? I personally started to pass some of these operations to my postgresql dbms as typically I would only need to issue one sql statement to obtain an answer, however, to do it in PHP way I would have to write a lot more code that means more chances for errors to occur…
Are there any libraries in PHP that does datetime operation in a way that don’t require a lot of code? that beats sql in a situation where ‘Given two dates, how many workdays are there between the two dates? Implement in either SQL, or $pet_lang’ that is solved by making this query?
SELECT COUNT(*) AS total_days FROM (SELECT date '2008-8-26' + generate_series(0, (date '2008-9-1' - date '2008-8-26')) AS all_days) AS calendar WHERE EXTRACT(isodow FROM all_days) < 6;
Advertisement
Answer
PHP5+’s DateTime object is useful because it is leap time and daylight savings aware, but it needs some extension to really solve the problem. I wrote the following to solve a similar problem. The find_WeekdaysFromThisTo() method is brute-force, but it works reasonably quickly if your time span is less than 2 years.
$tryme = new Extended_DateTime('2007-8-26'); $newer = new Extended_DateTime('2008-9-1'); print 'Weekdays From '.$tryme->format('Y-m-d').' To '.$newer->format('Y-m-d').': '.$tryme -> find_WeekdaysFromThisTo($newer) ."n"; /* Output: Weekdays From 2007-08-26 To 2008-09-01: 265 */ print 'All Days From '.$tryme->format('Y-m-d').' To '.$newer->format('Y-m-d').': '.$tryme -> find_AllDaysFromThisTo($newer) ."n"; /* Output: All Days From 2007-08-26 To 2008-09-01: 371 */ $timefrom = $tryme->find_TimeFromThisTo($newer); print 'Between '.$tryme->format('Y-m-d').' and '.$newer->format('Y-m-d').' there are '. $timefrom['years'].' years, '.$timefrom['months'].' months, and '.$timefrom['days']. ' days.'."n"; /* Output: Between 2007-08-26 and 2008-09-01 there are 1 years, 0 months, and 5 days. */ class Extended_DateTime extends DateTime { public function find_TimeFromThisTo($newer) { $timefrom = array('years'=>0,'months'=>0,'days'=>0); // Clone because we're using modify(), which will destroy the object that was passed in by reference $testnewer = clone $newer; $timefrom['years'] = $this->find_YearsFromThisTo($testnewer); $mod = '-'.$timefrom['years'].' years'; $testnewer -> modify($mod); $timefrom['months'] = $this->find_MonthsFromThisTo($testnewer); $mod = '-'.$timefrom['months'].' months'; $testnewer -> modify($mod); $timefrom['days'] = $this->find_AllDaysFromThisTo($testnewer); return $timefrom; } // end function find_TimeFromThisTo public function find_YearsFromThisTo($newer) { /* If the passed is: not an object, not of class DateTime or one of its children, or not larger (after) $this return false */ if (!is_object($newer) || !($newer instanceof DateTime) || $newer->format('U') < $this->format('U')) return FALSE; $count = 0; // Clone because we're using modify(), which will destroy the object that was passed in by reference $testnewer = clone $newer; $testnewer -> modify ('-1 year'); while ( $this->format('U') < $testnewer->format('U')) { $count ++; $testnewer -> modify ('-1 year'); } return $count; } // end function find_YearsFromThisTo public function find_MonthsFromThisTo($newer) { /* If the passed is: not an object, not of class DateTime or one of its children, or not larger (after) $this return false */ if (!is_object($newer) || !($newer instanceof DateTime) || $newer->format('U') < $this->format('U')) return FALSE; $count = 0; // Clone because we're using modify(), which will destroy the object that was passed in by reference $testnewer = clone $newer; $testnewer -> modify ('-1 month'); while ( $this->format('U') < $testnewer->format('U')) { $count ++; $testnewer -> modify ('-1 month'); } return $count; } // end function find_MonthsFromThisTo public function find_AllDaysFromThisTo($newer) { /* If the passed is: not an object, not of class DateTime or one of its children, or not larger (after) $this return false */ if (!is_object($newer) || !($newer instanceof DateTime) || $newer->format('U') < $this->format('U')) return FALSE; $count = 0; // Clone because we're using modify(), which will destroy the object that was passed in by reference $testnewer = clone $newer; $testnewer -> modify ('-1 day'); while ( $this->format('U') < $testnewer->format('U')) { $count ++; $testnewer -> modify ('-1 day'); } return $count; } // end function find_AllDaysFromThisTo public function find_WeekdaysFromThisTo($newer) { /* If the passed is: not an object, not of class DateTime or one of its children, or not larger (after) $this return false */ if (!is_object($newer) || !($newer instanceof DateTime) || $newer->format('U') < $this->format('U')) return FALSE; $count = 0; // Clone because we're using modify(), which will destroy the object that was passed in by reference $testnewer = clone $newer; $testnewer -> modify ('-1 day'); while ( $this->format('U') < $testnewer->format('U')) { // If the calculated day is not Sunday or Saturday, count this day if ($testnewer->format('w') != '0' && $testnewer->format('w') != '6') $count ++; $testnewer -> modify ('-1 day'); } return $count; } // end function find_WeekdaysFromThisTo public function set_Day($newday) { if (is_int($newday) && $newday > 0 && $newday < 32 && checkdate($this->format('m'),$newday,$this->format('Y'))) $this->setDate($this->format('Y'),$this->format('m'),$newday); } // end function set_Day public function set_Month($newmonth) { if (is_int($newmonth) && $newmonth > 0 && $newmonth < 13) $this->setDate($this->format('Y'),$newmonth,$this->format('d')); } // end function set_Month public function set_Year($newyear) { if (is_int($newyear) && $newyear > 0) $this->setDate($newyear,$this->format('m'),$this->format('d')); } // end function set_Year } // end class Extended_DateTime