7

I need to get a next month payment date right. So I'm taking the last due date and adding a month to it. How can I do it?

My example is: last payment was 31-st of Januarry, I'm doing

Carbon::create(2018, 1, 31, 0, 0, 0)->addMonthsNoOverflow(1)

and it works, it gives 2018-02-28 but after that next payment will be due on 28-th again. So I need to set a date after I'm adding a month.

Carbon::create(2018, 2, 28, 0, 0, 0)->addMonthsNoOverflow(1)->day(31)

it gives me 2018-03-31 which is good but if I use this formula with the first example

Carbon::create(2018, 1, 31, 0, 0, 0)->addMonthsNoOverflow(1)->day(31)

it gives me overflow 2018-03-03. And this is my problem. What should I do? Is there something like ->setDayNoOverflow(31) ?

Yevgeniy Afanasyev
  • 37,872
  • 26
  • 173
  • 191

5 Answers5

11

You should not use last payment date, but keep the first date and calculate all the other date from the first, not the previous one:

Carbon::create(2018, 1, 31, 0, 0, 0)
Carbon::create(2018, 1, 31, 0, 0, 0)->addMonthsNoOverflow(1)
Carbon::create(2018, 1, 31, 0, 0, 0)->addMonthsNoOverflow(2)
Carbon::create(2018, 1, 31, 0, 0, 0)->addMonthsNoOverflow(3)

Supposing you don't have this data, you still can:

$day = 31;
$date = Carbon::create(2018, 1, 28, 0, 0, 0);
$date->addMonthsNoOverflow(1);
$date->day(min($day, $date->daysInMonth));
KyleK
  • 4,643
  • 17
  • 33
  • Thank you. I don't need an end of month, I need to set a date. It may be 20th and in this case my firs idea will work and it might be 30th in which case my first and second idea will fail as well as your idea. – Yevgeniy Afanasyev Jul 12 '18 at 05:44
  • 1
    OK, get it, you've iterate multiple time, so here you need to start from the initial date (and add 2, 3, 4 months...), not adding from previous date. – KyleK Jul 12 '18 at 05:48
  • Good idea. It would go for an answer if only I would not request to use previous due date and a day as input parameters. My problem is that - if someone did not pay in time but pay later, he should get a new due date based on previous successful payment date. – Yevgeniy Afanasyev Jul 12 '18 at 05:52
  • So I recommend `$date->day(min($day, $date->daysInMonth))` see my last edit. – KyleK Jul 12 '18 at 06:44
1

Seems like there is no such function in Carbon, so why don't make it yourself?

public function setDaysNoOverflow($value)
    {
        $year = $this->year;
        $month = $this->month;

        $this->day((int)$value);

        if ($month !== $this->month) {
            $this->year = $year;
            $this->month = $month;
            $this->modify('last day of this month');
        }

        return $this;
    }

Just add it to Carbon/Carbon.php and it should do the job.

It's based on addMonthsNoOverflow function source.

Warning: it's not tested, only for inspiration purposes.

Maxim Mazurok
  • 3,856
  • 2
  • 22
  • 37
0

I think the actual problem here is about the business logic or database schema, not date manipulation.

Despite I'm not sure what your subscription model is, it seems that you want to keep track of two separate pieces of information:

  • date until which service has been paid for,
  • day of month on which the payment is due.

By trying to store both in a single field you may lose some information, just as in case of January/February/March transition. By using separate fields you could prevent it and build a due date for payment on each month, for example as proposed by KyleK:

$lastPayment->addMonthsNoOverflow(1)->day(
    min($paymentDue->day, $lastPayment->daysInMonth)
);

In my opinion this is a bit complicated way to do this and could by greatly simplified by:

  1. using business months (30 days) instead of calendar months,
  2. letting go of the original payment day and just adding a month to the last payment.

However I understand that it may not be up to you to decide.

Paul
  • 3,186
  • 1
  • 19
  • 22
0

You can use it as a loop

    // from month
    $min = 3;
    // to mont
    $max = 9;
    // all dates
    $paymentDates = [];
    $date2 = \Carbon\Carbon::now();
    // next day (or today)
    $day = $date2->addDay(1)->format('d');
    $currentMonth = $date2->format('m');
    $year = $date2->format('Y');;
    
    $date = \Carbon\Carbon::now();

    $nextMonth = $currentMonth;

    for($i = $min; $i <= $max; $i++) {
      $date = \Carbon\Carbon::create($year, $currentMonth, $day)->addMonthsNoOverflow($nextMonth)->format('d/m/Y');
      // add to array
      array_push($paymentDates, $date);
      // next month
      $nextMonth++;
    }
Davron Achilov
  • 536
  • 4
  • 14
-1

You can use addMonth() function available in Carbon Library like below

Carbon::create(2018, 1, 31, 0, 0, 0)->addMonth();
Umang Patel
  • 133
  • 3