fix: carry numeric round/floor/ceil precision >= 60s into minute/hour/day
A numeric $precision passed to round()/floor()/ceil() is always resolved
to the "second" unit. Once the precision reaches or exceeds 60 (e.g.
round(3600) meaning "nearest hour"), the rounding stayed confined to the
second field (range 0-59) and never carried into minute/hour/day, unlike
the equivalent string form (round('hour')).
Re-target the unit to minute/hour/day once the numeric precision reaches
that unit's own span in seconds, rescaling the precision accordingly,
mirroring the existing week -> day re-targeting already in roundUnit().
Sub-minute numeric precisions are unaffected.
This commit is contained in:
1 parent
06596ca5df
commit
5192615f4e
2 files changed
+50
No files matched your search
@@ -70,6 +70,24 @@ trait Rounding
|
|||||||
|
|
||||||
$precision *= $factor;
|
$precision *= $factor;
|
||||||
|
|
||||||
|
// A numeric precision is in seconds, so once it spans the next coarser
|
||||||
|
// unit it must be re-targeted there, like "week" -> "day" above.
|
||||||
|
if ($normalizedUnit === 'second' && (\is_int($precision) || \is_float($precision))) {
|
||||||
|
$secondsPerHour = static::MINUTES_PER_HOUR * static::SECONDS_PER_MINUTE;
|
||||||
|
$secondsPerDay = static::HOURS_PER_DAY * $secondsPerHour;
|
||||||
|
|
||||||
|
if ($precision >= $secondsPerDay) {
|
||||||
|
$normalizedUnit = 'day';
|
||||||
|
$precision /= $secondsPerDay;
|
||||||
|
} elseif ($precision >= $secondsPerHour) {
|
||||||
|
$normalizedUnit = 'hour';
|
||||||
|
$precision /= $secondsPerHour;
|
||||||
|
} elseif ($precision >= static::SECONDS_PER_MINUTE) {
|
||||||
|
$normalizedUnit = 'minute';
|
||||||
|
$precision /= static::SECONDS_PER_MINUTE;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (!isset($ranges[$normalizedUnit])) {
|
if (!isset($ranges[$normalizedUnit])) {
|
||||||
throw new UnknownUnitException($unit);
|
throw new UnknownUnitException($unit);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -205,4 +205,36 @@ class RoundTest extends AbstractTestCase
|
|||||||
|
|
||||||
Carbon::now()->roundUnit('foobar');
|
Carbon::now()->roundUnit('foobar');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function testRoundWithNumericPrecisionCarry()
|
||||||
|
{
|
||||||
|
// A numeric precision >= 60s must carry, like the equivalent string unit.
|
||||||
|
$dt = Carbon::create(2026, 1, 1, 14, 21, 37);
|
||||||
|
|
||||||
|
$this->assertCarbon($dt->copy()->round(3600), 2026, 1, 1, 14, 0, 0, 0);
|
||||||
|
$this->assertTrue($dt->copy()->round(3600)->eq($dt->copy()->round('hour')));
|
||||||
|
|
||||||
|
$this->assertCarbon($dt->copy()->round(120), 2026, 1, 1, 14, 22, 0, 0);
|
||||||
|
$this->assertTrue($dt->copy()->round(120)->eq($dt->copy()->round('2 minutes')));
|
||||||
|
|
||||||
|
$this->assertCarbon($dt->copy()->round(86400), 2026, 1, 2, 0, 0, 0, 0);
|
||||||
|
$this->assertTrue($dt->copy()->round(86400)->eq($dt->copy()->round('day')));
|
||||||
|
|
||||||
|
// floor() with numeric precision must also carry, not just clamp within the minute.
|
||||||
|
$almostNextHour = Carbon::create(2026, 1, 1, 14, 59, 59);
|
||||||
|
$this->assertCarbon($almostNextHour->copy()->floor(3600), 2026, 1, 1, 14, 0, 0, 0);
|
||||||
|
$this->assertTrue($almostNextHour->copy()->floor(3600)->eq($almostNextHour->copy()->floor('hour')));
|
||||||
|
|
||||||
|
// ceil() with numeric precision must carry across the hour boundary.
|
||||||
|
$halfPastHour = Carbon::create(2026, 1, 1, 14, 30, 0);
|
||||||
|
$this->assertCarbon($halfPastHour->copy()->ceil(3600), 2026, 1, 1, 15, 0, 0, 0);
|
||||||
|
$this->assertTrue($halfPastHour->copy()->ceil(3600)->eq($halfPastHour->copy()->ceil('hour')));
|
||||||
|
|
||||||
|
// Carry across a month boundary via a day-level numeric precision.
|
||||||
|
$monthEnd = Carbon::create(2026, 1, 31, 20, 0, 0);
|
||||||
|
$this->assertCarbon($monthEnd->copy()->round(86400), 2026, 2, 1, 0, 0, 0, 0);
|
||||||
|
|
||||||
|
// Sub-minute numeric precision must keep rounding within the second field, unchanged.
|
||||||
|
$this->assertCarbon($dt->copy()->round(30), 2026, 1, 1, 14, 21, 30, 0);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
Reference in new issue
Block a user