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) ?
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));
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.
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:
using business months (30 days) instead of calendar months,
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.
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++;
}
You can use addMonth() function available in Carbon Library like below
Carbon::create(2018, 1, 31, 0, 0, 0)->addMonth();
Related
I've been using the following function fine until the other day when the clocks went forward:
function months($month_format="F") {
$array = array();
for ($i = 1; $i <=12; $i++) {
$array[$i]['string'] = date($month_format, mktime(0,0,0,$i));
$array[$i]['int'] = date('m', mktime(0,0,0,$i));
}
return $array;
}
It outputs an array with:
string[]
int[]
Since the other day (like 2 days ago, when the clocks went forward in the UK), the function seems to be showing me 2 march months and 0 Februarys....
Very very strange...
I'm using rethinkdb with their eachPosTime function... not sure if this is causing it.
I tried using a different function, but still using mktime:
function months(){
$start_month = 1;
$end_month = 12;
$start_year = date("Y");
$array = array();
for($m=$start_month; $m<=12; ++$m){
echo $m.'<br>';
$array[$m]['string'] = date('F', mktime(0,0,0,$m));
$array[$m]['int'] = date('m', mktime(0,0,0,$m));
if($start_month == 12 && $m==12 && $end_month < 12)
{
$m = 0;
$start_year = $start_year+1;
}
//echo date('F Y', mktime(0, 0, 0, $m, 1, $start_year)).'<br>';
if($m == $end_month) break;
}
return $array;
}
Still, I am having no luck.
Check the image here, which shows the output of the months() function:
Output of the months() function
This is not to do with the clocks changing, but the time of the month, and the entirely unhelpful signature of the mktime function.
When you leave out parameters from a "mktime" call, they are filled in with today's date. In your case, you specified month without day or year, so when you ask for mktime(0, 0, 0, 2); it will fill in today's day and year, and look for the 29th February 2021 - a day that doesn't exist. Since the time library "overflows" dates, this becomes the 1st March.
The solution is to pass an explicit day to "mktime", as in mktime(0,0,0,$m,1) or to use a less confusing function.
This question already has answers here:
How to find the last day of the month from date?
(30 answers)
Closed 2 years ago.
I'm having some trouble figuring out how to get an array item with a $variable - 1.
What I am trying to do:
I have an array of days in a month
$monthsArray = [31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31];
I have a variable which is the month
$monthOfDeparture = mb_substr($departureDate, 3, 4);
which has the value of 01 for January etc.
I am trying to get the correct month by
$theMonth = $monthsArray[$monthOfDeparture - 1]
What I actually get is the month of departure ie: March (31) instead of February (28) if $monthOfDeparture = 03.
Any help would be welcome.
Providing code: note the $arrivalDate and $departureDate look like this 23-02-21 (dd-mm-yy)
$arrivalDateNumber = mb_substr($arrivalDate, 0, 1);
$departureDateNumber = mb_substr($departureDate, 0, 1);
$monthOfArrival = mb_substr($arrivalDate, 3, 4);
$monthOfDeparture = mb_substr($departureDate, 3, 4);
$monthsArray = [31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31];
if( $departureDateNumber < $arrivalDateNumber) {
$depNumTwo = $monthsArray[$monthOfDeparture - 1];
$lodgingNights = $depNumTwo - $arrivalDateNumber + $departureDateNumber;
$pay = $lodgingNights * $price_per_night;
}else{
$lodgingNights = $departureDateNumber - $arrivalDateNumber;
$pay = $lodgingNights * $price_per_night;
}
Edit
There was an error my logic. What I wanted to do was:
// if the number of days in departure is greater then the number of days in arrival
if( $departureDateNumber < $arrivalDateNumber) {
//get the number of days in the month of arrival
$arrNumTwo = $monthsArray[$monthOfArrival];
//the user will stay: total number of days in arrival month - starting date of arrival + days in the next month
$lodgingNights = $arrNumTwo - $arrivalDateNumber + $departureDateNumber;
$pay = $lodgingNights * $price_per_night;
}else{
$lodgingNights = $departureDateNumber - $arrivalDateNumber;
$pay = $lodgingNights * $price_per_night;
}
The problem I am having now is that I get Notice: Undefined index: 02-2 in my path on line $arrNumTwo = $monthsArray[$monthOfArrival]; I guess that is becouse of this code segment $monthOfArrival = mb_substr($arrivalDate, 3, 4); although I don't know why it dosesn't just take the 3rd and 4th character.
Edit nmb xy.
Figured out I misnderstood how substr works. Needed to use $monthOfArrival = mb_substr($arrivalDate, 3, 2);
While not an answer to your placed question, this might be an answer worth your while:
$daysInMonth = cal_days_in_month(CAL_GREGORIAN, $theMonth, $year);
*You might need to do a +2000 for the $year
Docs for cal_days_in_month. This has the added bonus of working in leap years.
Alternatively you can use DateTime (which I recommend). Datetime seems a bit intimidating in the beginning, but has a lot of features which make your life a lot easier. Example:
$arrival = DateTime::createFromFormat("d-m-y", $arrivalDate);
$departure = DateTime::createFromFormat("d-m-y", $departureDate);
You can compare those with eachother (eg if( $departure < $arrival) {}) and you can get the diff a lot easier:
$diff = $arrival->diff($departure)->days;
Basically, I have a date set to today. From that date, I want to get the next and previous 20th day of the month. Basically, starting with 07/10/2020 (date of the post in d/m/Y format), I should end up with a date/timestamp to 20/09/2020 (previous 20th) and 20/10/2020 (next 20th).
I tried a few things and ended up with:
$ld_next_str = date("d/m/Y", strtotime("20 day this month"));
$ld_next_dt = DateTime::createFromFormat("d/m/Y", $ld_next_str);
$ld_prev_str = date("d/m/Y", strtotime("20 day last month"));
$ld_prev_dt = DateTime::createFromFormat("d/m/Y", $ld_prev_str);
But turns out this : "20 day this month" just adds 20 days to the current day so right now (on the seventh) it returns the 27th.
Knowing this, I figured I could probably just do something like subtracting the current day to that date (27 - 7 = 20) but I feel like there's probably a simpler way to do it, using strtotime without any extra steps.
Thanks in advance!
PS : I know you can get some days using "first" / "second" / "third" etc... but (I think) it doesn't go any higher than "twelfth", because I tried "twentieth" and that didn't work...
The formula for the previous 20th is:
mktime(0, 0, 0, $month - (1 - floor($day / 20)), 20, $year))
The formula for the next 20th is:
mktime(0, 0, 0, $month + floor($day / 20), 20, $year))
As a demo:
for ($month = 1; $month <= 12; $month++) {
for ($day = 1; $day <= 30; $day += 5) {
$prev = mktime(0, 0, 0, $month - (1 - floor($day / 20)), 20, 2020);
$next = mktime(0, 0, 0, $month + floor($day / 20), 20, 2020);
echo "$day/$month/2020: ", date('d/m/Y', $prev), ', ', date('d/m/Y', $next), PHP_EOL;
}
}
Outputs:
1/1/2020: 20/12/2019, 20/01/2020
6/1/2020: 20/12/2019, 20/01/2020
11/1/2020: 20/12/2019, 20/01/2020
16/1/2020: 20/12/2019, 20/01/2020
21/1/2020: 20/01/2020, 20/02/2020
26/1/2020: 20/01/2020, 20/02/2020
1/2/2020: 20/01/2020, 20/02/2020
6/2/2020: 20/01/2020, 20/02/2020
11/2/2020: 20/01/2020, 20/02/2020
16/2/2020: 20/01/2020, 20/02/2020
21/2/2020: 20/02/2020, 20/03/2020
...
Solution with an external class (a DateTime extension) that supports the cron syntax.
The string "0 0 20 * *" specifies the 20th of every month and every weekday at 00:00.
The nextCron() and previousCron() methods are used to determine the previous/next point in time based on a specific date.
$cronStr = "0 0 20 * *"; //every 20th 00:00
//source on https://github.com/jspit-de/dt
$next20 = dt::create('now')->nextCron($cronStr);
echo $next20; //2020-10-20 00:00:00
$prev20 = dt::create('now')->previousCron($cronStr);
echo $prev20; //2020-09-20 00:00:00
Outputs for the execution on October 8, 2020.
Here a better solution by using DateTime
$lastMonth = (new DateTime("last month"))->format("Y-m-20");
$thisMonth = (new DateTime("this month"))->format("Y-m-20");
$nextMonth = (new DateTime("next month"))->format("Y-m-20");
echo "$lastMonth - $thisMonth - $nextMonth";
I'm using this code to get number of days in last month, but if the current month is January (01) it's going to use 00 month instead of 12.
$prev_month_days = cal_days_in_month(CAL_GREGORIAN, date('m')-1, date('Y'));
Searched Google for 20 minutes and could not find any reasonable and shorthand solution.
Please help. Thanks.
<?php
echo date("t", mktime(0,0,0, date("n") - 1));
?>
The example provided by Dan is not correct.
You have to check the result of date("n") and correct for January return
And if this is part of a time function, which seems logical since you are looking for the previous number of days in a month. You will also have to compensate for the year shifting back. Otherwise you would shoot to December of the current year.
What works better is..
$NowYear = date("Y");
$NowMonth = date("n");
if ($NowMonth == 1) {
$ThenMonth = 12;
$ThenYear = $NowYear - 1;
}
else {
$ThenMonth = $NowMonth - 1;
$ThenYear = $NowYear;
}
$ThenNumberOfDays = date("t", mktime(0, 0, 0, $ThenMonth, 1, $ThenYear));
$ThenNumberOfDays would have the correct number of days...
for ($y = 25; $y >= 7; $y--)
{
$showYear = false;
for ($m = 12; $m >= 1; $m--)
{
if (blogList($m, $y))
$showYear = true;
}
if ($showYear) {
echo '<h2>' . (2000 + $y) . '</h2>';
for ($m = 12; $m >= 1; $m--)
{
echo blogList($m, $y);
}
}
}
//blog archives
function blogList($month, $year)
{
$lastDate = array(31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31);
$beginDate = mkTime(0, 0, 0, $month, 1, $year);
$endDate = mkTime(0, 0, 0, $month, $lastDate[$month - 1], $year);
$query = .......;
}
i don't know why he set the $y=25.$showYear = false;
why the $lastDate = array(31,29,31,30,31,30,31,31,30,31,30,31);?
$y = 25 is because he's looping backwards from 2025 to 2007. $y ends up as the year argument for mkTime (see http://php.net/manual/en/function.mktime.php).
that array holds the last date of each calendar month, e.g. January has 31 days.
To show blog posts from 2007 to 2025.
The number of days in the month of each month.
However, don't try to learn from the above code!
Because blogList($m,$y) expects values for $y in the range of 7-25.
Those are the last dates of the months on a Gregorian calendar.
$lastdate holds the number of days in each of the 12 months of the year, and before running the query its trying to find the beginning and ending days of the month.
I'm not sure about the y=25 - that might depend on whats defined in the $query variable
1) Because it's the date from 2007 to 2025. The variable it's set to false because it needs to check something inside blogList, it's counting the days remaining to the end of the month, I think.
2) That's the last day of the month. The array has 12 elements.