I need to get difference between months, independently of days.
if i do this:
$todaStart = new DateTime("2015-06-30");
$dateEnd = new DateTime("2015-07-01");
$diff = $todaStart->diff($dateEnd);
var_dump($diff);
object(DateInterval)[3]
public 'y' => int 0
public 'm' => int 0
public 'd' => int 1
public 'h' => int 0
public 'i' => int 0
public 's' => int 0
[...]
the "m" parameter is 0, but the month changed, so for the counts that i need to do, i need that the result be 1
The same happens if i change the month to "08", it should count 2 month difference, but the response come as 1 month.
Digging on the web i found on this site this solution, hope it help others.
$d1= new DateTime("2015-06-01");
$d2= new DateTime("2015-07-30");
$y1 = $d1->format('Y');
$m1 = $d1->format('m');
$y2 = $d2->format('Y');
$m2 = $d2->format('m');
$diff = (($y2 - $y1) * 12) + ($m2 - $m1);
// var_dump($diff) output 1
Related
I'm trying to get the difference between two dates like this
[
'years' : 4, // 0 if the difference is not above a year
'months': 4, // 0 if the difference is not of above a month
'weeks': 4, // 0 if the difference is not of above a week
'days': 4, // 0 if the difference is not of above a day
'hours' : 4 // 0 if the difference is not of above a hour
'minutes': 54 // 0 if the difference is not of above a minute
'seconds': 5 // 0 if the difference is not of above a second
]
Is there any utility function that gives me an output something like above in laravel PHP
this is my code at the moment
$date1 = new Carbon('2018-08-18 11:09:12');
$date2 = new Carbon('2018-04-02 08:15:03');
// dd($date1->diffForHumans($date2, false, false, 6));
$p = $date2->diffForHumans($date1, false, false, 6);
You could use the diffAsCarbonInterval()
$p = $date2->diffAsCarbonInterval($date1);
Then you can access the above values with:
$p->years //year
$p->months //month
$p->weeks //week
$p->daysExcludeWeeks //day
$p->hours //hour
$p->minutes //minute
$p->seconds //second
Or to take it one step further you could create a macro. One way to do this would be to add the following to the register method of your app service provider:
\Carbon\Carbon::macro('diffAsArray', function ($date = null, $absolute = true) {
$interval = $this->diffAsCarbonInterval($date, $absolute);
return [
'year' => $interval->years,
'month' => $interval->months,
'week' => $interval->weeks,
'day' => $interval->daysExcludeWeeks,
'hour' => $interval->hours,
'minute' => $interval->minutes,
'second' => $interval->seconds,
];
});
Then you can call:
$p = $date2->diffAsArray($date1);
Obviously, feel free to change the method name of the macro to something else if you want to.
I have recently been using Carbon to display humanized time strings, but for some reason I can only get it to show the main factor, for example, I have a date that it needs to show how long until that date is. So for example, if its 6 days, 4 hours and 32 minutes away from now, it currently only displays '6 days'.
How would I go about getting it to display the hours too, and possibly the minutes? It's kind of horrible looking when it only gives you the days, and many people may want to know more like the hours and seconds?
I can't find anything on the Carbon documentation for this. I am using it inside a laravel 5.3 view if that's even relevant.
Heres my code:
{{ \Carbon\Carbon::createFromTimeStamp(strtotime($item->created_at))->diffForHumans() }}
You shouldn't use diffForHumans()in this case, because it returns only a string on which one can no longer work. This is an end result.
It is better to work with a Carbon object, like this :
$created = \Carbon\Carbon::createFromTimeStamp(strtotime($item->created_at));
And then, add this in the view :
{{ $created ->diff(\Carbon\Carbon::now())->format('%d days, %h hours and %i minutes'); }}
You can extend for a larger period :
$created ->diff(\Carbon\Carbon::now())->format('%y year, %m months, %d days, %h hours and %i minutes');
EDIT (according to the comments) :
If you do :
$diff = $created->diff(Carbon::now());
var_dump($diff);
You get this result :
object(DateInterval)[113]
public 'y' => int 0
public 'm' => int 0
public 'd' => int 6
public 'h' => int 4
public 'i' => int 32
public 's' => int 29
public 'f' => float 0.397424
public 'weekday' => int 0
public 'weekday_behavior' => int 0
public 'first_last_day_of' => int 0
public 'invert' => int 0
public 'days' => int 6
public 'special_type' => int 0
public 'special_amount' => int 0
public 'have_weekday_relative' => int 0
public 'have_special_relative' => int 0
// results depend of the current time
From there, you browse the elements to create the best answer to your needs.
Show only Hours:Minutes (11:11)
To display only Hours:Minutes you can use:
$curr = \Carbon\Carbon::now()->format('H:i');
echo $curr; // 11:11
If you have to set a timezone you have to use the timezone function. You can pass to these function your timezone timezone('Your/Zone')
$curr = `\Carbon\Carbon::now()->timezone('Europe/Amsterdam')->format('H:i');
echo $curr; // 12:11
I'm having a hell of a time trying to solve the following problem:
It's a calendar program where given a set of available datetime sets from multiple people, I need to figure out what datetime ranges everyone is available in PHP
Availability Sets:
p1: start: "2016-04-30 12:00", end: "2016-05-01 03:00"
p2: start: "2016-04-30 03:00", end: "2016-05-01 03:00"
p3: start: "2016-04-30 03:00", end: "2016-04-30 13:31"
start: "2016-04-30 15:26", end: "2016-05-01 03:00"
I'm looking for a function that I can call that will tell me what datetime ranges all (p) people are available at the same time.
In the above example the answer should be:
2016-04-30 12:00 -> 2016-04-30 13:31
2016-04-30 15:26 -> 2016-05-01 03:00
I did find this similar question and answer
Datetime -Determine whether multiple(n) datetime ranges overlap each other in R
But I have no idea what language that is, and have to unable to translate the logic in the answer.
Well that was fun. There's probably a more elegant way of doing this than looping over every minute, but I don't know if PHP is the language for it. Note that this currently needs to manage the start and end times to search separately, although it would be fairly trivial to calculate them based on the available shifts.
<?php
$availability = [
'Alex' => [
[
'start' => new DateTime('2016-04-30 12:00'),
'end' => new DateTime('2016-05-01 03:00'),
],
],
'Ben' => [
[
'start' => new DateTime('2016-04-30 03:00'),
'end' => new DateTime('2016-05-01 03:00'),
],
],
'Chris' => [
[
'start' => new DateTime('2016-04-30 03:00'),
'end' => new DateTime('2016-04-30 13:31')
],
[
'start' => new DateTime('2016-04-30 15:26'),
'end' => new DateTime('2016-05-01 03:00')
],
],
];
$start = new DateTime('2016-04-30 00:00');
$end = new DateTime('2016-05-01 23:59');
$tick = DateInterval::createFromDateString('1 minute');
$period = new DatePeriod($start, $tick, $end);
$overlaps = [];
$overlapStart = $overlapUntil = null;
foreach ($period as $minute)
{
$peopleAvailable = 0;
// Find out how many people are available for the current minute
foreach ($availability as $name => $shifts)
{
foreach ($shifts as $shift)
{
if ($shift['start'] <= $minute && $shift['end'] >= $minute)
{
// If any shift matches, this person is available
$peopleAvailable++;
break;
}
}
}
// If everyone is available...
if ($peopleAvailable == count($availability))
{
// ... either start a new period...
if (!$overlapStart)
{
$overlapStart = $minute;
}
// ... or track an existing one
else
{
$overlapUntil = $minute;
}
}
// If not and we were previously in a period of overlap, end it
elseif ($overlapStart)
{
$overlaps[] = [
'start' => $overlapStart,
'end' => $overlapUntil,
];
$overlapStart = null;
}
}
foreach ($overlaps as $overlap)
{
echo $overlap['start']->format('Y-m-d H:i:s'), ' -> ', $overlap['end']->format('Y-m-d H:i:s'), PHP_EOL;
}
There are some bugs with this implementation, see the comments. I'm unable to delete it as it's the accepted answer. Please use iainn or fusion3k's very good answers until I get around to fixing it.
There's actually no need to use any date/time handling to solve this
problem. You can exploit the fact that dates in this format are in alphabetical as well as chronological order.
I'm not sure this makes the solution any less complex. It's probably less
readable this way. But it's considerably faster than iterating over every minute so you might choose it if performance is a concern.
You also get to use
every
single
array
function
out there, which is nice.
Of course, because I haven't used any date/time functions, it might not work if Daylight Savings Time or users in different time zones need dealing with.
$availability = [
[
["2016-04-30 12:00", "2016-05-01 03:00"]
],
[
["2016-04-30 03:00", "2016-05-01 03:00"]
],
[
["2016-04-30 03:00", "2016-04-30 13:31"],
["2016-04-30 15:26", "2016-05-01 03:00"]
]
];
// Placeholder array to contain the periods when everyone is available.
$periods = [];
// Loop until one of the people has no periods left.
while (count($availability) &&
count(array_filter($availability)) == count($availability)) {
// Select every person's earliest date, then choose the latest of these
// dates.
$start = array_reduce($availability, function($carry, $ranges) {
$start = array_reduce($ranges, function($carry, $range) {
// This person's earliest start date.
return !$carry ? $range[0] : min($range[0], $carry);
});
// The latest of all the start dates.
return !$carry ? $start : max($start, $carry);
});
// Select each person's range which contains this date.
$matching_ranges = array_filter(array_map(function($ranges) use($start) {
return current(array_filter($ranges, function($range) use($start) {
// The range starts before and ends after the start date.
return $range[0] <= $start && $range[1] >= $start;
}));
}, $availability));
// Find the earliest of the ranges' end dates, and this completes our
// first period that everyone can attend.
$end = array_reduce($matching_ranges, function($carry, $range) {
return !$carry ? $range[1] : min($range[1], $carry);
});
// Add it to our list of periods.
$periods[] = [$start, $end];
// Remove any availability periods which finish before the end of this
// new period.
array_walk($availability, function(&$ranges) use ($end) {
$ranges = array_filter($ranges, function($range) use($end) {
return $range[1] > $end;
});
});
}
// Output the answer in the specified format.
foreach ($periods as $period) {
echo "$period[0] -> $period[1]\n";
}
/**
* Output:
*
* 2016-04-30 12:00 -> 2016-04-30 13:31
* 2016-04-30 15:26 -> 2016-05-01 03:00
*/
A different approach to your question is to use bitwise operators. The benefits of this solution are memory usage, speed and short code. The handicap is that — in your case — we can not use php integer, because we work with large numbers (1 day in minutes is 224*60), so we have to use GMP Extension, that is not available by default in most php distribution. However, if you use apt-get or any other packages manager, the installation is very simple.
To better understand my approach, I will use an array with a total period of 30 minutes to simplify binary representation:
$calendar =
[
'p1' => [
['start' => '2016-04-30 12:00', 'end' => '2016-04-30 12:28']
],
'p2' => [
['start' => '2016-04-30 12:10', 'end' => '2016-04-30 12:16'],
['start' => '2016-04-30 12:22', 'end' => '2016-05-01 12:30']
]
];
First of all, we find min and max dates of all array elements, then we init the free (time) variable with the difference in minutes between max and min. In above example (30 minutes), we obtain 230-20=1,073,741,823, that is a binary with 30 ‘1’ (or with 30 bits set):
111111111111111111111111111111
Now, for each person, we create the corresponding free-time variable with the same method. For the first person is easy (we have only one time interval): the difference between start and min is 0, the difference between end and min is 28, so we have 228-20=268435455, that is:
001111111111111111111111111111
At this point, we update global free time with a AND bitwise operation between global free time itself and person free time. The OR operator set bits if they are set in both compared values:
111111111111111111111111111111 global free time
001111111111111111111111111111 person free time
==============================
001111111111111111111111111111 new global free time
For the second person, we have two time intervals: we calculate each time interval with know method, then we compone global person free time using OR operator, that set bits if they are set in either first or second value:
000000000000001111110000000000 12:10 - 12:16
111111110000000000000000000000 12:22 - 12:30
==============================
111111110000001111110000000000 person total free time
Now we update global free time with the same method used for first person (AND operator):
001111111111111111111111111111 previous global free time
111111110000001111110000000000 person total free time
==============================
001111110000001111110000000000 new global free time
└────┘ └────┘
:28-:22 :16-:10
As you can see, at the end we have an integer with bits set only in minutes when everyone is available (you have to count starting from right). Now, you can convert back this integer to datetimes. Fortunately, GMP extension has a method to find 1/0 offset, so we can avoid to perform a for/foreach loop through all digits (that in real case are many more than 30).
Let's see the complete code to apply this concept to your array:
$calendar =
[
'p1' => [
['start' => '2016-04-30 12:00', 'end' => '2016-05-01 03:00']
],
'p2' => [
['start' => '2016-04-30 03:00', 'end' => '2016-05-01 03:00']
],
'p3' => [
['start' => '2016-04-30 03:00', 'end' => '2016-04-30 13:31'],
['start' => '2016-04-30 15:26', 'end' => '2016-05-01 03:00']
]
];
/* Get active TimeZone, then calculate min and max dates in minutes: */
$tz = new DateTimeZone( date_default_timezone_get() );
$flat = call_user_func_array( 'array_merge', $calendar );
$min = date_create( min( array_column( $flat, 'start' ) ) )->getTimestamp()/60;
$max = date_create( max( array_column( $flat, 'end' ) ) )->getTimestamp()/60;
/* Init global free time (initially all-free): */
$free = gmp_sub( gmp_pow( 2, $max-$min ), gmp_pow( 2, 0 ) );
/* Process free time(s) for each person: */
foreach( $calendar as $p )
{
$pf = gmp_init( 0 );
foreach( $p as $time )
{
$start = date_create( $time['start'] )->getTimestamp()/60;
$end = date_create( $time['end'] )->getTimestamp()/60;
$pf = gmp_or( $pf, gmp_sub( gmp_pow( 2, $end-$min ), gmp_pow( 2, $start-$min ) ) );
}
$free = gmp_and( $free, $pf );
}
$result = [];
$start = $end = 0;
/* Create resulting array: */
while( ($start = gmp_scan1( $free, $end )) >= 0 )
{
$end = gmp_scan0( $free, $start );
if( $end === False) $end = strlen( gmp_strval( $free, 2 ) )-1;
$result[] =
[
'start' => date_create( '#'.($start+$min)*60 )->setTimezone( $tz )->format( 'Y-m-d H:i:s' ),
'end' => date_create( '#'.($end+$min)*60 )->setTimezone( $tz )->format( 'Y-m-d H:i:s' )
];
}
print_r( $result );
Output:
Array
(
[0] => Array
(
[start] => 2016-04-30 12:00:00
[end] => 2016-04-30 13:31:00
)
[1] => Array
(
[start] => 2016-04-30 15:26:00
[end] => 2016-05-01 03:00:00
)
)
3v4l.org demo
Some additional notes:
At the start, we set $tz to current timezone: we will use it later, at the end, when we create final dates from timestamps. Dates created from timestamps are in UTC, so we have to set correct timezone.
To retrieve initial $min and $max values in minutes, firstly we flat original array, then we retrieve min and max date using array_column.
gmp_sub subtract second argument from first argument, gmp_pow raise number (arg 1) into power (arg 2).
In the final while loop, we use gmp_scan1 and gmp_scan0 to retrieve each ‘111....’ interval, then we create returning array elements using gmp_scan1 position for start key and gmp_scan0 position for end key.
i am trying to figure out how to get total Months in between two overlapping date Ranges.
e-g
Date Range from Date-A to Date-B overlapping the Date range of Date-X and Date-Y.
_ Start|Jan - Feb - March - April ------ Nov - Dec|End (DateRange A)
Start|Jan - Feb ---- Dec - Jan - Feb|End _
In two date ranges the Jan and Feb Months are colliding. Means Total of 2 Months are there.
So i want these two months in my array so i can apply different functions on it.
e-g i have these two dates
$dateRange1Start = "2015-07-01";
$dateRange1End = "2014-06-30";
=-=-=-=====-=-==
$dateRange2Start = "2012-02-01";
$dateRange2End = "2014-12-31";
There are total 6 Months in colliding between these two date ranges. i want to get these 6 Months.
I tried to search for help in google, but mostly i get less than or greater than signs. but specifically like this. I tried to implement my own logic but its not getting me anywhere, sadly to only more problems :(
i am trying to get results like this
$collidingDates = array("2014-07","2014-08","2014-09","2014-10","2014-11","2014-12");
Any help would be appreciated. :)
=-=-=-=-=-=-=-==-=-=-=-=-=-=-=-=--=-=
UPDATE:
Here is what i have done so far, yes getting some code from here and there. and tweaking it a bit up to fulfill my requirements.
//Getting all Months from First Date Range
$start = (new DateTime('2014-06-01'))->modify('first day of this month');
$end = (new DateTime('2015-05-06'))->modify('first day of next month');
$interval = DateInterval::createFromDateString('1 month');
$firstPeriod = new DatePeriod($start, $interval, $end);
//Getting all Months from Second Date Range.
$start = (new DateTime('2012-02-01'))->modify('first day of this month');
$end = (new DateTime('2014-12-31'))->modify('first day of next month');
$interval = DateInterval::createFromDateString('1 month');
$secondPeriod = new DatePeriod($start, $interval, $end);
$collidingDates = array();
foreach ($firstPeriod as $f_dt) {
foreach($secondPeriod as $s_dt){
if($f_dt->format("Y-m") === $s_dt->format("Y-m")){
array_push($collidingDates,$f_dt->format("Y-m"));
}
}
}
echo "<pre>";
print_r($collidingDates);
i got this output.
Array
(
[0] => 2014-06
[1] => 2014-07
[2] => 2014-08
[3] => 2014-09
[4] => 2014-10
[5] => 2014-11
[6] => 2014-12
)
But i think i am getting 1 extra month
2014-06, not sure how O_o??
This is a two step process. First you need to establish the narrowest date range from the start and end dates. Then list the months between those dates.
// An array of start and end dates. There are just 2 in this example but you
// could have as many as you like in the same "start, then end" format.
$ranges = [
[new DateTime('2014-07-01'), new DateTime('2015-06-30')],
[new DateTime('2012-02-01'), new DateTime('2014-12-31')]
];
// Reduce the ranges to one set of two dates, the latest of the start dates
// and the earliest of the end dates.
$range = array_reduce($ranges, function($carry, $item){
return $carry
? [max($carry[0], $item[0]), min($carry[1], $item[1])]
: $item;
});
var_dump($range);
/*
array (size=2)
0 =>
object(DateTime)[1]
public 'date' => string '2014-07-01 00:00:00.000000' (length=26)
public 'timezone_type' => int 3
public 'timezone' => string 'Europe/London' (length=13)
1 =>
object(DateTime)[4]
public 'date' => string '2014-12-31 00:00:00.000000' (length=26)
public 'timezone_type' => int 3
public 'timezone' => string 'Europe/London' (length=13)
*/
// Shift both dates to the first of the month. Strictly speaking, we only
// need to do this with the start date.
$range = array_map(function($date){
return $date->modify("first day of this month");
}, $range);
var_dump($range);
/*
array (size=2)
0 =>
object(DateTime)[1]
public 'date' => string '2014-07-01 00:00:00.000000' (length=26)
public 'timezone_type' => int 3
public 'timezone' => string 'Europe/London' (length=13)
1 =>
object(DateTime)[4]
public 'date' => string '2014-12-01 00:00:00.000000' (length=26)
public 'timezone_type' => int 3
public 'timezone' => string 'Europe/London' (length=13)
*/
$months = [];
$interval = new DateInterval("P1M");
for ($month = $range[0]; $month <= $range[1]; $month->add($interval)) {
$months[] = $month->format("Y-m");
}
var_dump($months);
/*
array (size=6)
0 => string '2014-07' (length=7)
1 => string '2014-08' (length=7)
2 => string '2014-09' (length=7)
3 => string '2014-10' (length=7)
4 => string '2014-11' (length=7)
5 => string '2014-12' (length=7)
*/
I think you are looking for this:
$start = (
new DateTime('2015-12-02'))
->modify('first day of this month');
$end = (new DateTime('2016-05-06'))->modify('first day of next month');
$interval = DateInterval::createFromDateString('1 month');
$period = new DatePeriod($start, $interval, $end);
foreach ($period as $dt) {
echo $dt->format("Y-m") . "<br>\n";
}
Is there any way to optimize this piece of code to work faster? I'd appreciate any suggestions!
This piece of code processes the transferring of edges during graph creation.
foreach($times_arrival as $city_id => $time_points) {
// if city is not prohibited for transfers and there is and exists any departure times for this city
if (isset($times_departure[$city_id]) && isset($cities[$city_id]))
{
foreach($times_arrival[$city_id] as $t1_info)
{
foreach($times_departure[$city_id] as $t2_info)
{
if ($t1_info[0] != $t2_info[0]) //transfers are allowed only for different passages
{
$t1 = $t1_info[1];
$t2 = $t2_info[1];
$vertex_key = new Vertex($city_id, $t1, 1);
$vertex_key = $vertex_key->toString();
//minimum transfer time is 10 min.
if (date('H:i', strtotime($t2)) > date('H:i', strtotime('+ 10 minutes', strtotime($t1))))
{
$this->graph[$vertex_key][] = new Edge(
NULL,
$vertex_key,
new Vertex($city_id, $t2, 0),
(float) 0,
$f((strtotime($t2) - strtotime($t1)) / 60, 0, 1) //edge weight
);
}
//if transfer is on the bound of the twenty-four hours
else if (date('H:i', strtotime('+ 24 hours', strtotime($t2))) > date('H:i', strtotime('+ 10 minutes', strtotime($t1))))
{
$this->graph[$vertex_key][] = new Edge(
NULL,
$vertex_key,
new Vertex($city_id, $t2, 0),
(float) 0,
$f(strtotime('+ 24 hours', strtotime($t2)) - strtotime($t1) / 60, 0, 1) //edge weight
);
}
}
}
}
}
}
example of variables:
var_dump($times_arrival); //$times_departure have the same structure
array
3 =>
array
0 =>
array
0 => string '1' (length=1)
1 => string '08:12' (length=5)
1 =>
array
0 => string '2' (length=1)
1 => string '08:40' (length=5)
41 =>
array
0 =>
array
0 => string '21' (length=2)
1 => string '12:40' (length=5)
Thank you all!
The reason of slow speed was coz of using functions strtotime() and date().
In that case only you can say whether you chose a good or bad algorithm. In my point of view your code not has no extra computations.
Only one recommendation - use Xdebug to profile your code and find out where the bottleneck is, if possible.