Why is there a conflict of days in the Carbon API?

Asked

Viewed 165 times

3

I am developing an application. And I am using the Carbon API. But conflicts are happening from one month to the next. Today is July 31st. And the code I used was the following:

    $inicioMesAtual = Carbon::now()->startOfMonth();
    $fimMesAtual =  Carbon::now()->endOfMonth();

    $inicioMesPassado = Carbon::now()->subMonth()->startOfMonth();
    $fimMesPassado = Carbon::now()->subMonth()->endOfMonth();

It happened that in the $fimMesAtual gives the same result as the $fimMesPassado

The exits were like this:

Carbon @1564628399 {#350 ▼
  date: 2019-07-31 23:59:59.999999 America/Sao_Paulo (-03:00)
}
Carbon @1564628399 {#352 ▼
  date: 2019-07-31 23:59:59.999999 America/Sao_Paulo (-03:00)
}

This case also occurs at the beginning of the month (The beginning of this month is the same as last month). Could someone explain to me why this is happening? Before July 31, the beginning and end of last month worked well. Why is this not working until July 31? How can I fix this?

2 answers

4

There is an inconsistency of the day of the month. We are at 31/07, when you do Carbon::now()->subMonth() it will basically subtract a month from the date, getting 31/06. As the month of June does not have the day 31, it changes to 01/07, which is the next day. If it changed to 30/06 the difference between the dates would be greater than one month.

See it with the class DateTime:

$now = new DateTime("2019-07-31");
$now->modify("-1 month");

var_dump($now);

The exit will be:

object(DateTime)#1 (3) {
  ["date"]=>
  string(26) "2019-07-01 17:50:54.359727"
  ["timezone_type"]=>
  int(3)
  ["timezone"]=>
  string(3) "UTC"
}

See that it changed to 01/07. This behavior would not happen, for example, if we were in August, which also has 31 days:

$now = new DateTime("2019-08-31");
$now->modify("-1 month");

var_dump($now);

Getting the expected output 31/07, because July has the day 31.

object(DateTime)#1 (3) {
  ["date"]=>
  string(26) "2019-07-31 00:00:00.000000"
  ["timezone_type"]=>
  int(3)
  ["timezone"]=>
  string(3) "UTC"
}

To get the dates you want, with DateTime, you could do:

$inicioMesAtual = new DateTime("first day of this month");  // 2019-07-01
$fimMesAtual =  new DateTime("last day of this month");  // 2019-07-31

$inicioMesPassado = new DateTime("first day of last month");  // 2019-06-01
$fimMesPassado = new DateTime("last day of last month");  // 2019-06-31

As the class Carbon inherits from DateTime, just do:

$inicioMesAtual = new Carbon("first day of this month");  // 2019-07-01
$fimMesAtual =  new Carbon("last day of this month");  // 2019-07-31

$inicioMesPassado = new Carbon("first day of last month");  // 2019-06-01
$fimMesPassado = new Carbon("last day of last month");  // 2019-06-31
  • It worked fine, thank you for your answer. I went to search the github and something came up, as a tip to adjust, using PHP instead of Carbon. At first, you would consider the Carbon subMonth() as something not recommended for use?

  • 4

    I can’t say if it is recommended or not, because I’ve never used Carbon (I’ve never seen meaning in it), but we can say it’s unnecessary in this problem. I see no justification in performing arithmetic operations between dates to get something that can be obtained in a simpler way.

3


To documentation of Carbon warns about this:

By default, Carbon relies on the underlying Parent class PHP Datetime behavior. As a result Adding or subtracting months can overflow.

If you are using version 2 of Carbon, you can solve the problem in two ways.

Disable overflow behavior through the method settings for each instance:

$dt = Carbon::now();
$dt->settings([
    'monthOverflow' => false
]);

$inicioMesPassado = $dt->copy()->subMonth()->startOfMonth();
$fimMesPassado = $dt->copy()->subMonth()->endOfMonth();

Or use the methods themselves that prevent this overflow:

$dt = Carbon::now();
$inicioMesPassado = $dt->copy()->subMonthsNoOverflow()->startOfMonth();
$fimMesPassado = $dt->copy()->subMonthsNoOverflow()->endOfMonth();

If you are using version 1 of Carbon, you can use Carbon::useMonthsOverflow(false).

Carbon::useMonthsOverflow(false);
$dt = Carbon::now();
$inicioMesPassado = $dt->copy()->subMonth()->startOfMonth();
$fimMesPassado = $dt->copy()->subMonth()->endOfMonth();

Note that in the examples I used the method copy() to generate a copy of the Carbon object so that it is not modified in accordance with interactions that are made.

  • It worked fine, it looks like I’m using version 1 of Carbon. Thanks again. At first, in your opinion, the subMonth() working only subtracting the month. It would be a poorly done feature or it should work anyway?

  • 1

    In fact Carbon is dependent on DateTime PHP, so this behavior is DateTime. I believe that this happens because of performance, because it is easier to subtract only the month instead of validating the date. But also the DateTime offers other ways to search for certain dates as @Andersoncarloswoss showed.

Browser other questions tagged

You are not signed in. Login or sign up in order to post.