Repository: briannesbitt/Carbon (PHP)

Issue:
FloorMonth for last day of month, returns next month if month has 31 days
<!--
    🛑 DON'T REMOVE ME.
    This issue template apply to all
      - bug reports,
      - feature proposals,
      - and documentation requests

    Having all those informations will allow us to know exactly
    what you expect and answer you faster and precisely (answer
    that matches your Carbon version, PHP version and usage).
    
    Note: Comments between <!- - and - -> won't appear in the final
    issue (See [Preview] tab).
-->
Hello,

I encountered an issue with the following code:
```php
<?php
require 'vendor/autoload.php';

use Carbon\Carbon;

function showDate($date) {
    echo "Date:" . $date->format("Y-m-d") . ", floorMonth: " . $date->copy()->floorMonth()->format("Y-m-d") . PHP_EOL;
}

$date1 = Carbon::parse("2021-03-30");
$date2 = Carbon::parse("2021-03-31");

showDate($date1);
showDate($date2);
```

Carbon version: **2.57.0 and 2.55.2**

PHP version: **8.0.8 and 8.1.1**

<!--
    Run the command `composer show nesbot/carbon`
    to get "versions"
    Use `echo phpversion();`
    to get PHP version.

    Some issues can depends on your context, settings,
    macros, timezone, language. So to be sure the code
    you give is enough to reproduce your bug, try it
    first in:
    https://try-carbon.herokuapp.com/?theme=xcode&export&embed

    You can use the [Options] button to change the version
    then when you get the bug with this editor, you can use
    the [Export] button, copy the link of the opened tab,
    then paste it in the issue. Then we can immediatly get
    your issue.
-->


I expected to get:

```
Date:2021-03-30, floorMonth: 2021-03-01
Date:2021-03-31, floorMonth: 2021-03-01
```
<!--
    Always give your expectations. Each use has their owns.
    You may want daylight saving time to be taken into account,
    someone else want it to be ignored. You may want timezone
    to be used in comparisons, someone else may not, etc.
-->

But I actually get:

```
Date:2021-03-30, floorMonth: 2021-03-01
Date:2021-03-31, floorMonth: 2021-04-01
```
<!--
    If you did not succeed to get the same result in
    https://try-carbon.herokuapp.com/ then precise the
    result you get.
-->

Thanks!



Discussion:
Same issue with months that have 31 days:
```php
<?php
require 'vendor/autoload.php';

use Carbon\Carbon;

function showDate($date) {
    echo "Date: " . $date->format("Y-m-d") . ", floorMonth: " . $date->copy()->floorMonth()->format("Y-m-d") . PHP_EOL;
}

for ($i = 1; $i <= 12; $i++) {
    showDate(Carbon::parse("2021-$i-01")->endOfMonth());
}

```

Output:
```
Date: 2021-01-31, floorMonth: 2021-02-01
Date: 2021-02-28, floorMonth: 2021-02-01
Date: 2021-03-31, floorMonth: 2021-04-01
Date: 2021-04-30, floorMonth: 2021-04-01
Date: 2021-05-31, floorMonth: 2021-06-01
Date: 2021-06-30, floorMonth: 2021-06-01
Date: 2021-07-31, floorMonth: 2021-08-01
Date: 2021-08-31, floorMonth: 2021-09-01
Date: 2021-09-30, floorMonth: 2021-09-01
Date: 2021-10-31, floorMonth: 2021-11-01
Date: 2021-11-30, floorMonth: 2021-11-01
Date: 2021-12-31, floorMonth: 2022-01-01 
```
This will be fixed in the next release, it can be tested using `composer require "nesbot/carbon:dev-master as 2.58.0"`

Thanks 🙏 



Patch:
diff --git a/src/Carbon/Traits/Date.php b/src/Carbon/Traits/Date.php
index de204bd48a..eb5818137f 100644
--- a/src/Carbon/Traits/Date.php
+++ b/src/Carbon/Traits/Date.php
@@ -643,7 +643,7 @@ public function getTimezone()
      *
      * @return array
      */
