[PHP/Carbon]The result varies by subMonth, subRealMonth, subMonthNoOverflow
Tags: php, laravel
Published: 2021-04-06 10:28:40

## An error occurred when the end of March

In the end of March, suddenly my CI threw errors running PHPUnit.
The reason is that difference from subMonth, subRealMonth, subMonthNoOverflow in Carbon, which is PHP-date library.

## Version of Carbon

- 2.43.0

## Results at the end of March

I executed following PHPUnit code, and checked the result.

```php
    /**
     * When subtract one month from the date at the end of March, you get 28 days before, 30 days before, and exactly one month before, respectively.
     */
    public function test_3月末の日付から1ヶ月引くとそれぞれ28日前、30日前、正確な1月前になる()
    {
        Carbon::setTestNow(Carbon::create(2001, 3, 31, 10));
        self::assertEquals(
            Carbon::create(2001, 3, 3, 10),
            Carbon::now()->subMonth(),
        );
        self::assertEquals(
            Carbon::now()->subDays(30),
            Carbon::now()->subRealMonth(),
        );
        self::assertEquals(
            Carbon::create(2001, 2, 28, 10),
            Carbon::now()->subMonthNoOverflow(),
        );
    }
```

The result of subMonth is March 3, which is 3 days after end of February. Why?
Because subMonth judged the date is February **31**. But it does not exist. And February 31 is equally March 3. So the result is March 3.

The result of subRealMonth is just 30 days ago.

Finally, subMonthNoOverflow outputted the end of February!
Because the method rounds down overflowed date, so February **31** becomes February **28**.

In short, subMonth and subRealMonth sometimes output day in **current** Month.

## Results at the end of March(Leap year)

And, in leap years, the methods has same logic.

```php
    /**
     * When subtract one month from the end of March in a leap year, you get 29 days before, 30 days before, and exactly one month before, respectively.
     */
    public function test_うるう年の3月末から1ヶ月引くとそれぞれ29日前、30日前、正確な1月前になる()
    {
        Carbon::setTestNow(Carbon::create(2000, 3, 31, 10));
        self::assertEquals(
            Carbon::create(2001, 3, 2, 10),
            Carbon::now()->subMonth(),
        );
        self::assertEquals(
            Carbon::now()->subDays(30),
            Carbon::now()->subRealMonth(),
        );
        self::assertEquals(
            Carbon::create(2000, 2, 29, 10),
            Carbon::now()->subMonthNoOverflow(),
        );
    }
```

The difference point is subMonth's result.
In leap years, February **31** equals March **2** because February has 29 days.

---

Therefore, I recommend subMonthNoOverflow method when you should subtract months from specific date. The result is intuitive.

## How to calculate the 15th of the previous month

According to difference of these methods, let's check how to calculate the 15th of the previous month.

```php
    /**
     * When the requirement is to calculate 0:00:00 on the 15th of the previous month, care should be taken to execute startOfMonth() first, or subMonthNoOverflow() should be used.
     */
    public function test_前月の15日0時0分を算出するという要件の時は先にstartOfMonthを実行するように気をつけるか、subMonthNoOverflowを使うべき()
    {
        Carbon::setTestNow(Carbon::create(2001, 3, 31));
        self::assertEquals(
            Carbon::create(2001, 2, 15),
            Carbon::now()->startOfMonth()->subMonth()->addDays(14),
        );

        // when subMonth executed before startOfMonth, the month is March...
        self::assertEquals(
            Carbon::create(2001, 3, 15),
            Carbon::now()->subMonth()->startOfMonth()->addDays(14),
        );

        self::assertEquals(
            Carbon::create(2001, 2, 15),
            Carbon::now()->subMonthNoOverflow()->startOfMonth()->addDays(14),
        );

        self::assertEquals(
            Carbon::create(2001, 2, 15),
            Carbon::now()->startOfMonth()->subMonthNoOverflow()->addDays(14),
        );
    }
```

## Conclusion

Use subMonthNoOverflow instead of subMonth, subRealMonth. The result is always intuitive. In limited case, subMonth and subRealMonth should be used.