calc the working hours between 2 dates in PHP

前端 未结 3 1282
南方客
南方客 2020-12-03 23:49

I have a function to return the difference between 2 dates, however I need to work out the difference in working hours, assuming Monday to Friday (9am to 5:30pm):

相关标签:
3条回答
  • 2020-12-04 00:34

    As the old saying goes "if you want something done right do it yourself". Not saying this is optimal but its atleast returning the correct amount of hours for me.

    function biss_hours($start, $end){
    
        $startDate = new DateTime($start);
        $endDate = new DateTime($end);
        $periodInterval = new DateInterval( "PT1H" );
    
        $period = new DatePeriod( $startDate, $periodInterval, $endDate );
        $count = 0;
    
          foreach($period as $date){
    
               $startofday = clone $date;
               $startofday->setTime(8,30);
    
               $endofday = clone $date;
               $endofday->setTime(17,30);
    
        if($date > $startofday && $date <= $endofday && !in_array($date->format('l'), array('Sunday','Saturday'))){
    
            $count++;
        }
    
    }
    
    //Get seconds of Start time
    $start_d = date("Y-m-d H:00:00", strtotime($start));
    $start_d_seconds = strtotime($start_d);
    $start_t_seconds = strtotime($start);
    $start_seconds = $start_t_seconds - $start_d_seconds;
    
    //Get seconds of End time
    $end_d = date("Y-m-d H:00:00", strtotime($end));
    $end_d_seconds = strtotime($end_d);
    $end_t_seconds = strtotime($end);
    $end_seconds = $end_t_seconds - $end_d_seconds;
    
    $diff = $end_seconds-$start_seconds;
    
    if($diff!=0):
        $count--;
    endif;
    
    $total_min_sec = date('i:s',$diff);
    
    return $count .":".$total_min_sec;
    }
    
    $start = '2014-06-23 12:30:00';
    $end = '2014-06-27 15:45:00';
    
    $go = biss_hours($start,$end);
    echo $go;
    
    0 讨论(0)
  • 2020-12-04 00:39

    This example uses PHP's built in DateTime classes to do the date math. How I approached this was to start by counting the number of full working days between the two dates and then multiply that by 8 (see notes). Then it gets the hours worked on the partial days and adds them to the total hours worked. Turning this into a function would be fairly straightforward to do.

    Notes:

    • Does not take timestamps into account. But you already know how to do that.
    • Does not handle holidays. (That can be easily added by using an array of holidays and adding it to where you filter out Saturdays and Sundays).
    • Requires PHP 5.3.6+
    • Assumes an 8 hour workday. If employees do not take lunch change $hours = $days * 8; to $hours = $days * 8.5;

    .

    <?php
    // Initial datetimes
    $date1 = new DateTime('2012-03-22 11:29:16');
    $date2 = new DateTime('2012-03-24 03:58:58');
    
    // Set first datetime to midnight of next day
    $start = clone $date1;
    $start->modify('+1 day');
    $start->modify('midnight');
    
    // Set second datetime to midnight of that day
    $end = clone $date2;
    $end->modify('midnight');
    
    // Count the number of full days between both dates
    $days = 0;
    
    // Loop through each day between two dates
    $interval = new DateInterval('P1D');
    $period = new DatePeriod($start, $interval, $end);
    foreach ($period as $dt) {
        // If it is a weekend don't count it
        if (!in_array($dt->format('l'), array('Saturday', 'Sunday'))) {
            $days++;
        }
    }
    
    // Assume 8 hour workdays
    $hours = $days * 8;
    
    // Get the number of hours worked on the first day
    $date1->modify('5:30 PM');
    $diff = $date1->diff($start);
    $hours += $diff->h;
    
    // Get the number of hours worked the second day
    $date1->modify('8 AM');
    $diff = $date2->diff($end);
    $hours += $diff->h;
    
    echo $hours;
    

    See it in action

    Reference

    • DateTime Class
    • DatePeriod Class
    • DateInterval Class
    0 讨论(0)
  • 2020-12-04 00:48

    Here's what I've come up with.

    My solution checks the start and end times of the original dates, and adjusts them according to the actual start and end times of the work day (if the original start time is before work's opening time, it sets it to the latter).

    After this is done to both start and end times, the times are compared to retrieve a DateInterval diff, calculating the total days, hours, etc. The date range is then checked for any weekend days, and if found, one total day is reduced from the diff.

    Finally, the hours are calculated as commented. :)

    Cheers to John for inspiring some of this solution, particularly the DatePeriod to check for weekends.

    Gold star to anyone who breaks this; I'll be happy to update if anyone finds a loophole!


    Gold star to myself, I broke it! Yeah, weekends are still buggy (try starting at 4pm on Saturday and ending at 1pm Monday). I will conquer you, work hours problem!

    Ninja edit #2: I think I took care of the weekend bugs by reverting the start and end times to the most recent respective weekday if they fall on a weekend. Got good results after testing a handful of date ranges (starting and ending on the same weekend barfs, as expected). I'm not entirely convinced this is as optimized / simple as it could be, but at least it works better now.


    // Settings
    $workStartHour = 9;
    $workStartMin = 0;
    $workEndHour = 17;
    $workEndMin = 30;
    $workdayHours = 8.5;
    $weekends = ['Saturday', 'Sunday'];
    $hours = 0;
    
    // Original start and end times, and their clones that we'll modify.
    $originalStart = new DateTime('2012-03-22 11:29:16');
    $start = clone $originalStart;
    
    // Starting on a weekend? Skip to a weekday.
    while (in_array($start->format('l'), $weekends))
    {
        $start->modify('midnight tomorrow');
    }
    
    $originalEnd = new DateTime('2012-03-24 03:58:58');
    $end = clone $originalEnd;
    
    // Ending on a weekend? Go back to a weekday.
    while (in_array($end->format('l'), $weekends))
    {
        $end->modify('-1 day')->setTime(23, 59);
    }
    
    // Is the start date after the end date? Might happen if start and end
    // are on the same weekend (whoops).
    if ($start > $end) throw new Exception('Start date is AFTER end date!');
    
    // Are the times outside of normal work hours? If so, adjust.
    $startAdj = clone $start;
    
    if ($start < $startAdj->setTime($workStartHour, $workStartMin))
    {
        // Start is earlier; adjust to real start time.
        $start = $startAdj;
    }
    else if ($start > $startAdj->setTime($workEndHour, $workEndMin))
    {
        // Start is after close of that day, move to tomorrow.
        $start = $startAdj->setTime($workStartHour, $workStartMin)->modify('+1 day');
    }
    
    $endAdj = clone $end;
    
    if ($end > $endAdj->setTime($workEndHour, $workEndMin))
    {
        // End is after; adjust to real end time.
        $end = $endAdj;
    }
    else if ($end < $endAdj->setTime($workStartHour, $workStartMin))
    {
        // End is before start of that day, move to day before.
        $end = $endAdj->setTime($workEndHour, $workEndMin)->modify('-1 day');
    }
    
    // Calculate the difference between our modified days.
    $diff = $start->diff($end);
    
    // Go through each day using the original values, so we can check for weekends.
    $period = new DatePeriod($start, new DateInterval('P1D'), $end);
    
    foreach ($period as $day)
    {
        // If it's a weekend day, take it out of our total days in the diff.
        if (in_array($day->format('l'), ['Saturday', 'Sunday'])) $diff->d--;
    }
    
    // Calculate! Days * Hours in a day + hours + minutes converted to hours.
    $hours = ($diff->d * $workdayHours) + $diff->h + round($diff->i / 60, 2);
    
    0 讨论(0)
提交回复
热议问题