Search code examples
phpdatetimeintervals

Calculating business hours duration, excluding public holidays


I'm trying to determine the duration of a time interval in PHP, taking into account business hours (from 9 am to 5 pm) on Monday to Friday and excluding specific public holidays. Let's consider the public holidays as 15th August 2023, 1st November 2023, 11th November 2023, and 25th December 2023.

For example, suppose I have an interval from 28th October 07:00:00 to 3rd November 10:00:00. How can I calculate the duration (in seconds) only during business hours, excluding weekends and the mentioned holidays?

I'm looking for guidance on an effective approach to achieve this duration calculation.


Solution

  • By using the spatie/opening-hours and briannesbitt/Carbon library, I successfully implemented the desired functionality. The script, utilizing a sample JSON file (sample.json) as input, calculates time differences while excluding public holidays and weekends. The focus is on determining the duration only during working hours.

    sample.json

    [
    {"entity_id":2478013,"end_phase":"2023-10-16T14:39:49.839+02:00","start_phase":"2023-01-02T10:40:01.982+01:00"}
    ]
    

    Below is the complete code:

    <?php
    require 'opening-hours-master/vendor/autoload.php';
    require 'Carbon-master/vendor/autoload.php';
    
    use Spatie\OpeningHours\OpeningHours;
    use Carbon\CarbonInterval;
    
    $openingHours = OpeningHours::create([
        'monday'     => ['09:00-17:00'],
        'tuesday'    => ['09:00-17:00'],
        'wednesday'  => ['09:00-17:00'],
        'thursday'   => ['09:00-17:00'],
        'friday'     => ['09:00-17:00'],
        'saturday'   => [],
        'sunday'     => [],
        'exceptions' => [
            '2023-04-10' => [],
            '2023-05-08' => [],
            '2023-05-18' => [],
            '2023-05-29' => [],
            '2023-08-15' => [],
            '2023-11-01' => [],
            '2023-11-11' => [],
            '07-14'      => [],                // Recurring on each 14 of July
            '05-01'      => [],                // Recurring on each 1st of May
            '01-01'      => [],                // Recurring on each 1st of January
            '12-25'      => []                 // Recurring on each 25th of December
        ],
        'timezone' => [
            'input' => 'Europe/Paris',
            'output' => 'Europe/Paris',
        ]
    ]);
    
    $result_csv = array(array('entity_id', 'start_phase', 'end_phase', 'difference_in_seconds', 'difference_in_human_readable', 'isBreached'));
    
    $data = file_get_contents('sample.json');
    $data = json_decode($data, true);
    $isBreached = '';
    foreach($data as $row){
        $diffInSeconds = $openingHours->diffInOpenSeconds(new DateTime($row['start_phase']), new DateTime($row['end_phase']));  
        $human_readable = CarbonInterval::seconds($diffInSeconds)->cascade()->forHumans();
    
        if($diffInSeconds > 144000){
            $isBreached = 'true';
        }else{
            $isBreached = 'false';
        }
    
        $result_csv[] = array($row['entity_id'], $row['start_phase'], $row['end_phase'], $diffInSeconds, $human_readable, $isBreached);
    }
    
    $fp = fopen('file.csv', 'wb');
    foreach ($result_csv as $fields) {
        fputcsv($fp, $fields);
    }
    fclose($fp);