Calculating business duration between two dates with pauses

62 views Asked by At

I must determine the duration between two dates within business hours (Monday to Friday, 09:00 am to 5:00 pm), excluding public holidays. Additionally, I need to account for pauses within the business interval. I've successfully handled the initial part of my program using two libraries: https://github.com/briannesbitt/Carbon and https://github.com/spatie/opening-hours now I need to take into account the pause time.

Below is my code:

<?php
require 'opening-hours-master/vendor/autoload.php';
require 'carbon-master/vendor/autoload.php';

use Spatie\OpeningHours\OpeningHours;
use Carbon\CarbonInterval;

//Initiate the working hours and exlude public hollidays
$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',
    ]
]);

//Date to calculate interval
$data = '[
    {"entity_id":1,"start_phase":"2023-12-04T10:00:00.973+01:00","end_phase":"2023-12-05T12:01:01.973+01:00"},
    {"entity_id":2,"start_phase":"2023-11-30T08:00:00.973+01:00","end_phase":"2023-12-07T16:31:01.973+01:00"},
    {"entity_id":2,"start_phase":"2023-12-12T14:35:51.973+01:00","end_phase":"2023-12-12T16:31:01.973+01:00"}
]';

//Pause per entity_id
$data_pause = '[
    {"entity_id":1,"start_pause":"2023-12-04T09:00:00.973+01:00","end_pause":"2023-12-04T12:00:00.973+01:00"},
    {"entity_id":1,"start_pause":"2023-12-05T12:00:00.973+01:00","end_pause":"2023-12-05T14:00:00.973+01:00"}
]';

$report_data = json_decode($data, true);
$data_pause = json_decode($data_pause, true);
$result = array();

//Calculate the durations in business time between two dates
foreach($report_data as $row){
    foreach($data_pause as $pause){
        if($row['entity_id'] == $pause['entity_id']){
            //TO DO
            //Remove pause duration inside the specified start and end phases, ensuring it falls within the designated business time interval.
            var_dump($pause);
        }
    }
    $diffInSeconds = round($openingHours->diffInOpenSeconds(new DateTime($row['start_phase']), new DateTime($row['end_phase'])));   
    $human_readable = CarbonInterval::seconds($diffInSeconds)->cascade()->forHumans();
    $data_diff[] = array(
        'entity_id' => $row['entity_id'], 
        'start_phase' => $row['start_phase'],
        'end_phase' => $row['end_phase'],
        'difference_in_seconds' => $diffInSeconds,
        'human_readable' => $human_readable
    );
}
var_dump($data_diff);

Output :

array(3) {
  [0]=>
  array(5) {
    ["entity_id"]=>
    int(1)
    ["start_phase"]=>
    string(29) "2023-12-04T10:00:00.973+01:00"
    ["end_phase"]=>
    string(29) "2023-12-05T12:01:01.973+01:00"
    ["difference_in_seconds"]=>
    float(36061)
    ["human_readable"]=>
    string(26) "10 hours 1 minute 1 second"
  }
  [1]=>
  array(5) {
    ["entity_id"]=>
    int(2)
    ["start_phase"]=>
    string(29) "2023-11-30T08:00:00.973+01:00"
    ["end_phase"]=>
    string(29) "2023-12-07T16:31:01.973+01:00"
    ["difference_in_seconds"]=>
    float(171062)
    ["human_readable"]=>
    string(35) "1 day 23 hours 31 minutes 2 seconds"
  }
  [2]=>
  array(5) {
    ["entity_id"]=>
    int(2)
    ["start_phase"]=>
    string(29) "2023-12-12T14:35:51.973+01:00"
    ["end_phase"]=>
    string(29) "2023-12-12T16:31:01.973+01:00"
    ["difference_in_seconds"]=>
    float(6910)
    ["human_readable"]=>
    string(28) "1 hour 55 minutes 10 seconds"
  }
}

How can I exclude the pause duration within the phase interval and during business hours? I can use $openingHours->isOpenAt($date); to determine if a date falls within working hours, but how can I proceed with the remaining logic?

For exemple for the entity_id 1, I have the following interval :

  • start phase : 2023-12-04 10:00:00
  • end phase : 2023-12-05 12:01:01
  • Duration => 10 hours 1 minute 1 second

Pauses are :

1st pause :

  • start pause : 2023-12-04 09:00:00
  • end pause : 2023-12-04 12:00:00

2nd pause :

  • start pause : 2023-12-05 12:00:00
  • end pause : 2023-12-05 14:00:00

In this case, the initial pause requires the exclusion of time from 10:00 to 12:00, totaling 2 hours. As for the second pause, we should deduct the duration from 12:00 to 12:01:01, equivalent to 1 minute and 1 second. Consequently, the cumulative result should be 8 hours.

Or to simplify : I must calculate the duration between the start phase and end phase (indicated by the green line). The durations must fall within the designated working hours (showed by the red line), and the duration of the pause (illustrated by the blue line) should be excluded from the overall calculation.

Edit :

Users can take multiple pauses throughout the day, and the initiation of a pause is done by the user. Pauses can occur at any time and can be taken multiple times during the day. Pause time can fall at any time, including weekends and public holidays.

enter image description here

1

There are 1 answers

4
KyleK On

Using \BusinessTime\Schedule from cmixin/business-time, you can subtract the diff from your pauses to the diff from your opening hours (not milliseconds won't be taken into account, this is second-precise):

$tz = 'Europe/Paris';
$schedule = \BusinessTime\Schedule::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' => $tz,
        'output' => $tz,
    ]
]);

$pauses = json_decode('[
    {"entity_id":1,"start_pause":"2023-12-04T09:00:00.973+01:00","end_pause":"2023-12-04T12:00:00.973+01:00"},
    {"entity_id":1,"start_pause":"2023-12-05T12:00:00.973+01:00","end_pause":"2023-12-05T14:00:00.973+01:00"}
]', true);

$exceptions = [];

foreach ($pauses as $pause) {
    $start = \Carbon\CarbonImmutable::parse($pause['start_pause'])->tz($tz);
    $end = \Carbon\CarbonImmutable::parse($pause['end_pause'])->tz($tz);
    $days = $start->startOfDay()->daysUntil($end->startOfDay())->toArray();
    $lastIndex = count($days) - 1;

    foreach ($start->startOfDay()->daysUntil($end->startOfDay()) as $index => $date) {
        $isFirstDay = ($index === 0);
        $isLastDay = ($index === $lastIndex);
        $day = $date->format('Y-m-d');
        $exceptions[$day] ??= [];
        $exceptions[$day][] = ($isFirstDay ? $start->format('H:i') : '00:00') . '-' .
            ($isLastDay ? $end->format('H:i') : '24:00');
    }
}

$pauses = \BusinessTime\Schedule::create([
    'exceptions' => $exceptions,
]);

$start = \Carbon\CarbonImmutable::parse('2023-12-04 10:00:00', $tz);
$end = \Carbon\CarbonImmutable::parse('2023-12-05 12:01:01', $tz);

$seconds = $schedule->diffInBusinessSeconds($start, $end) - $pauses->diffInBusinessSeconds($start, $end);

$diff = \Carbon\CarbonInterval::seconds($seconds)->cascade();

echo $diff->forHumans();