Home > Net >  Counting working hours (different for weekdays and Saturdays)
Counting working hours (different for weekdays and Saturdays)

Time:04-24

I tried @ebelendez's code for Calculating working hours between two dates, however I'm confused on how to set the value of Saturdays by 3 hours (08:00-11:00). For example, the working hours per day during weekdays is 8 hours (excluding 1 hour break), let's say I want to get the total working hours from Thursday to Saturday, the expected result would be 19 hours.

Here is what I've done. Can someone help me with this?

$from = '2022-04-21 07:00:00';
$to = '2022-04-23 16:00:00';

echo abs(get_working_hours($from, $to));

function get_working_hours($from,$to){
    //config
    $ini_time = [7,0]; //hr, min
    $end_time = [16,0]; //hr, min
    //date objects
    $ini = date_create($from);
    $ini_wk = date_time_set(date_create($from),$ini_time[0],$ini_time[1]);
    $end = date_create($to);
    $end_wk = date_time_set(date_create($to),$end_time[0],$end_time[1]);
    //days
    $workdays_arr = get_workdays($ini,$end);
    $workdays_count = count($workdays_arr);
    $workday_seconds = (($end_time[0] * 60   $end_time[1]) - ($ini_time[0] * 60   $ini_time[1])) * 60 - 3600;
    //get time difference
    $ini_seconds = 0;
    $end_seconds = 0;
    if(in_array($ini->format('Y-m-d'),$workdays_arr)) $ini_seconds = $ini->format('U') - $ini_wk->format('U');
    if(in_array($end->format('Y-m-d'),$workdays_arr)) $end_seconds = $end_wk->format('U') - $end->format('U');
    $seconds_dif = $ini_seconds > 0 ? $ini_seconds : 0;
    if($end_seconds > 0) $seconds_dif  = $end_seconds;
    //final calculations
    $working_seconds = ($workdays_count * $workday_seconds) - $seconds_dif;
    return $working_seconds / 3600; //return hrs
}

function get_workdays($ini,$end){
    //config
    $skipdays = [0]; //sunday:0
    $skipdates = []; 
    //vars
    $current = clone $ini;
    $current_disp = $current->format('Y-m-d');
    $end_disp = $end->format('Y-m-d');
    $days_arr = [];
    //days range
    while($current_disp <= $end_disp){
        if(!in_array($current->format('w'),$skipdays) && !in_array($current_disp,$skipdates)){
            $days_arr[] = $current_disp;
        }
        $current->add(new DateInterval('P1D')); //adds one day
        $current_disp = $current->format('Y-m-d');
    }
    return $days_arr;
}

CodePudding user response:

Your code and linked answers seem unnecessarily complicated. All we really need is to:

  1. Configure how many hours should be counted for for each day;
  2. Create an iterable DatePeriod (with DateTime objects for each date in the period);
  3. Iterate dates, look up how many hours should be counted for each day, sum it up.
class CountWorkingHours
{
    // Define hours counted for each day:
    public array $hours = [ 
        'Mon' => 8, 
        'Tue' => 8, 
        'Wed' => 8, 
        'Thu' => 8, 
        'Fri' => 8, 
        'Sat' => 3, 
        'Sun' => 0
    ];

    // Method for counting the hours:
    public function get_hours_for_period(string $from, string $to): int
    {
        // Create DatePeriod with requested Start/End dates:
        $period = new DatePeriod(
            new DateTime($from), 
            new DateInterval('P1D'), 
            new DateTime($to)
        );
        
        $hours = [];

        // Loop over DateTime objects in the DatePeriod:
        foreach($period as $date) {
            // Get name of day and add matching hours:
            $day = $date->format('D');
            $hours[] = $this->hours[$day];
        }
        // Return sum of hours:
        return array_sum($hours);
    }
}

Usage (returns an integer with working hours in a given period):

$cwh = new CountWorkingHours();
$hours = $cwh->get_hours_for_period('2022-04-21 07:00:00', '2022-04-30 16:00:00');
// = 62

If you need to account for public holidays etc. exceptions to the standard weekly hour counts, you can add a check inside the period loop for "skip dates". For example, have a $skip_dates property with an array of non-work-days, then check for !in_array($date->format('Y-m-d'), $this->skip_dates) before incrementing the work hours for a given day.

P.S. This code assumes that you are calculating whole working days. If your start or end hours were defined in the middle of a working day, that wouldn't be accounted for. (If you wanted to factor that in, you'd have to configure daily work times and the code would have to account for that in evaluating start and end dates. Seemed an unnecessary exercise for current purposes.)

  • Related