I want to populate a select input with the last 5 months (including the current month). So on 09/04/2014 the options should look something like this:
April 2014
March 2014
February 2014
January 2014
December 2013
My first solution with PHP DateTime seemed to work:
for ($i = 0; $i < 5; $i++) {
$date = new DateTime($i.' months ago');
echo $date->format('F Y'); //populate select input
}
But in fact it doesn't handle edge cases. For example, on 31/03/2014 it produces:
March 2014
March 2014
January 2014
December 2013
December 2013
What is the correct way to list the last 10 months with PHP DateTime?
$date = new DateTime("2014/03/31");
for ($i = 0; $i <= 9; $i++) {
$date->modify("-1 month");
echo $date->format('F Y'); //populate select input
}
You are getting same 2 months, because when you are subtracting 1 month from date, that has more days than previous month. Example: when you subtract 1 month from 31.3. you will get 3.3. (same month), and not 28.2. as you might expect...
My suggestion is to get first day of current month, and then do your logic:
$dt = new DateTime('first day of this month');
for ($i = 1; $i <= 10; $i++) {
echo $dt->format('F Y'), "\n";
$dt->modify('-1 month');
}
demo
Related
This question already has answers here:
Get the last 12 months in PHP
(5 answers)
Closed 3 years ago.
I'm trying to use a loop to show the last 12 months, but March appears twice.
for ($i=0; $i < 12; $i++) {
$month = date("d/m/Y", strtotime("now -$i month"));
echo "$month<br>";
}
Output:
30/01/2020
30/12/2019
30/11/2019
30/10/2019
30/09/2019
30/08/2019
30/07/2019
30/06/2019
30/05/2019
30/04/2019
30/03/2019
02/03/2019
How can I solve this?
Use the first day of the month as the basis in your script.
"first day of this month -$i month"
Use DateTime and keep track of month/year combinations that you already had:
$dt = new DateTime();
$previous = [];
for ($i=0; $i < 12; $i++) {
$month = $dt->format("d/m/Y");
echo "$month<br>".PHP_EOL;
$previous[$dt->format('Y-m')] = true;
$dt->modify('-1 month');
while (array_key_exists($dt->format('Y-m'), $previous)) {
$dt->modify('-1 day');
}
}
If this encounters a previously encountered month-year combination, it starts substracting days until it reaches the previous month.
Will produce this output:
30/01/2020
30/12/2019
30/11/2019
30/10/2019
30/09/2019
30/08/2019
30/07/2019
30/06/2019
30/05/2019
30/04/2019
30/03/2019
28/02/2019
You probably want to use DatePeriod for this task and not date. It's much simpler and more sane.
$start = new DateTime;
$start->setDate($start->format('Y'), $start->format('n'), 1); // Normalize the day to 1
$start->sub(new DateInterval('P12M'));
$interval = new DateInterval('P1M');
$recurrences = 12;
foreach (new DatePeriod($start, $interval, $recurrences, true) as $date) {
echo $date->format('F, Y'), "\n"; // attempting to make it more clear to read here
}
Output:
February, 2019
March, 2019
April, 2019
May, 2019
June, 2019
July, 2019
August, 2019
September, 2019
October, 2019
November, 2019
December, 2019
January, 2020
I am trying to get the last six months from the current date in PHP. It's a simple problem, but my code is not giving the right result.
for ($j = 0; $j <= 5; $j++) {
echo date("F Y", strtotime(" -$j month"));
echo "<br>";
}
And the output is
March 2018
March 2018
January 2018
December 2017
December 2017
October 2017
I dont understand why March is coming twice.
Because strototime('-1 month') doesn't handle correctly the end of month.
You could use the first day of the current month:
$dt = strtotime(date('Y-m-01'));
for ($j = 0; $j <= 5; $j++) {
echo date("F Y", strtotime(" -$j month", $dt));
echo "<br>";
}
Outputs:
March 2018
February 2018
January 2018
December 2017
November 2017
October 2017
I'm having trouble coming up with a solution for this and Google doesn't seem to be yielding anything (I'm also unsure what to search for) but basically, what I want is to get the last three 15-day periods at any given date.
A period being the 1st to the 15th of the month and 16th to the last day of the month.
Let's say today, we're on Sep 22, 2016. The date ranges I want to get are:
Sep 1 - Sep 15
Aug 16 - Aug 31
Aug 1 - Aug 15
Similarly if we were on Sep 12, 2016, the dates I'd want to get are:
Aug 16 - Aug 31
Aug 1 - Aug 15
Jul 16 - July 31
Right now I have some pseudo code that goes like this:
Get day (number)
check if greater than 15
Get last 15-day period date ranges
Continue to get 2 more date ranges before that
I guess what I'm having trouble with here is translating 3 and 4 into PHP code.
Can someone shed some light here?
We can simplify these requirements into two distinct things.
Define a given month into 2 periods. The first, being the 1st day of the month to the 15th day of the month. The second, being the 16th day of the month to the last day of the month.
Given a date, determine which are the last 3 periods subsequent the current period from that date, exclusive.
Step 1
The first part is easy.
$date = new DateTimeImmutable("Sep 22, 2016");
// Period 1 (from 1st day of month to 15th)
$p1Start = $date->modify("first day of this month");
$p1End = $p1Start->add(new DateInterval("P14D"));
// Period 2 (from 16th day of month to last day of month)
$p2Start = $p1End->add(new DateInterval("P1D"));
$p2End = $p2Start->modify("last day of this month");
echo "Period 1 Starts On: {$p1Start->format('M j, Y')}\n";
echo "Period 1 Ends On: {$p1End->format('M j, Y')}\n\n";
echo "Period 2 Starts On: {$p2Start->format('M j, Y')}\n";
echo "Period 2 Ends On: {$p2End->format('M j, Y')}\n";
Output you get from this is as expected:
Period 1 Starts On: Sep 1, 2016
Period 1 Ends On: Sep 15, 2016
Period 2 Starts On: Sep 16, 2016
Period 2 Ends On: Sep 30, 2016
Now you just need to put all of this in a function and reuse the same solution to get to step 2.
function getPeriods($dateString) {
$date = new DateTimeImmutable($dateString);
// Period 1 (from 1st day of month to 15th)
$p1Start = $date->modify("first day of this month");
$p1End = $p1Start->add(new DateInterval("P14D"));
// Period 2 (from 16th day of month to last day of month)
$p2Start = $p1End->add(new DateInterval("P1D"));
$p2End = $p2Start->modify("last day of this month");
return [
"period1" => ["start" => $p1Start, "end" => $p1End],
"period2" => ["start" => $p2Start, "end" => $p2End],
];
}
So now let's say you start with a date like Sep 12, 2016. You can figure out which period this date falls in by checking the return value from this function like so.
$date = "Sep 12, 2016";
$periods = getPeriods($date);
if ($date >= $periods["period1"]["start"] && $date <= $periods["period1"]["end"]) {
// It's in Period1
} else {
// It's in Period2
}
Step 2
Now let's just modify this function a little to simplify so that the solution can be expanded upon. So we'll name it getPeriod instead of getPeriods and have it only return one period from a given date.
function getPeriod($dateString) {
$periods = [];
$date = new DateTimeImmutable($dateString);
// Period 1 (from 1st day of month to 15th)
$p1Start = $date->modify("first day of this month");
$p1End = $p1Start->add(new DateInterval("P14D"));
// Period 2 (from 16th day of month to last day of month)
$p2Start = $p1End->add(new DateInterval("P1D"));
$p2End = $p2Start->modify("last day of this month");
// Figure out which period the given date belongs in
if ($date >= $p1Start && $date <= $p1End) {
$period = ["start" => $p1Start, "end" => $p1End];
} else {
$period = ["start" => $p2Start, "end" => $p2End];
}
return $period;
}
So to get the previous period from this date we just simply take the start date of the current period, returned by this function, subtract 1 day from that, and send the new date back to the function again to get the previous period.
Similarly, to get the next period just take the end date, add 1 day, and send that back to the function.
Final Result
Here's an example.
// We want up to 3 periods previous to this given date's period.
$date = "Sep 12, 2016";
$periods = [];
$currentPeriod = getPeriod($date);
for ($i = 0; $i < 3; $i++) {
$currentPeriod = $periods[] = getPeriod($currentPeriod["start"]->sub(new DateInterval("P1D"))->format("c"));
}
// Print out the period dates
$i = 1;
foreach($periods as $period) {
echo "Period $i: {$period['start']->format('M j, Y')} - {$period['end']->format('M j, Y')}\n";
$i++;
}
Output is what you'd expect...
Period 1: Aug 16, 2016 - Aug 31, 2016
Period 2: Aug 1, 2016 - Aug 15, 2016
Period 3: Jul 16, 2016 - Jul 31, 2016
function getDateRangesStartingFrom(\Carbon\Carbon $date, $numberOfMonths = 3)
{
$ranges = [];
for ($i = 0; $i < $numberOfMonths; $i++)
{
$month = [];
/**
* Generates the first 01-15 range.
*/
$rangeOne = \Carbon\Carbon::createFromFormat('Y-m-d', "{$date->year}-{$date->month}-01")->addMonths($i);
$rangeTwo = (clone $rangeOne)->addDays(14);
$month[] = [$rangeOne, $rangeTwo];
/**
* Generates the second 16-X (depending on the month) range.
*/
$rangeThree = (clone $rangeTwo)->addDays(1);
$rangeFour = (clone $rangeTwo)->addDays($rangeOne->daysInMonth - 15);
$month[] = [$rangeThree, $rangeFour];
/**
* We push all the ranges into the global array so we can return it later.
*/
$ranges[$rangeOne->format('F')] = $month;
}
return $ranges;
}
$ranges = getDateRangesStartingFrom(\Carbon\Carbon::createFromFormat('Y-m-d', '2016-09-22'));
var_dump($ranges);
This is how I would do it.
I use Carbon so that dealing with dates is much easier, go check it out!
I have created a function named getDateRangesStartingFrom(\Carbon\Carbon $date) that takes a parameter that represents from which date (or the month, essentially, since that's what we care about) the ranges should begin from to generate the ranges.
I then make a loop of $numberOfMonths iterations to begin the generation.
First, we proceed by creating the first part of the range, by using the year and month of the starting date. I then add the iterator index that represents what month we are in. Second, we create a second date that represents the second half of the first range (1-15).
After, we create the other two parts, by subtracting the days in the month by the days we've added before, so that we get the remaining days of the month.
We push all these ranges into a function scoped variable, and return the results.
Sherif's answer is good as well but take this as an alternative if you want to try Carbon.
Tried to find a solution written as simple code as possible.
I have some comments in code. I think that make clear how code is working.
Check the working code here
<?php
// use one of the two examples.
// $now = DateTime::createFromFormat('Y-m-d', '2016-09-12');
$now = new DateTime();
print_r(getPeriods($now));
function getPeriods(DateTime $now)
{
$currentDay = $now->format('d');
$dates = []; // Hold all date ranges
$start = clone $now;
$end = clone $now;
if ($currentDay > 15) { // For dates after 15th day of month
// Starting period is the first day of previous month
$start->modify('first day of previous month');
//Ending date is the 15th day of this month
$end->modify('first day of this month')->modify('14 days');
} else { // For dates before 15th day of month
//Starting period is the 16th day of the previous of previous month.
$start->modify('first day of previous month')
->modify('first day of previous month')
->modify('15 days');
//Ending period is the last day of previous month
$end->modify('last day of previous month');
}
$dates[] = $start;
$i = 2;
// $c will hold all intermediate dates of periods between
// $start and $end
$c = clone $start;
while ($c < $end) {
if ($c->format('d') > 15) {
// if we are in the 16th of the month,
// give me the last day of the month
$c->modify('last day of this month');
} else {
// give me the 15th day of month
$c->modify('14 days');
}
$dates[] = clone $c; // always clone $c so can not change other references
if ($i > 0) {
$c->modify('1 day');
$dates[] = clone $c;
}
$i--;
}
return array_chunk($dates, 2);
}
I am displaying month titles 3 month into the future as well as getting the 1st and last day of each of those months.
for($i = 1; $i < 4; $i++) { // For each month for 3 months
$monthTitle = date('F Y', strtotime('+'.$i.' month'));
$begin_date = date('Y-m-01', strtotime('+'.$i.' month')); // First day of calendar month in future.
$end_date = date('Y-m-t', strtotime('+'.$i.' month')); // Last day of calendar months in future.
};
Nov. 29, 2015 output is:
December 2015
2015-12-01
2015-12-31
January 2016
2016-01-01
2016-01-31
February 2016
2016-02-01
2016-02-29
This was working great right up until yesterday, Nov. 29, 2015 but today Nov. 30, 2015 it skips February.
Nov. 30, 2015 output is:
December 2015
2015-12-01
2015-12-31
January 2016
2016-01-01
2016-01-31
March 2016
2016-03-01
2016-03-31
I'm guessing a bug but does anybody know of a work around?
Thanks to #devlin carnate for pointing me in the right direction.
for($i = 1; $i < 4; $i++) { # for each month
$tmp = date('Y-m-15'); // Get the middle of the month to avoid PHP date bug.
$begin_date = date('Y-m-01', strtotime($tmp . '+'.$i.' month')); // First day of calendar month in future.
$end_date = date('Y-m-t', strtotime($begin_date)); // Last day of calendar months in future.
$monthTitle = date('F Y', strtotime($begin_date));
};
This seems to work very well.
You can use DateInterval to add one month to the current date, so you can get the first and the last day of month.
<?php
$date = new DateTime('2015-12-01');
$i = 0;
while($i < 3){
printf("%s | first day: %s, | last day: %s <br>", $date->format('F Y'), $date->format('d'), $date->format('t'));
$date->add(new DateInterval('P1M'));
$i++;
}
Output:
December 2015 - first day: 01, | last day: 31
January 2016 - first day: 01, | last day: 31
February 2016 - first day: 01, | last day: 29
if last day of next month is needed then you can use this
$d = new DateTime( '2010-01-31' );
$d->modify( 'last day of next month' );
echo $d->format( 'Y-m-d' ), "\n";
Im trying to generate a list of months along with the year and 1st date of the month.
I use the below code, but oddly February is missing and March is being repeated 2 times
Code
for ($i = 1; $i <= 12; $i++):
$year='2013';
$month_numb = date($year.'-m-01', mktime(0,0,0,$i));
echo $month_numb.'<br>';
endfor;
Output
2013-01-01
2013-03-01
2013-03-01
2013-04-01
2013-05-01
2013-06-01
2013-07-01
2013-08-01
2013-09-01
2013-10-01
2013-11-01
2013-12-01
Can someone tell me why this is happening and how do i fix it?
Today is the 30th Jan. Dates in Feburary go up to only 28 this year. mktime() uses today's date for values not supplied. Since 30th Feburary is invalid, mktime() rolls over to the next valid date - 01 March 2013.
Change your code
for ($i = 1; $i <= 12; $i++):
$month_numb = date('Y-m-01', mktime(0,0,0,$i, 1, 2013));
echo $month_numb.'<br>';
endfor;
The above assigns the day/year to the code and then translates it, rather than taking today's values.
When calling mktime(), the default day of the month is the current day of the month (in today's case, it's 30.) Since Feb 30 is actually March 2nd (most of the time), that's why mktime(0,0,0,2) will return the month of March.
Give the day of the month to mktime, mktime(0,0,0,$i,1).
Isn't it better to do it without date and mktime ?
$year = 2013;
for ( $i = 1; $i <= 12; $i++ ) {
$month_numb = $year . '-' . $i . '-01';
echo $month_numb . '<br/>';
}
Use DateTime. It's simpler:
$datetime = new DateTime('2013-01-01');
for ($i = 1; $i <= 11; $i++)
{
echo $datetime->format('Y-m-d') . "<br>\n";
$datetime->modify('+1 month');
}
See it in action
This is because today it's the 30th. And there is no 30th of February. Tomorrow you will have the same issue with all months with 30 days only. And on Feb 1st, your problems will be gone again.
The fix, add the 5th parameter, don't let the day of month default to today's date:
$month_numb = date($year.'-m-01', mktime(0,0,0,$i, 1));
The fifth parameter of mktime will be set to today's day: (from docs: ... int $day = date("j") ...), which is 30 (of January). 30th of February doesn't exist, so it goes to March.
You'll see it will work correctly the first of february.