7

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;
Jeffrey04
  • 6,138
  • 12
  • 45
  • 68

12 Answers12

5

While for most datetime operations I would normally convert to Unixtime and perform addition subtraction etc. on the Unixtime integer, you may want to look at the Zend framework Zend_Date class.

This has a lot of the functionality you describe. Although Zend is billed as a "framework" it works exceptionally well as a class library to pick and chose elements from. We routinely include it in projects and then just pull in bits as and when we need them.

reefnet_alex
  • 9,703
  • 5
  • 33
  • 32
2

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
Manoj Sharma
  • 1,467
  • 2
  • 13
  • 20
user13414
  • 184
  • 1
  • 6
2

For adding a date, you can use the method DateTime::add (Adds an amount of days, months, years, hours, minutes and seconds to a DateTime object), available from php 5.3.0 onwards.

To find the difference between two dates, there's the DateTime::diff method; but there doesn't seem to be a method for counting the working days between two dates.

Vince Bowdren
  • 8,326
  • 3
  • 31
  • 56
2

PEAR::Date looks like it might have some useful functionality.

PEAR::Calendar might also be useful.

MarredCheese
  • 17,541
  • 8
  • 92
  • 91
Darryl Hein
  • 142,451
  • 95
  • 218
  • 261
2

strtotime() is useful but it does have some odd behaviors that can pop-up from time to time if you are not just using it to convert a formatted date/time string.

things like "+1 month" or "-3 days" can sometimes not give you what you expect it to output.

Laith
  • 349
  • 1
  • 7
1

The easiest method is to use a timestamp, representing the number of seconds since January 1, 2008. With a timestamp type, you can do things like...

now = time();
tomorrow = now + 24 * 60 * 60; // 24 hours * 60 minutes * 60 seconds

Check out the documentation for time(), date() and mktime() on the php web pages. Those are the three methods that I tend to use the most frequently.

Jarrett Meyer
  • 19,333
  • 6
  • 58
  • 52
0

You can use a combination of strtotime, mktime and date todo the arithmetic

Here is an example which uses a combo todo some arithmetic http://rushi.wordpress.com/2008/04/13/php-print-out-age-of-date-in-words/ I'll reproduce the code here for simplicity

if ($timestamp_diff < (60*60*24*7)) {
   echo floor($timestamp_diff/60/60/24)." Days";
} elseif ($timestamp_diff > (60*60*24*7*4)) {
   echo floor($timestamp_diff/60/60/24/7)." Weeks";
} else {
   $total_months = $months = floor($timestamp_diff/60/60/24/30);
   if($months >= 12) {
      $months = ($total_months % 12);
      $years&nbsp; = ($total_months - $months)/12;
      echo $years . " Years ";
   }
   if($months > 0)
      echo $months . " Months";
}
?>
Rushi
  • 1,652
  • 3
  • 17
  • 24
0

@Rushi I don't like strtotime() personally.. i don't know why but i discovered this morning that passing a string like this '2008-09-11 9:5 AM' to strtotime returns a false...

I don't think the code you provided solve the example problem 'Given two dates, how many workdays are there between the two dates? Implement in either SQL, or $pet_lang' and I haven't consider if I have a list of public holiday...

Jeffrey04
  • 6,138
  • 12
  • 45
  • 68
0

If you have a look at http://php.net/date , you will find some examples of using mktime() to perform operations.

A simple example would be to workout what tomorrows date would be. You can do that by simply adding 1, to the day value in mktime() as follows:

$tomorrow = date("Y-m-d", mktime(0, 0, 0, date("m"), date("d") + 1, date("Y")));

So here, you will receive a date in the form of YYYY-MM-DD containing tomorrows date. You can also subtract days by simply replacing '+' with '-'. mktime() makes life a lot easier, and saves you from having to do nested if statements and other such troublesome coding.

A J
  • 3,970
  • 14
  • 38
  • 53
ralfe
  • 1,412
  • 2
  • 15
  • 25
0

You can get number of days between two dates like this:

$days = (strtotime("2008-09-10") - strtotime("2008-09-12")) / (60 * 60 * 24);

And you can make function something like that (I don't have php installed in my work computer so i can't guarantee syntax is 100% correct)

function isWorkDay($date)
{
 // check if workday and return true if so
}

function numberOfWorkDays($startdate, $enddate)
{
  $workdays = 0;
  $tmp = strtotime($startdate);
  $end = strtotime($enddate);
  while($tmp <= $end)
  {
    if ( isWorkDay( date("Y-m-d",$tmp) ) ) $workdays++;
    $tmp += 60*60*24;
  }
  return $workdays;
}

If you don't like strtotime and you always have date in same format you can use explode function like

list($year, $month, day) = explode("-", $date);
Vertigo
  • 2,714
  • 1
  • 22
  • 24
  • currently date and time is collected from the user via a datetime picker javascript... however, when javascript is turned off, then the user can simply enter anything to the system and fails... – Jeffrey04 Sep 13 '08 at 19:20
0

I would strongly recommend using PHP 5.2's DateTime objects, rather than using UNIX timestamps, when doing date calculations. When you use the PHP date functions that return UNIX timestamps, you have a very limited range to work with (e.g. nothing before 1970).

JW.
  • 50,691
  • 36
  • 115
  • 143
-1

to get working days/holidays, postgresql CTE ftw -- see http://osssmb.wordpress.com/2009/12/02/business-days-working-days-sql-for-postgres-2/

helmi03
  • 169
  • 1
  • 7