0

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;
}
Markus AO
  • 4,771
  • 2
  • 18
  • 29
Mandy
  • 15
  • 3
  • Can you please edit your question in such a way that it stands on its own? Otherwise, just post is as a comment in the linked question/answer instead. – Markus AO Apr 23 '22 at 13:13
  • @MarkusAO someone also commented from the linked question the same situation as mine, but still didn't find an answer. – Mandy Apr 23 '22 at 13:19
  • In that case, please ask an original complete question here, describing what you need and what you've tried. Add the link as a reference to what you've already looked at. – Markus AO Apr 23 '22 at 13:43
  • Thanks for the update. I've edited the subject a bit, too. Your code and the answers linked are somewhat headache-inducing. So much code and complexity for a simple task. :) My answer below should get the job done, please test and report if the results don't match what you expect. – Markus AO Apr 23 '22 at 17:11

1 Answers1

2

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);
    }
}
  • Source @ BitBucket
  • 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.)

Markus AO
  • 4,771
  • 2
  • 18
  • 29
  • By the way, how can I define the hours in weekdays dynamically? – Mandy Apr 24 '22 at 11:46
  • The `$hours` property is public, so you can just redefine it directly before you call the method, as: `$cwh->hours = ['Mon' => 5, ...];`. or even, `$cwh->hours['Tue'] = 7;` if you only want to redefine one day's hours. – Markus AO Apr 24 '22 at 12:15