PHP DateInterval diff wrong calculates begin and end of interval

I have a strange problem with DateIntervals.

For my client I’m writing some time basis report. Based on given date I have to detect valid date interval, move the current time pointer to it, and then make some series of interval till the end.

I have a problem with detection of start and end of given interval.
Let’s see this in the code

I have this intervals:

const TIME_INTERVAL_5_MINUTES  = '5 minutes';
const TIME_INTERVAL_30_MINUTES = '30 minutes';

const TIME_INTERVAL_1_HOUR   = '1 hour';
const TIME_INTERVAL_6_HOURS  = '6 hours';
const TIME_INTERVAL_12_HOURS = '12 hours';

const TIME_INTERVAL_1_DAY   = '1 day';
const TIME_INTERVAL_1_WEEK  = '1 week';

const INTERVAL_TO_TIME_MAPPING = [
    self::TIME_INTERVAL_5_MINUTES  => 300,
    self::TIME_INTERVAL_30_MINUTES => 1800,
    self::TIME_INTERVAL_1_HOUR     => 3600,
    self::TIME_INTERVAL_6_HOURS    => 21600,
    self::TIME_INTERVAL_12_HOURS   => 43200,
    self::TIME_INTERVAL_1_DAY      => 86400,
    self::TIME_INTERVAL_1_WEEK     => 604800,
    self::TIME_INTERVAL_1_MONTH    => -1, // month has a special treatment, won't be the subject here.
];

Now I want to calculate the time interval border based on given time:

private function calculateNearestRange(DateTimeImmutable $time, $rangeLength, $mode)
{
    $rangeMiddlePointLength = (int)($rangeLength / 2);

    // The modulo calculates should contain only the time 'inside'
    // date interval. So if I have 12h interval (which starts at
    // each day on 00:00 and 12:00) when I have time 22:51, it
    // should be equal (in seconds) 10h and 51m, but mostly it is 
    // not. So source of wrong calculations probably is this line.
    // And I don't know what I'm doing wrong
    $remainingPart          = $time->getTimestamp() % $rangeLength;

    $rangeStart       = $time->sub(new DateInterval("PT{$remainingPart}S"));
    $rangeEnd         = $time->add(new DateInterval("PT{$rangeLength}S"));
    $rangeMiddlePoint = $rangeStart->add(new DateInterval("PT{$rangeMiddlePointLength}S"));

    switch ($mode) {
        case static::MODE_NEXT:
            return $rangeEnd;

        case static::MODE_PREVIOUS:
            return $rangeStart;

        default:
        case static::MODE_NEAREST:
            return $time >= $rangeMiddlePoint
                ? $rangeEnd
                : $rangeStart;
    }
}

Everything is fine for intervals: 5min, 30min, 1hour. But when the next comes it starts to calculate bad things.

For the given data sets:

[
    'Interval length', 
    'time parameter in calculateNearestRange method'
    'expected output'
    'mode'
] 

'6hours: MODE_PREVIOUS'      => [
    TimeRangeIntervalCalculator::TIME_INTERVAL_6_HOURS,
    new DateTimeImmutable('2017-03-03 11:51'),
    new DateTimeImmutable('2017-03-03 06:00'), // But returns '2017-03-03 06:00'
    TimeRangeIntervalCalculator::MODE_PREVIOUS,
],
'6hours: MODE_NEXT'          => [
    TimeRangeIntervalCalculator::TIME_INTERVAL_6_HOURS,
    new DateTimeImmutable('2017-03-03 06:11'),
    new DateTimeImmutable('2017-03-03 12:00'), // But return '2017-03-03 07:00'
    TimeRangeIntervalCalculator::MODE_NEXT,
],
'12hours: MODE_PREVIOUS'     => [
    TimeRangeIntervalCalculator::TIME_INTERVAL_12_HOURS,
    new DateTimeImmutable('2017-03-03 11:51'),
    new DateTimeImmutable('2017-03-03 00:00'), // But returns '2017-03-03 01:00'
    TimeRangeIntervalCalculator::MODE_PREVIOUS,
],

The longer interval comes, the bigger differences comes.

When I use calculator (on timestamps), I have the save values as my PHP code have, so I think problem is somewhere with the precission of methodology ? Have any of you meet similar problem before and know some tips how to solve it?


Source: stackoverflow-php