Find Month difference in php? [duplicate] - php

This question already has answers here:
Elegant way to get the count of months between two dates?
(10 answers)
Closed 3 years ago.
Is there any way to find the month difference in PHP? I have the input of from-date 2003-10-17 and to-date 2004-03-24. I need to find how many months there are within these two days. Say if 6 months, I need the output in months only. Thanks for guiding me for day difference.
I find the solution through MySQL but I need it in PHP. Anyone help me, Thanks in advance.

The easiest way without reinventing the wheel. This'll give you the full months difference. I.e. the below two dates are almost 76 months apart, but the result is 75 months.
date_default_timezone_set('Asia/Tokyo'); // you are required to set a timezone
$date1 = new DateTime('2009-08-12');
$date2 = new DateTime('2003-04-14');
$diff = $date1->diff($date2);
echo (($diff->format('%y') * 12) + $diff->format('%m')) . " full months difference";

After testing tons of solutions, putting all in a unit test, this is what I come out with:
/**
* Calculate the difference in months between two dates (v1 / 18.11.2013)
*
* #param \DateTime $date1
* #param \DateTime $date2
* #return int
*/
public static function diffInMonths(\DateTime $date1, \DateTime $date2)
{
$diff = $date1->diff($date2);
$months = $diff->y * 12 + $diff->m + $diff->d / 30;
return (int) round($months);
}
For example it will return (test cases from the unit test):
01.11.2013 - 30.11.2013 - 1 month
01.01.2013 - 31.12.2013 - 12 months
31.01.2011 - 28.02.2011 - 1 month
01.09.2009 - 01.05.2010 - 8 months
01.01.2013 - 31.03.2013 - 3 months
15.02.2013 - 15.04.2013 - 2 months
01.02.1985 - 31.12.2013 - 347 months
Notice: Because of the rounding it does with the days, even a half of a month will be rounded, which may lead to issue if you use it with some cases. So DO NOT USE it for such cases, it will cause you issues.
For example:
02.11.2013 - 31.12.2013 will return 2, not 1 (as expected).

I just wanted to add this if anyone is looking for a simple solution that counts each touched-upon month in stead of complete months, rounded months or something like that.
// Build example data
$timeStart = strtotime("2003-10-17");
$timeEnd = strtotime("2004-03-24");
// Adding current month + all months in each passed year
$numMonths = 1 + (date("Y",$timeEnd)-date("Y",$timeStart))*12;
// Add/subtract month difference
$numMonths += date("m",$timeEnd)-date("m",$timeStart);
echo $numMonths;

Wow, way to overthink the problem... How about this version:
function monthsBetween($startDate, $endDate) {
$retval = "";
// Assume YYYY-mm-dd - as is common MYSQL format
$splitStart = explode('-', $startDate);
$splitEnd = explode('-', $endDate);
if (is_array($splitStart) && is_array($splitEnd)) {
$difYears = $splitEnd[0] - $splitStart[0];
$difMonths = $splitEnd[1] - $splitStart[1];
$difDays = $splitEnd[2] - $splitStart[2];
$retval = ($difDays > 0) ? $difMonths : $difMonths - 1;
$retval += $difYears * 12;
}
return $retval;
}
NB: unlike several of the other solutions, this doesn't depend on the date functions added in PHP 5.3, since many shared hosts aren't there yet.

http://www.php.net/manual/en/datetime.diff.php
This returns a DateInterval object which has a format method.

$datetime1 = date_create('2009-10-11');
$datetime2 = date_create('2013-1-13');
$interval = date_diff($datetime1, $datetime2);
echo $interval->format('%a day %m month %y year');

// get year and month difference
$a1 = '20170401';
$a2 = '20160101'
$yearDiff = (substr($a1, 0, 4) - substr($a2, 0, 4));
$monthDiff = (substr($a1, 4, 2) - substr($a2, 4, 2));
$fullMonthDiff = ($yearDiff * 12) + $monthDiff;
// fullMonthDiff = 16

This is my enhanced version of #deceze answer:
/**
* #param string $startDate
* Current date is considered if empty string is passed
* #param string $endDate
* Current date is considered if empty string is passed
* #param bool $unsigned
* If $unsigned is true, difference is always positive, otherwise the difference might be negative
* #return int
*/
public static function diffInFullMonths($startDate, $endDate, $unsigned = false)
{
$diff = (new DateTime($startDate))->diff(new DateTime($endDate));
$reverse = $unsigned === true ? '' : '%r';
return ((int) $diff->format("{$reverse}%y") * 12) + ((int) $diff->format("{$reverse}%m"));
}

