Skip to content

Commit

Permalink
Merge pull request #2655 from kylekatarnls/feature/float-cascade-factors
Browse files Browse the repository at this point in the history
Allow float numbers in cascade factors
  • Loading branch information
kylekatarnls authored Aug 28, 2022
2 parents 1dd341d + f1a490a commit 7507aec
Show file tree
Hide file tree
Showing 2 changed files with 107 additions and 18 deletions.
59 changes: 41 additions & 18 deletions src/Carbon/CarbonInterval.php
Original file line number Diff line number Diff line change
Expand Up @@ -410,7 +410,7 @@ public function __construct($years = 1, $months = null, $weeks = null, $days = n
* @param string $source
* @param string $target
*
* @return int|null
* @return int|float|null
*/
public static function getFactor($source, $target)
{
Expand Down Expand Up @@ -438,7 +438,7 @@ public static function getFactor($source, $target)
* @param string $source
* @param string $target
*
* @return int|null
* @return int|float|null
*/
public static function getFactorWithDefault($source, $target)
{
Expand All @@ -465,7 +465,7 @@ public static function getFactorWithDefault($source, $target)
/**
* Returns current config for days per week.
*
* @return int
* @return int|float
*/
public static function getDaysPerWeek()
{
Expand All @@ -475,7 +475,7 @@ public static function getDaysPerWeek()
/**
* Returns current config for hours per day.
*
* @return int
* @return int|float
*/
public static function getHoursPerDay()
{
Expand All @@ -485,7 +485,7 @@ public static function getHoursPerDay()
/**
* Returns current config for minutes per hour.
*
* @return int
* @return int|float
*/
public static function getMinutesPerHour()
{
Expand All @@ -495,7 +495,7 @@ public static function getMinutesPerHour()
/**
* Returns current config for seconds per minute.
*
* @return int
* @return int|float
*/
public static function getSecondsPerMinute()
{
Expand All @@ -505,7 +505,7 @@ public static function getSecondsPerMinute()
/**
* Returns current config for microseconds per second.
*
* @return int
* @return int|float
*/
public static function getMillisecondsPerSecond()
{
Expand All @@ -515,7 +515,7 @@ public static function getMillisecondsPerSecond()
/**
* Returns current config for microseconds per second.
*
* @return int
* @return int|float
*/
public static function getMicrosecondsPerMillisecond()
{
Expand Down Expand Up @@ -1081,11 +1081,11 @@ public function get($name)
return (int) round($this->f * Carbon::MICROSECONDS_PER_SECOND) % Carbon::MICROSECONDS_PER_MILLISECOND;

case 'weeks':
return (int) ($this->d / static::getDaysPerWeek());
return (int) ($this->d / (int) static::getDaysPerWeek());

case 'daysExcludeWeeks':
case 'dayzExcludeWeeks':
return $this->d % static::getDaysPerWeek();
return $this->d % (int) static::getDaysPerWeek();

case 'locale':
return $this->getTranslatorLocale();
Expand Down Expand Up @@ -1136,7 +1136,7 @@ public function set($name, $value = null)
break;

case 'week':
$this->d = $value * static::getDaysPerWeek();
$this->d = $value * (int) static::getDaysPerWeek();

break;

Expand All @@ -1147,7 +1147,7 @@ public function set($name, $value = null)

case 'daysexcludeweek':
case 'dayzexcludeweek':
$this->d = $this->weeks * static::getDaysPerWeek() + $value;
$this->d = $this->weeks * (int) static::getDaysPerWeek() + $value;

break;

Expand Down Expand Up @@ -2243,9 +2243,11 @@ private function doCascade(bool $deep)
$originalData = $this->toArray();
$originalData['milliseconds'] = (int) ($originalData['microseconds'] / static::getMicrosecondsPerMillisecond());
$originalData['microseconds'] = $originalData['microseconds'] % static::getMicrosecondsPerMillisecond();
$originalData['daysExcludeWeeks'] = $originalData['days'];
$originalData['weeks'] = (int) ($this->d / static::getDaysPerWeek());
$originalData['daysExcludeWeeks'] = fmod($this->d, static::getDaysPerWeek());
unset($originalData['days']);
$newData = $originalData;
$previous = [];

foreach (self::getFlipCascadeFactors() as $source => [$target, $factor]) {
foreach (['source', 'target'] as $key) {
Expand All @@ -2255,9 +2257,29 @@ private function doCascade(bool $deep)
}

$value = $newData[$source];
$modulo = ($factor + ($value % $factor)) % $factor;
$modulo = fmod($factor + fmod($value, $factor), $factor);
$newData[$source] = $modulo;
$newData[$target] += ($value - $modulo) / $factor;

$decimalPart = fmod($newData[$source], 1);

if ($decimalPart !== 0.0) {
$unit = $source;

foreach ($previous as [$subUnit, $subFactor]) {
$newData[$unit] -= $decimalPart;
$newData[$subUnit] += $decimalPart * $subFactor;
$decimalPart = fmod($newData[$subUnit], 1);

if ($decimalPart === 0.0) {
break;
}

$unit = $subUnit;
}
}

array_unshift($previous, [$source, $factor]);
}

$positive = null;
Expand Down Expand Up @@ -2338,13 +2360,13 @@ public function total($unit)
$cumulativeFactor = 0;
$unitFound = false;
$factors = self::getFlipCascadeFactors();
$daysPerWeek = static::getDaysPerWeek();
$daysPerWeek = (int) static::getDaysPerWeek();

$values = [
'years' => $this->years,
'months' => $this->months,
'weeks' => (int) ($this->d / $daysPerWeek),
'dayz' => $this->d % $daysPerWeek,
'dayz' => fmod($this->d, $daysPerWeek),
'hours' => $this->hours,
'minutes' => $this->minutes,
'seconds' => $this->seconds,
Expand Down Expand Up @@ -2405,10 +2427,11 @@ public function total($unit)
}

if ($unit === 'weeks') {
return $result / $daysPerWeek;
$result /= $daysPerWeek;
}

return $result;
// Cast as int numbers with no decimal part
return fmod($result, 1) === 0.0 ? (int) $result : $result;
}

/**
Expand Down
66 changes: 66 additions & 0 deletions tests/CarbonInterval/TotalTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -127,6 +127,72 @@ public function testTotalsWithCustomFactors()
CarbonInterval::setCascadeFactors($factors);
}

public function testFloatHoursFactors()
{
$factors = CarbonInterval::getCascadeFactors();
CarbonInterval::setCascadeFactors([
'minute' => [60, 'seconds'],
'hour' => [60, 'minutes'],
'day' => [7.5, 'hours'],
'week' => [5, 'days'],
]);

$this->assertSame(
'2 weeks 1 day 5 hours 45 minutes',
CarbonInterval::minutes(11 * (7.5 * 60) + (5 * 60) + 45)->cascade()->forHumans()
);

CarbonInterval::setCascadeFactors($factors);
}

public function testFloatDaysFactors()
{
$factors = CarbonInterval::getCascadeFactors();
CarbonInterval::setCascadeFactors([
'minute' => [60, 'seconds'],
'hour' => [60, 'minutes'],
'day' => [8, 'hours'],
'week' => [5.5, 'days'],
]);

$this->assertSame(
'3 weeks 1 day 5 hours 45 minutes',
CarbonInterval::minutes(17.5 * (8 * 60) + (5 * 60) + 45)->cascade()->forHumans()
);

CarbonInterval::setCascadeFactors($factors);
}

public function testFloatInMultipleFactors()
{
$factors = CarbonInterval::getCascadeFactors();
CarbonInterval::setCascadeFactors([
'minute' => [23.2, 'seconds'],
'hour' => [25.662, 'minutes'],
'day' => [10 / 3, 'hours'],
'week' => [pi(), 'days'],
]);

$interval = CarbonInterval::minutes(50000)->cascade();

$this->assertSame(
'185 weeks 2 days 3 hours 35 minutes 35 seconds',
$interval->forHumans()
);
// Show how we (approximately) get back to initial values
$this->assertEqualsWithDelta(
50000 * 23.2,
35 + (35 * 23.2) + (3 * 25.662 * 23.2) + (2 * (10 / 3) * 25.662 * 23.2) + (185 * pi() * (10 / 3) * 25.662 * 23.2),
3
);
// Show how total uncascade
$this->assertEqualsWithDelta(50000 / 25.662, $interval->totalHours, 0.05);
$this->assertEqualsWithDelta(50000, $interval->totalMinutes, 0.1);
$this->assertEqualsWithDelta(50000 * 23.2, $interval->totalSeconds, 1);

CarbonInterval::setCascadeFactors($factors);
}

public function testGetTotalsViaGettersWithCustomFactors()
{
$cascades = CarbonInterval::getCascadeFactors();
Expand Down

0 comments on commit 7507aec

Please sign in to comment.