0

I need to figure out how much of an employee's shift is during the "night shift".

So, a night shift is any time from: 18:00 PM until 04:00 AM

My punches have a start and stop DateTime object. So if an employee has the following shift: 01:00 AM to 07:00 AM

They have 3 hours of that during the night shift. I calculate it in minutes because obviously that is more precise than hours.

However, my problem becomes if a shift carries over to midnight.

So they start at 22:00 PM on one date and finish next day at 07:00 AM.

I just cannot, for the life of me, figure out how to handle this.. Any help in this regard is much appriciated! Thanks all

EDIT Let me simplify my question as much as i can...

I have 2 DateTime objects representing a shift start and stop... Our company pays extra for any work done during the "night" which is between 18:00 PM and 04:00 AM... I need to calculate, how much (if any) of my shift is in that interval, so i can adjust the pay.

Here is my current code. I think it works for most cases i can think of, but i feel it is very complicated and convoluted.. maybe this is the right way, but i feel i made it more complicated than it can be.

private function calculateNightShift(\DateTime $shiftStart, \DateTime $shiftEnd)
{
    // We know night-shift is always from 18:00 to 04:00
    // So we just start with the interval on our shift-start
    $nightStart = new \DateTime($shiftStart->format('Y-m-d') . '18:00:00');
    $nightEnd = new \DateTime($shiftStart->format('Y-m-d') . '04:00:00');
    $nightEnd->modify('+1 days'); // always next day comparing to the start, since we cross midnight

    // The above contains our first possible night-shift interval that goes from shift_start date 18:00 to 04:00 next day
    // We check for overlap here, and we also check for an overlap by moving the night shift 1 day in the past.
    // This adjusts for any shift that crosses midnight


    // First figure out if this shift is a night shift at all - if it is not, we return 0
    // ($shiftStart >= $nightStart && $shiftStart <= $nightEnd) || ($shiftStart >= $nightStart->modify('-1 days') && $shiftStart <= $nightEnd->modify('-1 days'))
    if ( (max($shiftStart, $nightStart) <= min($shiftEnd, $nightEnd)) || (max($shiftStart, $nightStart->modify('-1 days')) <= min($shiftEnd, $nightEnd->modify('-1 days'))) ) {
        // First take all possible values into array (night start/end and shift start/end)
        $nightShiftArray = [
            $shiftStart,
            $shiftEnd,
            $nightStart,
            $nightEnd
        ];

        // Sort them
        sort($nightShiftArray);


        // Now the overlap is the value at index 1 and 2 (middle 2 values)
        $overlapStart = $nightShiftArray[1];
        $overlapEnd = $nightShiftArray[2];
        $overlapDiff = $overlapStart->diff($overlapEnd);

        // overlapDiff now contains the night shift total - store as minutes total
        return ($overlapDiff->h * 60) + $overlapDiff->i;
    } else {
        return 0;
    }
}
J.B.J.
  • 440
  • 1
  • 8
  • 15
  • Why would going over midnight make a difference? If you have the date as well as the time for the start and stop times, you can still calculate the difference in minutes between those two times, and then work out the overlap of how much of it occurs during the night shift. It's not clear _exactly_ where you're getting stuck code-wise. Can you show your best attempt so far, then we can help you to correct it? – ADyson Aug 01 '23 at 10:44
  • @ADyson Thank you very much. The problem is that i am essentially dealing with 4 different dates.. but i kinda think that i am totally overthinking it... essentially, i have 2 dates - a start and stop, and i need to figure how many minutes of that occurs between 18:00 and 04:00 – J.B.J. Aug 01 '23 at 10:56
  • 2
    Try drawing on a piece of paper lines that represent the different durations here, i.e. a line from 18:00 to 4:00, and then various lines besides that representing shifts. You will see that you're only interested in a number of different _overlap_ scenarios that you want to test for; usually with some test like `start > end` or `end > start`. That should help you narrow down what kind of test you're actually trying to write. For more concrete help, presenting some concrete values in something code-like would help… – deceze Aug 01 '23 at 11:11
  • This ought to help you get going (for example, you can google "php check amount of overlap between two date ranges" for many more ideas): https://codereview.stackexchange.com/questions/181119/check-if-two-date-ranges-overlap-by-x-minutes . So should https://stackoverflow.com/questions/325933/determine-whether-two-date-ranges-overlap – ADyson Aug 01 '23 at 11:34
  • @J.B.J. re last comment on your now-deleted duplicate. This question can be re-opened for answers _if_ you can show there is more to your issue than simply not knowing how to compare two dates. As we said earlier, the lack of any code attempt makes it hard to tell precisely where you're getting stuck. But even if it's re-opened, I'd say it's likely to be just a duplicate of one of the other many related questions instead, such as I've linked to above. If you think it isn't, [edit] the post with your latest effort and explain exactly where you're having a problem, so we can compare. – ADyson Aug 01 '23 at 13:13
  • @ADyson thank you very much for all your help along the way. I have updated my answer to show my current code.. i THINK i might have got it working - not sure if it is the best way, it seems kinda convoluted - but maybe date intervals that cross midnight are just like that :) – J.B.J. Aug 01 '23 at 15:21
  • Thanks. If you think you might have got it working, come up with a load of test cases, as many as you can think of, including the awkward ones, and fire them at it, and check they all produce the result you expect. If they do, you can write your final code up as an Answer below. If they don't, post the test results and we'll see if we can work out where it's going wrong. – ADyson Aug 01 '23 at 16:11
  • Convert the date with the time to unixTime, deduct and you have the difference in seconds, from there you can convert to minutes and hours. The strtotime() function converts any given date string into a Unix timestamp. – Grumpy Aug 01 '23 at 16:39
  • 1
    @Grumpy well you could, but why do all that when DateTime::diff() exists? – ADyson Aug 01 '23 at 17:16
  • I posted my solution, which seems to work. Thanks a lot @ADyson for pointing me in the right direction! – J.B.J. Aug 02 '23 at 10:10