-    protected static function getRangesByUnit()
+    protected static function getRangesByUnit(int $daysInMonth = 31): array
     {
         return [
             // @call roundUnit
@@ -651,7 +651,7 @@ protected static function getRangesByUnit()
             // @call roundUnit
             'month' => [1, static::MONTHS_PER_YEAR],
             // @call roundUnit
-            'day' => [1, 31],
+            'day' => [1, $daysInMonth],
             // @call roundUnit
             'hour' => [0, static::HOURS_PER_DAY - 1],
             // @call roundUnit
diff --git a/src/Carbon/Traits/Rounding.php b/src/Carbon/Traits/Rounding.php
index 33062397fd..c991a9b0aa 100644
--- a/src/Carbon/Traits/Rounding.php
+++ b/src/Carbon/Traits/Rounding.php
@@ -52,7 +52,7 @@ public function roundUnit($unit, $precision = 1, $function = 'round')
             'millisecond' => [1000, 'microsecond'],
         ];
         $normalizedUnit = static::singularUnit($unit);
-        $ranges = array_merge(static::getRangesByUnit(), [
+        $ranges = array_merge(static::getRangesByUnit($this->daysInMonth), [
             // @call roundUnit
             'microsecond' => [0, 999999],
         ]);
@@ -93,7 +93,7 @@ public function roundUnit($unit, $precision = 1, $function = 'round')
                 $delta = $maximum + 1 - $minimum;
                 $factor /= $delta;
                 $fraction *= $delta;
-                $arguments[0] += $this->$unit * $factor;
+                $arguments[0] += ($this->$unit - $minimum) * $factor;
                 $changes[$unit] = round(
                     $minimum + ($fraction ? $fraction * $function(($this->$unit - $minimum) / $fraction) : 0)
                 );


Test patch:
diff --git a/tests/Carbon/RoundTest.php b/tests/Carbon/RoundTest.php
index fa49853c0e..b0420e71d1 100644
--- a/tests/Carbon/RoundTest.php
+++ b/tests/Carbon/RoundTest.php
@@ -113,6 +113,12 @@ public function testRoundWithBaseUnit()
 
         $this->assertCarbon($dt->copy()->roundMonth(), 2315, 8, 1, 0, 0, 0, 0);
         $this->assertCarbon($dt->copy()->floorMonth(), 2315, 7, 1, 0, 0, 0, 0);
+
+        for ($i = 1; $i <= Carbon::MONTHS_PER_YEAR; $i++) {
+            $dt = Carbon::parse("2021-$i-01")->endOfMonth()->floorMonth();
+
+            $this->assertCarbon($dt, 2021, $i, 1, 0, 0, 0, 0);
+        }
     }
 
     public function testRoundWithMetaUnit()
diff --git a/tests/CarbonInterval/ForHumansTest.php b/tests/CarbonInterval/ForHumansTest.php
index 8179f60066..8ebcf8eeff 100644
--- a/tests/CarbonInterval/ForHumansTest.php
+++ b/tests/CarbonInterval/ForHumansTest.php
@@ -378,18 +378,18 @@ public function testRoundCarryOverDoesntMatter()
 
         $this->assertSame('2 years 6 months', $interval);
 
-        $interval = CarbonInterval::months(1)->days(30);
-        $this->assertSame('2 months', $interval->forHumans(['parts' => 1, 'options' => CarbonInterface::ROUND]));
-        $interval = CarbonInterval::months(1)->days(31);
-        $this->assertSame('2 months', $interval->forHumans(['parts' => 1, 'options' => CarbonInterface::ROUND]));
-        $interval = CarbonInterval::months(1)->days(32);
-        $this->assertSame('2 months', $interval->forHumans(['parts' => 1, 'options' => CarbonInterface::ROUND]));
-        $interval = CarbonInterval::months(1)->days(30);
-        $this->assertSame('2 months', $interval->forHumans(['parts' => 1, 'options' => CarbonInterface::CEIL]));
-        $interval = CarbonInterval::months(1)->days(31);
+        $interval = CarbonInterval::months(2)->days(30);
+        $this->assertSame('3 months', $interval->forHumans(['parts' => 1, 'options' => CarbonInterface::ROUND]));
+        $interval = CarbonInterval::months(2)->days(31);
+        $this->assertSame('3 months', $interval->forHumans(['parts' => 1, 'options' => CarbonInterface::ROUND]));
+        $interval = CarbonInterval::months(2)->days(32);
+        $this->assertSame('3 months', $interval->forHumans(['parts' => 1, 'options' => CarbonInterface::ROUND]));
+        $interval = CarbonInterval::months(2)->days(30);
         $this->assertSame('3 months', $interval->forHumans(['parts' => 1, 'options' => CarbonInterface::CEIL]));
-        $interval = CarbonInterval::months(1)->days(32);
+        $interval = CarbonInterval::months(2)->days(31);
         $this->assertSame('3 months', $interval->forHumans(['parts' => 1, 'options' => CarbonInterface::CEIL]));
+        $interval = CarbonInterval::months(2)->days(32);
+        $this->assertSame('4 months', $interval->forHumans(['parts' => 1, 'options' => CarbonInterface::CEIL]));
     }
 
     public function testCeilShortMonth()