Set next month payment date (with PHP Carbon)
Asked Answered
T

5

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) ?

Tout answered 12/7, 2018 at 0:47 Comment(4)
Oh, it's very tricky. You can use ->endOfMonth() for your example, but still I would suggest you to count month as 30 days. It's simple to add, simple to calculate, no brainf*cking both for you, and customers :)Spasmodic
I wish I have an authority to make such decisions.Tout
This is virtually the same as #51286154Goldina
@JohnConde, thanks. It is really good answer, It can be used here too.Tout
M
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));
Makhachkala answered 12/7, 2018 at 5:35 Comment(4)
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.Tout
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.Makhachkala
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.Tout
So I recommend $date->day(min($day, $date->daysInMonth)) see my last edit.Makhachkala
S
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.

Spasmodic answered 12/7, 2018 at 1:12 Comment(11)
you may want to subtract a month tooTout
oh, sure, missed itSpasmodic
Here is the pull request on Carbon: github.com/briannesbitt/Carbon/pull/1387Spasmodic
Hey, man, you are fast. but you forget to test it. Replace this $this->modify((int)$value . ' day'); with $this->day((int)$value) or you'll have problem with months.Tout
uh-oh, I should really test it. I've even forgot to rename the function, thanks :)Spasmodic
you are not adding days, you are setting it. Please rename function to setDaysNoOverflowTout
oh, that's where confusion comes from. sure, it should set them, not add.Spasmodic
Turned out, that function still needs some fixes. I've updated my answer. Also, see updates in pull request, with tests.Spasmodic
Great idea, you cover situation with big numbers quite elegantly. Thank you. Do we need to cover other not quite relevant stuff like if the input parameter is zero, or negative or array...?Tout
I think @Makhachkala gave a better solution. I hope no hard feelings bout it.Tout
It's your decision, and I'm ok with that. Still, I had a lot of fun :)Spasmodic
T
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.

Theorize answered 12/7, 2018 at 8:8 Comment(0)
B
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++;
    }
Beckner answered 23/12, 2020 at 10:26 Comment(0)
E
-1

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

Carbon::create(2018, 1, 31, 0, 0, 0)->addMonth();
Edan answered 12/7, 2018 at 6:16 Comment(1)
I would expect this 2018-02-28 for a next payment date. But I would not get it with your formula.Tout

© 2022 - 2024 — McMap. All rights reserved.