1 Answers1

1

I got this code working, and it satisfies my use-case.

The only error I think it would produce is if a shift was ever 24 hours and could cross past midnight twice, but that doesn't ever seem to be a reality.

Thanks for pointing me in the right direction!

private function calculateNightShift(\DateTime $shiftStart, \DateTime $shiftEnd)
{
    // We know night-shift is always from 18:00 to 04:00
    // So we just start with the interval on our shift-start
    $nightStart = new \DateTime($shiftStart->format('Y-m-d') . '18:00:00');
    $nightEnd = new \DateTime($shiftStart->format('Y-m-d') . '04:00:00');
    $nightEnd->modify('+1 days'); // always next day comparing to the start, since we cross midnight

    // The above contains our first possible night-shift interval that goes from shift_start date 18:00 to 04:00 next day
    // We check for overlap here, and we also check for an overlap by moving the night shift 1 day in the past.
    // This adjusts for any shift that crosses midnight


    // First figure out if this shift is a night shift at all - if it is not, we return 0
    // ($shiftStart >= $nightStart && $shiftStart <= $nightEnd) || ($shiftStart >= $nightStart->modify('-1 days') && $shiftStart <= $nightEnd->modify('-1 days'))
    if ( (max($shiftStart, $nightStart) <= min($shiftEnd, $nightEnd)) || (max($shiftStart, $nightStart->modify('-1 days')) <= min($shiftEnd, $nightEnd->modify('-1 days'))) ) {
        // First take all possible values into array (night start/end and shift start/end)
        $nightShiftArray = [
            $shiftStart,
            $shiftEnd,
            $nightStart,
            $nightEnd
        ];

        // Sort them
        sort($nightShiftArray);


        // Now the overlap is the value at index 1 and 2 (middle 2 values)
        $overlapStart = $nightShiftArray[1];
        $overlapEnd = $nightShiftArray[2];
        $overlapDiff = $overlapStart->diff($overlapEnd);

        // overlapDiff now contains the night shift total - store as minutes total
        return ($overlapDiff->h * 60) + $overlapDiff->i;
    } else {
        return 0;
    }
}
ADyson
  • 57,178
  • 14
  • 51
  • 63
J.B.J.
  • 440
  • 1
  • 8
  • 15