The best way.
function getIntervals(DateTime $from, DateTime $to)
{
$intervals = [];
$startDate = $from->modify('first day of this month');
$endDate = $to->modify('last day of this month');
while($startDate < $endDate){
$firstDay = $startDate->format('Y-m-d H:i:s');
$startDate->modify('last day of this month')->modify('+1 day');
$intervals[] = [
'firstDay' => $firstDay,
'lastDay' => $startDate->modify('-1 second')->format('Y-m-d H:i:s'),
];
$startDate->modify('+1 second');
}
return $intervals;
}
$dateTimeFirst = new \DateTime('2013-01-01');
$dateTimeSecond = new \DateTime('2013-03-31');
$interval = getIntervals($dateTimeFirst, $dateTimeSecond);
print_r($interval);
Result:
Array
(
[0] => Array
(
[firstDay] => 2013-01-01 00:00:00
[lastDay] => 2013-01-31 23:59:59
)
[1] => Array
(
[firstDay] => 2013-02-01 00:00:00
[lastDay] => 2013-02-28 23:59:59
)
[2] => Array
(
[firstDay] => 2013-03-01 00:00:00
[lastDay] => 2013-03-31 23:59:59
)
)

In my case I needed to count full months and day leftovers as month as well to build a line chart labels.
/**
* Calculate the difference in months between two dates
*
* #param \DateTime $from
* #param \DateTime $to
* #return int
*/
public static function diffInMonths(\DateTime $from, \DateTime $to)
{
// Count months from year and month diff
$diff = $to->diff($from)->format('%y') * 12 + $to->diff($from)->format('%m');
// If there is some day leftover, count it as the full month
if ($to->diff($from)->format('%d') > 0) $diff++;
// The month count isn't still right in some cases. This covers it.
if ($from->format('d') >= $to->format('d')) $diff++;
}

<?php
# end date is 2008 Oct. 11 00:00:00
$_endDate = mktime(0,0,0,11,10,2008);
# begin date is 2007 May 31 13:26:26
$_beginDate = mktime(13,26,26,05,31,2007);
$timestamp_diff= $_endDate-$_beginDate +1 ;
# how many days between those two date
$days_diff = $timestamp_diff/2635200;
?>
Reference: http://au.php.net/manual/en/function.mktime.php#86916

function monthsDif($start, $end)
{
// Assume YYYY-mm-dd - as is common MYSQL format
$splitStart = explode('-', $start);
$splitEnd = explode('-', $end);
if (is_array($splitStart) && is_array($splitEnd)) {
$startYear = $splitStart[0];
$startMonth = $splitStart[1];
$endYear = $splitEnd[0];
$endMonth = $splitEnd[1];
$difYears = $endYear - $startYear;
$difMonth = $endMonth - $startMonth;
if (0 == $difYears && 0 == $difMonth) { // month and year are same
return 0;
}
else if (0 == $difYears && $difMonth > 0) { // same year, dif months
return $difMonth;
}
else if (1 == $difYears) {
$startToEnd = 13 - $startMonth; // months remaining in start year(13 to include final month
return ($startToEnd + $endMonth); // above + end month date
}
else if ($difYears > 1) {
$startToEnd = 13 - $startMonth; // months remaining in start year
$yearsRemaing = $difYears - 2; // minus the years of the start and the end year
$remainingMonths = 12 * $yearsRemaing; // tally up remaining months
$totalMonths = $startToEnd + $remainingMonths + $endMonth; // Monthsleft + full years in between + months of last year
return $totalMonths;
}
}
else {
return false;
}
}

Here's a quick one:
$date1 = mktime(0,0,0,10,0,2003); // m d y, use 0 for day
$date2 = mktime(0,0,0,3,0,2004); // m d y, use 0 for day
echo round(($date2-$date1) / 60 / 60 / 24 / 30);

Related

How to make a code that finds sundays when date picked a start date and end date in PHP

I'm currently working on a project (membership system) with a start date and expiration date. For example the user chose the 30 days membership and his starting date is Sep, 05,2022.
So 09/05/2022 + 30 days is equals to expiration date.
But I want to skip sundays when adding the 30 days in the starting date.
How can I do that in PHP?
Edit: Sorry I'm a begginer, I tried all of your recommendation but it doesn't match to what I want. These are my code in computing the expiredate.
date_default_timezone_set('Asia/Manila');
$startDate = date('Y-m-d');
$many_days = 30;//the value of this comes from database but let's assume that its 30
$expireDate = date('Y-m-d', strtotime('+'.$many_days.' day'));//expiredate
I would use a loop. It's also a great start if you're going to extend that to holidays. Otherwise, it is an overkill but I really don't think you'd suffer performance issues.
So here we go:
$start_date = "2022-09-05";
$start = new DateTime($start_date);
$days = 30;
while ($days) {
// P1D means a period of 1 day
$start->add(new DateInterval('P1D'));
$day = $start->format('N');
// 7 = sunday
if ($day != 7) {
$days--;
}
}
print_r($start);
// output:
// [date] => 2022-10-10 00:00:00.000000
Full weeks should be pre-calculated for better performance. The following function allows you to name one or more days of the week that are not counted.
/**
* Add days without specific days of the week
*
* #param DateTime $startDate start Date
* #param int $days number of days
* #param array $withoutDays array with elements "Mon", "Tue", "Wed", "Thu", "Fri", "Sat","Sun"
* #return DateTime
*/
function addDaysWithout(DateTime $startDate ,int $days, array $withoutDays = []) : DateTime
{
//validate $withoutDays
$validWeekDays = 7 - count($withoutDays);
$validIdentifiers = ["Mon", "Tue", "Wed", "Thu", "Fri", "Sat","Sun"];
if($validWeekDays <= 0 OR
$withoutDays != array_intersect($withoutDays,$validIdentifiers)){
$msg = 'Invalid Argument "'.implode(',',$withoutDays).'" in withoutDays';
throw new \InvalidArgumentException($msg);
}
$start = clone $startDate;
$fullWeeks = (int)($days/$validWeekDays)-1;
if($fullWeeks > 0){
$start ->modify($fullWeeks.' weeks');
$days -= $fullWeeks * $validWeekDays;
}
while($days){
$start->modify('+1 Day');
if(!in_array($start->format('D'),$withoutDays)){
--$days;
}
}
return $start;
}
Application examples:
$start = date_create('2022-09-05');
$dt = addDaysWithout($start,30,["Sun"]);
var_dump($dt);
//object(DateTime)#3 (3) { ["date"]=> string(26) "2022-10-10 00:00:00.000000"
$start = date_create('2022-09-05');
$dt = addDaysWithout($start,30,["Mon","Sun"]);
var_dump($dt);
//object(DateTime)#3 (3) { ["date"]=> string(26) "2022-10-15
Demo: https://3v4l.org/AZX0E

