calc the working hours between 2 dates in PHP

前端 未结 3 1292
南方客
南方客 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: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);
    

提交回复
热议问题