Help - Calculate the time span in time range between checkin & checkout

Published 1 month ago by GTHell

I have a time range of 20:00-8:00 and I want to calculate the time span between checkout and checkin but only count the time that is inside the time range. For example, if I checkin in 2019-09-15 23:00:00 and checkout at 2019-09-16 10:00:00 then the time span is only 9 hours.

Has anyone done something like this before? Seem easy to solve from reading but inevitable without using formula or face a if-else hell.

Best Answer (As Selected By GTHell)
lostdreamer_nl

Here's an easy way:

$start = new DateTime('2019-09-15 23:00:00');
$end = new DateTime('2019-09-16 10:00:00');

// create a period based on 1 hour intervals
$interval = new DateInterval('PT1H');
$period = new DatePeriod($start, $interval, $end, DatePeriod::EXCLUDE_START_DATE );
$hours = [];
foreach ($period as $date) {
    // foreach hour, check if it's between 8 & 20
    if($date->format('H') >= 20 || $date->format('H')  < 8) {
        $hours[] = $date->format('Y-m-d H:i');
    }
}
print_r($hours);
Tray2
Tray2
1 month ago (109,870 XP)

Try.

$checkin = Carbon::parse(2019-09-15 23:00:00));
$checkout = Carbon::parse(2019-09-16 10:00:00));
$diff = $checkin->diffInHours($checkout); //Or the other way around.
Cronix
Cronix
4 weeks ago (783,370 XP)

As Tray2 said (except $checkout/$checkin would be reversed). Here are the docs: https://carbon.nesbot.com/docs/#api-difference

GTHell

Nah, that’s too simple. What if I check in at 21:00 and checkout at 3:00? if-else? There’re multiple cases. If-else is inefficient.

Snapey
Snapey
4 weeks ago (1,036,605 XP)

its not a problem if your checkin/checkout includes the date

GTHell

I think this problem I'm proposing can only be done by formula. I thought someone has done it before without using too many if-else. I've done it but it's flaw.

@Snapey If the the checkout is smaller than checkin then it's already overnight. Not hard to assume.

Here's the visual:

0      chin         0             0                0         cout   0
||------*--  --|==||==|------||-------|==||==|-----*-----||
                  23         10                 23        10

It's sound simple, I know because I thought the same when first started out but then not realize it's not. Anyone I know, implement with a lot, if not hundred, if else. lol

The above answer is 11 * 2 = 22h

lostdreamer_nl

Here's an easy way:

$start = new DateTime('2019-09-15 23:00:00');
$end = new DateTime('2019-09-16 10:00:00');

// create a period based on 1 hour intervals
$interval = new DateInterval('PT1H');
$period = new DatePeriod($start, $interval, $end, DatePeriod::EXCLUDE_START_DATE );
$hours = [];
foreach ($period as $date) {
    // foreach hour, check if it's between 8 & 20
    if($date->format('H') >= 20 || $date->format('H')  < 8) {
        $hours[] = $date->format('Y-m-d H:i');
    }
}
print_r($hours);
GTHell

@lostdreamer_nl This is gold. I'm interesting in knowing how did you come up with this solution? Also If I were to google, what keywords are essential? I googled this problem but seem to find no answer that related to what I wanted to do even though it probably has done by many. One more question if what if I want to do in minute instead of hours?

lostdreamer_nl

A few years ago I had to do a lot of checks with overlapping date period objects (was working at a parking company back) so I've used this logic quite a few times already.

This example is the simplest form of what you asked, but if you run into comparing dates with periods, or periods with other periods a lot at your job you might want to write your own classes for the logic, it makes life a lot easier.

Back then, I eventually created my own classes that held arrays of data for the interval's so I could use array functions to get unique's, merge, and subtract periods of time.

To change it into minutes, change the DatePeriod's interval ( http://php.net/manual/en/class.dateinterval.php )

$interval = new DateInterval('PT1H');  // interval of 1 hour
$interval = new DateInterval('PT1M')  // interval of 1 minute;
$interval = new DateInterval('PT45S')  // interval of 45 seconds;

By the way, I was bored a bit, so here's something to start you off:


Class Roster {
    private $openPeriod = [];
    private $interval;

    public function __construct(array $openPeriods, $interval) {
        $this->interval = new DateInterval($interval);
        foreach($openPeriods as $period) {
            $period = new DatePeriod(new DateTime($period[0]), $this->interval, new DateTime($period[1]), DatePeriod::EXCLUDE_START_DATE );
            foreach($period as $interval) {
                $this->openPeriod[$interval->format('Y-m-d H:i:s')] = 1;
            }
        }
    }

    public function getOverlap($start, $end) {
        $periodToCheck = new DatePeriod(new DateTime($start), $this->interval, new DateTime($end));
        $intervalsWithinPeriod = [];
        foreach($periodToCheck as $interval) {
            $moment = $interval->format('Y-m-d H:i:s');
            $intervalsWithinPeriod[$moment] = isset($this->openPeriod[$moment]);
        }
        return array_filter($intervalsWithinPeriod);
    }
}

$opened = [
        ['2019-09-15 20:00:00', '2019-09-16 08:00:00'],
        ['2019-09-16 20:00:00', '2019-09-17 08:00:00'],
        ['2019-09-17 20:00:00', '2019-09-18 08:00:00'],
        ['2019-09-18 20:00:00', '2019-09-19 08:00:00']
];

// use PT1H  /  PT1M  /  PT1S  here for hours -> seconds
$roster = new Roster($opened, 'PT1H');
$intervals = $roster->getOverlap('2019-09-15 23:00:00', '2019-09-18 23:00:00');
print_r($intervals);
GTHell

@lostdreamer_nl Thank man, the first algorithm work great.

Please sign in or create an account to participate in this conversation.