Generate random dates with random times between two dates for selected period and frequency

I have to create a scheduling component that will plan e-mails that need to be sent out. Users can select a start time, end time, and frequency. Code should produce a random moment for every frequency, between start and end time. Outside of office hours.
Paramaters:
User can select a period between 01/01/2020 (the start) and 01/01/2021 (the end). In this case user selects a timespan of one exactly year.
User can select a frequency. In this case user selects '2 months'.
Function:
Code produces a list of datetimes. The total time (one year) is divided by frequency (2 months). We expect a list of 6 datetimes.
Every datetime is a random moment in said frequency (2 months). Within office hours.
Result:
An example result for these paramaters might as follows, with the calculated frequency bounds for clarity:
[jan/feb] 21-02-2020 11.36
[mrt/apr] 04-03-2020 16.11
[mei/jun] 13-05-2020 09.49
[jul-aug] 14-07-2020 15.25
[sep-okt] 02-09-2020 14.09
[nov-dec] 25-12-2020 13.55
--
I've been thinking about how to implement this best, but I can't figure out an elegant solution.
How could one do this using PHP?
Any insights, references, or code spikes would be greatly appreciated. I'm really stuck on this one.
I think you're just asking for suggestions on how to generate a list of repeating (2 weekly) dates with a random time between say 9am and 5pm? Is that right?
If so - something like this (untested, pseudo code) might be a starting point:
$start = new Datetime('1st January 2021');
$end = new Datetime('1st July 2021');
$day_start = 9;
$day_end = 17;
$date = $start;
$dates = [$date]; // Start date into array
while($date < $end) {
$new_date = clone($date->modify("+ 2 weeks"));
$new_date->setTime(mt_rand($day_start, $day_end), mt_rand(0, 59));
$dates[] = $new_date;
}
var_dump($dates);
Steve's anwser seems good, but you should consider 2 additional things
holiday check, in the while after first $new_date line, like:
$holiday = array('2021-01-01', '2021-01-06', '2021-12-25');
if (!in_array($new_date,$holiday))
also a check if date is a office day or a weekend in a similar way as above with working days as an array.
It's kind of crappy code but I think it will work as you wish.
function getDiffInSeconds(\DateTime $start, \DateTime $end) : int
{
$startTimestamp = $start->getTimestamp();
$endTimestamp = $end->getTimestamp();
return $endTimestamp - $startTimestamp;
}
function getShiftData(\DateTime $start, \DateTime $end) : array
{
$shiftStartHour = \DateTime::createFromFormat('H:i:s', $start->format('H:i:s'));
$shiftEndHour = \DateTime::createFromFormat('H:i:s', $end->format('H:i:s'));
$shiftInSeconds = intval($shiftEndHour->getTimestamp() - $shiftStartHour->getTimestamp());
return [
$shiftStartHour,
$shiftEndHour,
$shiftInSeconds,
];
}
function dayIsWeekendOrHoliday(\DateTime $date, array $holidays = []) : bool
{
$weekendDayIndexes = [
0 => 'Sunday',
6 => 'Saturday',
];
$dayOfWeek = $date->format('w');
if (empty($holidays)) {
$dayIsWeekendOrHoliday = isset($weekendDayIndexes[$dayOfWeek]);
} else {
$dayMonthDate = $date->format('d/m');
$dayMonthYearDate = $date->format('d/m/Y');
$dayIsWeekendOrHoliday = (isset($weekendDayIndexes[$dayOfWeek]) || isset($holidays[$dayMonthDate]) || isset($holidays[$dayMonthYearDate]));
}
return $dayIsWeekendOrHoliday;
}
function getScheduleDates(\DateTime $start, \DateTime $end, int $frequencyInSeconds) : array
{
if ($frequencyInSeconds < (24 * 60 * 60)) {
throw new \InvalidArgumentException('Frequency must be bigger than one day');
}
$diffInSeconds = getDiffInSeconds($start, $end);
// If difference between $start and $end is bigger than two days
if ($diffInSeconds > (2 * 24 * 60 * 60)) {
// If difference is bigger than 2 days we add 1 day to start and subtract 1 day from end
$start->modify('+1 day');
$end->modify('-1 day');
// Getting new $diffInSeconds after $start and $end changes
$diffInSeconds = getDiffInSeconds($start, $end);
}
if ($frequencyInSeconds > $diffInSeconds) {
throw new \InvalidArgumentException('Frequency is bigger than difference between dates');
}
$holidays = [
'01/01' => 'New Year',
'18/04/2020' => 'Easter 1st official holiday because 19/04/2020',
'20/04/2020' => 'Easter',
'21/04/2020' => 'Easter 2nd day',
'27/04' => 'Konings',
'04/05' => '4mei',
'05/05' => '4mei',
'24/12' => 'Christmas 1st day',
'25/12' => 'Christmas 2nd day',
'26/12' => 'Christmas 3nd day',
'27/12' => 'Christmas 3rd day',
'31/12' => 'Old Year'
];
[$shiftStartHour, $shiftEndHour, $shiftInSeconds] = getShiftData($start, $end);
$amountOfNotifications = floor($diffInSeconds / $frequencyInSeconds);
$periodInSeconds = intval($diffInSeconds / $amountOfNotifications);
$maxDaysBetweenNotifications = intval($periodInSeconds / (24 * 60 * 60));
// If $maxDaysBetweenNotifications is equals to 1 then we have to change $periodInSeconds to amount of seconds for one day
if ($maxDaysBetweenNotifications === 1) {
$periodInSeconds = (24 * 60 * 60);
}
$dates = [];
for ($i = 0; $i < $amountOfNotifications; $i++) {
$periodStart = clone $start;
$periodStart->setTimestamp($start->getTimestamp() + ($i * $periodInSeconds));
$seconds = mt_rand(0, $shiftInSeconds);
// If $maxDaysBetweenNotifications is equals to 1 then we have to check only one day without loop through the dates
if ($maxDaysBetweenNotifications === 1) {
$interval = new \DateInterval('P' . $maxDaysBetweenNotifications . 'DT' . $seconds . 'S');
$date = clone $periodStart;
$date->add($interval);
$dayIsWeekendOrHoliday = dayIsWeekendOrHoliday($date, $holidays);
} else {
// When $maxDaysBetweenNotifications we have to loop through the dates to pick them
$loopsCount = 0;
$maxLoops = 3; // Max loops before breaking and skipping the period
do {
$day = mt_rand(0, $maxDaysBetweenNotifications);
$periodStart->modify($shiftStartHour);
$interval = new \DateInterval('P' . $day . 'DT' . $seconds . 'S');
$date = clone $periodStart;
$date->add($interval);
$dayIsWeekendOrHoliday = dayIsWeekendOrHoliday($date, $holidays);
// If the day is weekend or holiday then we have to increment $loopsCount by 1 for each loop
if ($dayIsWeekendOrHoliday === true) {
$loopsCount++;
// If $loopsCount is equals to $maxLoops then we have to break the loop
if ($loopsCount === $maxLoops) {
break;
}
}
} while ($dayIsWeekendOrHoliday);
}
// Adds the date to $dates only if the day is not a weekend day and holiday
if ($dayIsWeekendOrHoliday === false) {
$dates[] = $date;
}
}
return $dates;
}
$start = new \DateTime('2020-12-30 08:00:00', new \DateTimeZone('Europe/Sofia'));
$end = new \DateTime('2021-01-18 17:00:00', new \DateTimeZone('Europe/Sofia'));
$frequencyInSeconds = 86400; // 1 day
$dates = getScheduleDates($start, $end, $frequencyInSeconds);
var_dump($dates);
You have to pass $start, $end and $frequencyInSeconds as I showed in example and then you will get your random dates. Notice that I $start and $end must have hours in them because they are used as start and end hours for shifts. Because the rule is to return a date within a shift time only in working days. Also you have to provide frequency in seconds - you can calculate them outside the function or you can change it to calculate them inside. I did it this way because I don't know what are your predefined periods.
This function returns an array of \DateTime() instances so you can do whatever you want with them.
UPDATE 08/01/2020:
Holidays now are part of calculation and they will be excluded from returned dates if they are passed when you are calling the function. You can pass them in d/m and d/m/Y formats because of holidays like Easter and in case when the holiday is on weekend but people will get additional dayoff during the working week.
UPDATE 13/01/2020:
I've made updated code version to fix the issue with infinite loops when $frequencyInSeconds is shorter like 1 day. The new code used few functions getDiffInSeconds, getShiftData and dayIsWeekendOrHoliday as helper methods to reduce code duplication and cleaner and more readable code

How to List financial years in a specific format between a start date and end date using php?

Financial years starts from April. So for example:
$startDate='2015-01-28' // Start date
$endDate ='2018-05-28' // End date
Now output I'm looking at is like this FY-14-15(as start date falls before apr2015),FY-15-16, FY-16-17, FY-17-18 , FY-18-19(as end date falls after apr 2018). This format I need to query db(MySql) to get some values which will be based on FY-Year1-Year2.
I have tried this so far..
$startDate='2015-01-28' // Start date
$endDate ='2017-05-28'
function calculateFinancialYears($startdate,$enddate){ // function calculates FY years
$d = date_parse_from_format("Y-m-d", $startdate);
$y1 = $d['year'];
if($d['month']<4){ //checking if startdate month falls before april
$fy1 = "FY-".($d['year']-2001)."-".(($d['year'])-2000);//concat FY- for desired Output
}else {
$fy1 = "FY-".(($d['year'])-2000)."-".($d['year']-1999);
}
echo $fy1;
$d2 = date_parse_from_format("Y-m-d", $enddate);
$y2 = $d2['year'];
if($d2['month']<4){
$fy2 = "FY-".($d2['year']-2001)."-".(($d2['year'])-2000);
}else {
$fy2 = "FY-".(($d2['year'])-2000)."-".($d2['year']-1999);
}
echo $fy2;
return $fy1; return $fy2;
out Put is FY-14-15 FY-16-17 .
My problem is, Missing Fiscal year FY-15-16. Also what i have tried is not a better code to get this for more number of years say startdate ='2015-01-28' and endDate ='2018-01-28',
How about this ?
function calcFY($startDate,$endDate) {
$prefix = 'FY-';
$ts1 = strtotime($startDate);
$ts2 = strtotime($endDate);
$year1 = date('Y', $ts1);
$year2 = date('Y', $ts2);
$month1 = date('m', $ts1);
$month2 = date('m', $ts2);
//get months
$diff = (($year2 - $year1) * 12) + ($month2 - $month1);
/**
* if end month is greater than april, consider the next FY
* else dont consider the next FY
*/
$total_years = ($month2 > 4)?ceil($diff/12):floor($diff/12);
$fy = array();
while($total_years >= 0) {
$prevyear = $year1 - 1;
//We dont need 20 of 20** (like 2014)
$fy[] = $prefix.substr($prevyear,-2).'-'.substr($year1,-2);
$year1 += 1;
$total_years--;
}
/**
* If start month is greater than or equal to april,
* remove the first element
*/
if($month1 >= 4) {
unset($fy[0]);
}
/* Concatenate the array with ',' */
return implode(',',$fy);
}
Output:
echo calcFY('2015-03-28','2018-05-28');
//FY-14-15,FY-15-16,FY-16-17,FY-17-18,FY-18-19
/ was missing before year
<?php
/*
* AUTHOR: http://www.tutorius.com/calculating-a-fiscal-year-in-php
*
* This function figures out what fiscal year a specified date is in.
* $inputDate - the date you wish to find the fiscal year for. (12/4/08)
* $fyStartDate - the month and day your fiscal year starts. (7/1)
* $fyEndDate - the month and day your fiscal year ends. (6/30)
* $fy - returns the correct fiscal year
*/
function calculateFiscalYearForDate($inputDate, $fyStart, $fyEnd){
$date = strtotime($inputDate);
$inputyear = strftime('%Y',$date);
$fystartdate = strtotime("$fyStart/$inputyear");
$fyenddate = strtotime("$fyEnd/$inputyear");
if($date < $fyenddate){
$fy = intval($inputyear);
}else{
$fy = intval(intval($inputyear) + 1);
}
return $fy;
}
// my fiscal year starts on July,1 and ends on June 30, so...
echo calculateFiscalYearForDate("5/15/08","7/1","6/30")."\n";
// returns 2008
echo calculateFiscalYearForDate("12/1/08","7/1","6/30")."\n";
// returns 2009
?>

date minus date and take difference [duplicate]

This question already has answers here:
Elegant way to get the count of months between two dates?
(10 answers)
Closed 3 years ago.
Is there any way to find the month difference in PHP? I have the input of from-date 2003-10-17 and to-date 2004-03-24. I need to find how many months there are within these two days. Say if 6 months, I need the output in months only. Thanks for guiding me for day difference.
I find the solution through MySQL but I need it in PHP. Anyone help me, Thanks in advance.
The easiest way without reinventing the wheel. This'll give you the full months difference. I.e. the below two dates are almost 76 months apart, but the result is 75 months.
date_default_timezone_set('Asia/Tokyo'); // you are required to set a timezone
$date1 = new DateTime('2009-08-12');
$date2 = new DateTime('2003-04-14');
$diff = $date1->diff($date2);
echo (($diff->format('%y') * 12) + $diff->format('%m')) . " full months difference";
After testing tons of solutions, putting all in a unit test, this is what I come out with:
/**
* Calculate the difference in months between two dates (v1 / 18.11.2013)
*
* #param \DateTime $date1
* #param \DateTime $date2
* #return int
*/
public static function diffInMonths(\DateTime $date1, \DateTime $date2)
{
$diff = $date1->diff($date2);
$months = $diff->y * 12 + $diff->m + $diff->d / 30;
return (int) round($months);
}
For example it will return (test cases from the unit test):
01.11.2013 - 30.11.2013 - 1 month
01.01.2013 - 31.12.2013 - 12 months
31.01.2011 - 28.02.2011 - 1 month
01.09.2009 - 01.05.2010 - 8 months
01.01.2013 - 31.03.2013 - 3 months
15.02.2013 - 15.04.2013 - 2 months
01.02.1985 - 31.12.2013 - 347 months
Notice: Because of the rounding it does with the days, even a half of a month will be rounded, which may lead to issue if you use it with some cases. So DO NOT USE it for such cases, it will cause you issues.
For example:
02.11.2013 - 31.12.2013 will return 2, not 1 (as expected).
I just wanted to add this if anyone is looking for a simple solution that counts each touched-upon month in stead of complete months, rounded months or something like that.
// Build example data
$timeStart = strtotime("2003-10-17");
$timeEnd = strtotime("2004-03-24");
// Adding current month + all months in each passed year
$numMonths = 1 + (date("Y",$timeEnd)-date("Y",$timeStart))*12;
// Add/subtract month difference
$numMonths += date("m",$timeEnd)-date("m",$timeStart);
echo $numMonths;
Wow, way to overthink the problem... How about this version:
function monthsBetween($startDate, $endDate) {
$retval = "";
// Assume YYYY-mm-dd - as is common MYSQL format
$splitStart = explode('-', $startDate);
$splitEnd = explode('-', $endDate);
if (is_array($splitStart) && is_array($splitEnd)) {
$difYears = $splitEnd[0] - $splitStart[0];
$difMonths = $splitEnd[1] - $splitStart[1];
$difDays = $splitEnd[2] - $splitStart[2];
$retval = ($difDays > 0) ? $difMonths : $difMonths - 1;
$retval += $difYears * 12;
}
return $retval;
}
NB: unlike several of the other solutions, this doesn't depend on the date functions added in PHP 5.3, since many shared hosts aren't there yet.
http://www.php.net/manual/en/datetime.diff.php
This returns a DateInterval object which has a format method.
$datetime1 = date_create('2009-10-11');
$datetime2 = date_create('2013-1-13');
$interval = date_diff($datetime1, $datetime2);
echo $interval->format('%a day %m month %y year');
// get year and month difference
$a1 = '20170401';
$a2 = '20160101'
$yearDiff = (substr($a1, 0, 4) - substr($a2, 0, 4));
$monthDiff = (substr($a1, 4, 2) - substr($a2, 4, 2));
$fullMonthDiff = ($yearDiff * 12) + $monthDiff;
// fullMonthDiff = 16
This is my enhanced version of #deceze answer:
/**
* #param string $startDate
* Current date is considered if empty string is passed
* #param string $endDate
* Current date is considered if empty string is passed
* #param bool $unsigned
* If $unsigned is true, difference is always positive, otherwise the difference might be negative
* #return int
*/
public static function diffInFullMonths($startDate, $endDate, $unsigned = false)
{
$diff = (new DateTime($startDate))->diff(new DateTime($endDate));
$reverse = $unsigned === true ? '' : '%r';
return ((int) $diff->format("{$reverse}%y") * 12) + ((int) $diff->format("{$reverse}%m"));
}
The best way.
function getIntervals(DateTime $from, DateTime $to)
{
$intervals = [];
$startDate = $from->modify('first day of this month');
$endDate = $to->modify('last day of this month');
while($startDate < $endDate){
$firstDay = $startDate->format('Y-m-d H:i:s');
$startDate->modify('last day of this month')->modify('+1 day');
$intervals[] = [
'firstDay' => $firstDay,
'lastDay' => $startDate->modify('-1 second')->format('Y-m-d H:i:s'),
];
$startDate->modify('+1 second');
}
return $intervals;
}
$dateTimeFirst = new \DateTime('2013-01-01');
$dateTimeSecond = new \DateTime('2013-03-31');
$interval = getIntervals($dateTimeFirst, $dateTimeSecond);
print_r($interval);
Result:
Array
(
[0] => Array
(
[firstDay] => 2013-01-01 00:00:00
[lastDay] => 2013-01-31 23:59:59
)
[1] => Array
(
[firstDay] => 2013-02-01 00:00:00
[lastDay] => 2013-02-28 23:59:59
)
[2] => Array
(
[firstDay] => 2013-03-01 00:00:00
[lastDay] => 2013-03-31 23:59:59
)
)
In my case I needed to count full months and day leftovers as month as well to build a line chart labels.
/**
* Calculate the difference in months between two dates
*
* #param \DateTime $from
* #param \DateTime $to
* #return int
*/
public static function diffInMonths(\DateTime $from, \DateTime $to)
{
// Count months from year and month diff
$diff = $to->diff($from)->format('%y') * 12 + $to->diff($from)->format('%m');
// If there is some day leftover, count it as the full month
if ($to->diff($from)->format('%d') > 0) $diff++;
// The month count isn't still right in some cases. This covers it.
if ($from->format('d') >= $to->format('d')) $diff++;
}
<?php
# end date is 2008 Oct. 11 00:00:00
$_endDate = mktime(0,0,0,11,10,2008);
# begin date is 2007 May 31 13:26:26
$_beginDate = mktime(13,26,26,05,31,2007);
$timestamp_diff= $_endDate-$_beginDate +1 ;
# how many days between those two date
$days_diff = $timestamp_diff/2635200;
?>
Reference: http://au.php.net/manual/en/function.mktime.php#86916
function monthsDif($start, $end)
{
// Assume YYYY-mm-dd - as is common MYSQL format
$splitStart = explode('-', $start);
$splitEnd = explode('-', $end);
if (is_array($splitStart) && is_array($splitEnd)) {
$startYear = $splitStart[0];
$startMonth = $splitStart[1];
$endYear = $splitEnd[0];
$endMonth = $splitEnd[1];
$difYears = $endYear - $startYear;
$difMonth = $endMonth - $startMonth;
if (0 == $difYears && 0 == $difMonth) { // month and year are same
return 0;
}
else if (0 == $difYears && $difMonth > 0) { // same year, dif months
return $difMonth;
}
else if (1 == $difYears) {
$startToEnd = 13 - $startMonth; // months remaining in start year(13 to include final month
return ($startToEnd + $endMonth); // above + end month date
}
else if ($difYears > 1) {
$startToEnd = 13 - $startMonth; // months remaining in start year
$yearsRemaing = $difYears - 2; // minus the years of the start and the end year
$remainingMonths = 12 * $yearsRemaing; // tally up remaining months
$totalMonths = $startToEnd + $remainingMonths + $endMonth; // Monthsleft + full years in between + months of last year
return $totalMonths;
}
}
else {
return false;
}
}
Here's a quick one:
$date1 = mktime(0,0,0,10,0,2003); // m d y, use 0 for day
$date2 = mktime(0,0,0,3,0,2004); // m d y, use 0 for day
echo round(($date2-$date1) / 60 / 60 / 24 / 30);

PHP strtotime +1 month behaviour

I know about the unwanted behaviour of PHP's function
strtotime
For example, when adding a month (+1 month) to dates like: 31.01.2011 -> 03.03.2011
I know it's not officially a PHP bug, and that this solution has some arguments behind it, but at least for me, this behavior has caused a lot waste of time (in the past and present) and I personally hate it.
What I found even stranger is that for example in:
MySQL: DATE_ADD('2011-01-31', INTERVAL 1 MONTH) returns 2011-02-28
or
C# where new DateTime(2011, 01, 31).AddMonths(1); will return 28.02.2011
wolframalpha.com giving 31.01.2013 + 1 month as input; will return Thursday, February 28, 2013
It sees to me that others have found a more decent solution to the stupid question that I saw alot in PHP bug reports "what day will it be, if I say we meet in a month from now" or something like that. The answer is: if 31 does not exists in next month, get me the last day of that month, but please stick to next month.
So MY QUESTION IS: is there a PHP function (written by somebody) that resolves this not officially recognized bug? As I don't think I am the only one who wants another behavior when adding / subtracting months.
I am particulary interested in solutions what also work not just for the end of the month, but a complete replacement of strtotime. Also the case strotime +n months should be also dealt with.
Happy coding!
what you need is to tell PHP to be smarter
$the_date = strtotime('31.01.2011');
echo date('r', strtotime('last day of next month', $the_date));
$the_date = strtotime('31.03.2011');
echo date('r', strtotime('last day of next month', $the_date));
assuming you are only interesting on the last day of next month
reference - http://www.php.net/manual/en/datetime.formats.relative.php
PHP devs surely don't consider this as bug. But in strtotime's docs there are few comments with solutions for your problem (look for 28th Feb examples ;)), i.e. this one extending DateTime class:
<?php
// this will give us 2010-02-28 ()
echo PHPDateTime::DateNextMonth(strftime('%F', strtotime("2010-01-31 00:00:00")), 31);
?>
Class PHPDateTime:
<?php
/**
* IA FrameWork
* #package: Classes & Object Oriented Programming
* #subpackage: Date & Time Manipulation
* #author: ItsAsh <ash at itsash dot co dot uk>
*/
final class PHPDateTime extends DateTime {
// Public Methods
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
/**
* Calculate time difference between two dates
* ...
*/
public static function TimeDifference($date1, $date2)
$date1 = is_int($date1) ? $date1 : strtotime($date1);
$date2 = is_int($date2) ? $date2 : strtotime($date2);
if (($date1 !== false) && ($date2 !== false)) {
if ($date2 >= $date1) {
$diff = ($date2 - $date1);
if ($days = intval((floor($diff / 86400))))
$diff %= 86400;
if ($hours = intval((floor($diff / 3600))))
$diff %= 3600;
if ($minutes = intval((floor($diff / 60))))
$diff %= 60;
return array($days, $hours, $minutes, intval($diff));
}
}
return false;
}
/**
* Formatted time difference between two dates
*
* ...
*/
public static function StringTimeDifference($date1, $date2) {
$i = array();
list($d, $h, $m, $s) = (array) self::TimeDifference($date1, $date2);
if ($d > 0)
$i[] = sprintf('%d Days', $d);
if ($h > 0)
$i[] = sprintf('%d Hours', $h);
if (($d == 0) && ($m > 0))
$i[] = sprintf('%d Minutes', $m);
if (($h == 0) && ($s > 0))
$i[] = sprintf('%d Seconds', $s);
return count($i) ? implode(' ', $i) : 'Just Now';
}
/**
* Calculate the date next month
*
* ...
*/
public static function DateNextMonth($now, $date = 0) {
$mdate = array(0, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31);
list($y, $m, $d) = explode('-', (is_int($now) ? strftime('%F', $now) : $now));
if ($date)
$d = $date;
if (++$m == 2)
$d = (($y % 4) === 0) ? (($d <= 29) ? $d : 29) : (($d <= 28) ? $d : 28);
else
$d = ($d <= $mdate[$m]) ? $d : $mdate[$m];
return strftime('%F', mktime(0, 0, 0, $m, $d, $y));
}
}
?>
Here's the algorithm you can use. It should be simple enough to implement yourself.
Have the original date and the +1 month date in variables
Extract the month part of both variables
If the difference is greater than 1 month (or if the original is December and the other is not January) change the latter variable to the last day of the next month. You can use for example t in date() to get the last day: date( 't.m.Y' )
Had the same issue recently and ended up writing a class that handles adding/subtracting various time intervals to DateTime objects.
Here's the code:
https://gist.github.com/pavlepredic/6220041#file-gistfile1-php
I've been using this class for a while and it seems to work fine, but I'm really interested in some peer review. What you do is create a TimeInterval object (in your case, you would specify 1 month as the interval) and then call addToDate() method, making sure you set $preventMonthOverflow argument to true. The code will make sure that the resulting date does not overflow into next month.
Sample usage:
$int = new TimeInterval(1, TimeInterval::MONTH);
$date = date_create('2013-01-31');
$future = $int->addToDate($date, true);
echo $future->format('Y-m-d');
Resulting date is:
2013-02-28
Here is an implementation of an improved version of Juhana's answer above:
<?php
function sameDateNextMonth(DateTime $createdDate, DateTime $currentDate) {
$addMon = clone $currentDate;
$addMon->add(new DateInterval("P1M"));
$nextMon = clone $currentDate;
$nextMon->modify("last day of next month");
if ($addMon->format("n") == $nextMon->format("n")) {
$recurDay = $createdDate->format("j");
$daysInMon = $addMon->format("t");
$currentDay = $currentDate->format("j");
if ($recurDay > $currentDay && $recurDay <= $daysInMon) {
$addMon->setDate($addMon->format("Y"), $addMon->format("n"), $recurDay);
}
return $addMon;
} else {
return $nextMon;
}
}
This version takes $createdDate under the presumption that you are dealing with a recurring monthly period, such as a subscription, that started on a specific date, such as the 31st. It always takes $createdDate so late "recurs on" dates won't shift to lower values as they are pushed forward thru lesser-valued months (e.g., so all 29th, 30th or 31st recur dates won't eventually get stuck on the 28th after passing thru a non-leap-year February).
Here is some driver code to test the algorithm:
$createdDate = new DateTime("2015-03-31");
echo "created date = " . $createdDate->format("Y-m-d") . PHP_EOL;
$next = sameDateNextMonth($createdDate, $createdDate);
echo " next date = " . $next->format("Y-m-d") . PHP_EOL;
foreach(range(1, 12) as $i) {
$next = sameDateNextMonth($createdDate, $next);
echo " next date = " . $next->format("Y-m-d") . PHP_EOL;
}
Which outputs:
created date = 2015-03-31
next date = 2015-04-30
next date = 2015-05-31
next date = 2015-06-30
next date = 2015-07-31
next date = 2015-08-31
next date = 2015-09-30
next date = 2015-10-31
next date = 2015-11-30
next date = 2015-12-31
next date = 2016-01-31
next date = 2016-02-29
next date = 2016-03-31
next date = 2016-04-30
I have solved it by this way:
$startDate = date("Y-m-d");
$month = date("m",strtotime($startDate));
$nextmonth = date("m",strtotime("$startDate +1 month"));
if((($nextmonth-$month) > 1) || ($month == 12 && $nextmonth != 1))
{
$nextDate = date( 't.m.Y',strtotime("$initialDate +1 week"));
}else
{
$nextDate = date("Y-m-d",strtotime("$initialDate +1 month"));
}
echo $nextDate;
Somewhat similar to the Juhana's answer but more intuitive and less complications expected. Idea is like this:
Store original date and the +n month(s) date in variables
Extract the day part of both variables
If days do not match, subtract number of days from the future date
Plus side of this solution is that works for any date (not just the border dates) and it also works for subtracting months (by putting - instead of +).
Here is an example implementation:
$start = mktime(0,0,0,1,31,2015);
for ($contract = 0; $contract < 12; $contract++) {
$end = strtotime('+ ' . $contract . ' months', $start);
if (date('d', $start) != date('d', $end)) {
$end = strtotime('- ' . date('d', $end) . ' days', $end);
}
echo date('d-m-Y', $end) . '|';
}
And the output is following:
31-01-2015|28-02-2015|31-03-2015|30-04-2015|31-05-2015|30-06-2015|31-07-2015|31-08-2015|30-09-2015|31-10-2015|30-11-2015|31-12-2015|
function ldom($m,$y){
//return tha last date of a given month based on the month and the year
//(factors in leap years)
$first_day= strtotime (date($m.'/1/'.$y));
$next_month = date('m',strtotime ( '+32 day' , $first_day)) ;
$last_day= strtotime ( '-1 day' , strtotime (date($next_month.'/1/'.$y)) ) ;
return $last_day;
}

Categories