Get multiple PHP date periods from a range of dates - php

I have an array of dates like this:
[Room 01 - DBL] => Array
(
[0] => Mon 23-06-2014
[1] => Tue 24-06-2014
[2] => Wed 25-06-2014
[3] => Sat 28-06-2014
[4] => Sun 29-06-2014
)
[Room 02 - TWN] => Array
(
[0] => Tue 24-06-2014
[1] => Wed 25-06-2014
[2] => Sat 28-06-2014
[3] => Sun 29-06-2014
)
You can see that neither room has the date for the thursday or friday. I want to be able to create a date range (either a DateTime Interval object or not - don't mind) for each group of dates. So Room 02 - TWN should give me two date periods - one for Tue-Wed and another for Sat-Sun. How would I go about this? I know how to make a single date time period using the first and last items in the array but don't know how to detect if there is a gap...

I'm not very clear about what you're trying to accomplish. Anyway, in the general case, you can do something like this.
The idea is to run the whole array of items, and if the "next" item is contiguous to a candidate interval you already have, you extend the interval. Else, the candidate interval becomes a standalone interval, and the item that just failed the check gives birth to a new candidate interval.
You need two functions: one that, given two items, returns whether it's true or false that they are contiguous; the other, given two items, returns an "interval" with those two items as extremes.
An empty $items will return an empty interval.
function build_intervals($items, $is_contiguous, $make_interval) {
$intervals = array();
$end = false;
foreach ($items as $item) {
if (false === $end) {
$begin = $item;
$end = $item;
continue;
}
if ($is_contiguous($end, $item)) {
$end = $item;
continue;
}
$intervals[] = $make_interval($begin, $end);
$begin = $item;
$end = $item;
}
if (false !== $end) {
$intervals[] = $make_interval($begin, $end);
}
return $intervals;
}
For numbers, you can use
$interv = build_intervals(
array( 1, 2, 3, 5, 6, 9, 10, 11, 13, 17, 18 ),
function($a, $b) { return ($b - $a) <= 1; },
function($a, $b) { return "{$a}..{$b}"; }
);
print_r($interv);
returns
Array
(
[0] => 1..3
[1] => 5..6
[2] => 9..11
[3] => 13..13
[4] => 17..18
)
With dates, you can keep them as DateTime and DateTimeIntervals. If you use timestamps, then you must supply a contiguousness criterion that's valid for timestamps. This can be awkward if you have two timestamps that are just before and after midnight the following day. To be sure, you should take times at always around midday (i.e., given two dates, get the timestamps of those dates at midday. If they're less than 36 hours apart, they're two adjacent days.
function($a, $b) {
$a_ts = strtotime("{$a} 12:00:00");
$b_ts = strtotime("{$b} 12:00:00");
return ($b - $a) <= (36 * 60 * 60);
},

Related

Count months from a date to December then count months from January the following year to December that same year, then from January to end date

I have a start_date of 1/10/2018, and an end_date of 1/8/2020, the difference between the two dates in months is 22, that is 1 year 10 months, now, I want to create tables that terminate at the end of each year as follows:
table 1
column_heading will be "1/10/2018 - 31/12/2018"
and the row will be "2 months"
table 2
column_heading will be "1/1/2019 - 31/12/2019"
and the row will be "12 months"
table 3
column_heading will be "1/1/2020 - 1/8/2020"
and the row will be "8 months"
I would like to loop something, maybe the difference between the dates to create the number of tables necessary, if the two dates exist within the same year it will only create 1 table, or 2 if it enters the next year, I am using laravel and carbon to manipulate the dates.
Thank you in anticipation of your help.
Something like this
Here's one way. Note that I had to convert the format of your dates to YYYY-mm-dd in order to use PHP date functions. In the end you'll get an array and it's easy for you to transform the final dates into the format you desire. You can test it here: https://www.tehplayground.com/lvuTdWl91TeItEQC
The code:
<?php
// example code
$s = "1/10/2018";
$e = "1/08/2020";
// reassemble so we can use the date functions YYYY-mm-dd
$s = implode("-", array_reverse(explode("/", $s)) );
$e = implode("-", array_reverse(explode("/", $e)) );
// get the parts separated
$start = explode("-",$s);
$end = explode("-",$e) ;
$iterations = ((intVal($end[0]) - intVal($start[0])) * 12) - (intVal($start[1]) - intVal($end[1])) ;
$sets=[$start[0] => array("start" => $s, "end" => "", "months" => 0)];
$curdstart= $curd = $s;
$curyear = date("Y", strtotime($s));
for($x=1; $x<=$iterations; $x++) {
$curdend = date("Y-m-d", strtotime($curd . " +{$x} months"));
$curyear = date("Y", strtotime($curdend));
if (!isset($sets[$curyear])) {
$sets[$curyear]= array("start" => $curdend, "end" => "", "months" => 0);
}
$sets[$curyear]['months']++;
$sets[$curyear]['end'] = date("Y-m-", strtotime($curdend)) . "31";
}
die(print_r($sets,1));
$mctr = 0 ;
The output:
Array
(
[2018] => Array
(
[start] => 2018-10-1
[end] => 2018-12-31
[months] => 2
)
[2019] => Array
(
[start] => 2019-01-01
[end] => 2019-12-31
[months] => 12
)
[2020] => Array
(
[start] => 2020-01-01
[end] => 2020-08-31
[months] => 8
)
)

Check CSV for 2 rows with the same text & time within 60mins

I am looping through a CSV file with the following structure
text,time
"Hey you",20181219T15:59:00
"Hey you",20181219T15:39:00
"Random",20181219T15:39:00
This simply contains a 'text string' and another string that represents an ISO 8601 date.
I want to perform a function that will check if a) it has found two rows with the exact same text & has the timeframe between these is within 60 minutes?
Can anyone suggest the best way to approach this (taking into account the CSV will be a max of 50 entries so want to make this as efficient as possible.
So my array returned looks like this once the dates are parsed via DateTime
Array
(
[0] => Array
(
[text] => Hey you
[time] => DateTime Object
(
[date] => 2018-12-19 15:59:00.000000
[timezone_type] => 3
[timezone] => Europe/London
)
)
[1] => Array
(
[text] => Hey you
[time] => DateTime Object
(
[date] => 2018-12-19 15:39:00.000000
[timezone_type] => 3
[timezone] => Europe/London
)
)
)
What would be the best method to check for the exact same text AND time is within one hour of the same text?
Assuming that the array is ordered by Datetime (so bigger key means newer date time), you can do as follow:
// set an array to track text we meet
$textList = [];
foreach ($array as $key => $element){
$text = $element['text'];
if (!array_key_exists($text, $textList)) {
// first time we meet this text, we track it and its position in the array
$textList[$text] = $key;
}else{
// second time we meet this test, we compare the current date time with the previous one to get difference in minutes
$currentTime = $element['time'];
$previousTimeKey = $textList[$text];
$previousTime = $array[$previousTimeKey]['time'];
$diff = $currentTime->diff($previousTime);
// total minutes of diff: hours*60 + minutes
$diffInMinutes = $diff->format('%h') * 60 + $diff->format('%i');
if ($diffInMinutes < 60) {
// do whatever you need..
}
}
}
Complicate this for your particular needs. ;)
PS if the array is not ordered by date time, consider to order it before and then use this function: the algorithm with an array not ordered by date time would be much harder.
Tried this with your given array
My solution with the time difference might not be pretty but i noticed the other answer doesn't take days/months/years into the time difference so it will pass if the date is different but time of the day is less than 60minutes.
This also worked no matter if the first date is older or younger, so the array sorting isn't needed.
function findMatch($arrays){
$tmp_list = []; //takes the first occurance of text
foreach ($arrays as $key => $array) {
if(isset($tmp_list[$array['text']])){
//possible match check for time difference
$difference = $tmp_list[$array['text']]->diff($array['time']);
if($difference->y === 0 && $difference->m === 0 && $difference->d === 0 && $difference->h === 0 && $difference->i <= 59 && $difference->s <= 59){
//less than hour difference
}else{
//more than hour difference
}
}else{
$tmp_list[$array['text']] = $array['time'];
}
}
}
findMatch($arrays);

PHP: I have an assoc array with time ranges ($k=>$v integers), I want to trim and combine overlapping schedules

Premise: I'm working at a project that needs to be PHP 5.2 compliant, I cannot use PHP 5.3+ DateInterval or other PHP>=5.3-only instructions, regrettably.
I have an array that contains business hours expressed as $k => $v ranges, so the key is a start, the value is an end, like so:
array(
09:00 => 11:30
09:30 => 11:00
10:00 => 12:30
13:30 => 14:30
)
In this example we have the first three pairs of ranges that overlap, I could express the same thing as 09:00 => 12:30 (meaning: opens at 9 am, closes at 12.30 pm), since the start and end of the first three pairs overlap.
I could also write the array like so, as integers (or I could use floats e.g. 09:30 becomes 9.3 it doesn't matter I think):
array(
900 => 1130
930 => 1100
1000 => 1230
1330 => 1430
)
How to turn the array into:
array(
900 => 1230
1330 => 1430
)
Ideas that come to my mind are looping the array, using array_slice, passing values by reference and unset() things while at it... But I'm not sure if that would be the best idea or I'm just overcomplicating it.
There are many solutions to this problem; here's mine:
http://phpfiddle.org/main/code/batv-hzqw
Hopefully it is clear enough and fulfills the criteria that you have laid out -- If not please do tell me :)
One way to do this type of overlap checking is to use this algorithm truth: What's the most efficient way to test two integer ranges for overlap?
Pseudo code
Loop through all of your time ranges and put them into the same bucket if they overlap.
Break those buckets down into single elements with the lowest start and highest end time.
PHP example:
<?php
$times = array(
900 => 1130,
930 => 1100,
1000 => 1230,
1330 => 1430,
845 => 900,
1330 => 1700,
845 => 1000
);
function reduceOverlap($times) {
$reduced = array();
//Put the first entry into our final bucket
$reduced[array_keys($times)[0]] = $times[array_keys($times)[0]];
foreach ($times as $start => $end) {
// Trip this flag if a new bucket does not need to be created
$foundOverlap = false;
// Check if this time can go in one of our buckets
foreach ($reduced as $reducedStart => $reducedEnd) {
// Truthy check for overlap of time range with bucket range
if ($start <= $reducedEnd && $end >= $reducedStart) {
// Use this for start key incase it gets changed
$startKey = $reducedStart;
// Was the start less than the bucket's start?
// If yes update bucket's start time
if ($start < $reducedStart) {
unset($reduced[$reducedStart]);
$reduced[$start] = $reducedEnd;
$startKey = $start;
}
// Was the end greater than the bucket's end?
// If yes update bucket's end time
if ($end > $reducedEnd) {
$reduced[$startKey] = $end;
}
$foundOverlap = true;
}
}
// There was no overlap, create a new bucket
if (!$foundOverlap) {
$reduced[$start] = $end;
}
}
return $reduced;
}
var_dump(reduceOverlap($times));
Output:
array(2) {
[1330]=>
int(1700)
[845]=>
int(1230)
}

How to sum time periods between dates,and get the average

I have an array,each element have a start_date and end_date.
I want to sum all the time periods between the two dates and divide them by 3 to get the average time period.
The Array
Array
(
[0] => Array
(
[start_date] => "2014-01-30 09:27:02"
[end_date] => "2014-01-30 16:29:38"
)
[1] => Array
(
[start_date] => "2014-01-28 09:27:02"
[end_date] => "2014-01-30 16:29:38"
)
[2] => Array
(
[start_date] => "2014-01-30 09:05:02"
[end_date] => "2014-01-30 12:12:38"
)
)
I need some thing like this:
$total=0;
foreach($array as $ar)
{
$created_dt=strtotime($ar[$start_date]);
$done_dt=strtotime($ar[$end_date]);
$runing_time= $created_dt - $done_dt - 2*3600;
$runing_time= date('H:i',$runing_time);
$total+=$runing_time;
}
$runing_time=$total/3;
what is a good way to do this?
thanks :)
You are making a fundamental mistake in trying to treat a time period as a date/time, you cannot use date() and its related functions for time periods.
The way to find the average of time periods is to sum the number of seconds and then divide that by the number of periods. The following function does this:-
function averageTime(array $times){
$total = 0;
foreach($times as $time){
$total += (new \DateTime($time['end_date']))->getTimestamp() - (new \DateTime($time['start_date']))->getTimestamp();
}
$avSeconds = $total/count($times);
$hours = floor($avSeconds/3600);
$avSeconds -= $hours * 3600;
$minutes = floor($avSeconds/60);
$avSeconds -= $minutes * 60;
$seconds = floor($avSeconds);
return "{$hours}h {$minutes}m {$seconds}s";
}
Plugging your example array into it:-
$times= array(
0 => Array(
'start_date' => "2014-01-30 09:27:02",
'end_date' => "2014-01-30 16:29:38",
),
1 => Array(
'start_date' => "2014-01-28 09:27:02",
'end_date' => "2014-01-30 16:29:38",
),
2 => Array(
'start_date' => "2014-01-30 09:05:02",
'end_date' => "2014-01-30 12:12:38",
),
);
echo "Average time = " . averageTime($times);
Output:-
Average time = 21h 44m 16s
Ref DateTime manual.
As shown at this answer it's ultra simple. :) (i have slightly modified it)
$now = time(); // or your date as well
$your_date = strtotime("2010-01-01");
$datediff = $now - $your_date;
$numberOfDays = floor($datediff/(60*60*24));
Now you just have to put it in foreach and sum in $numberOfDays in another variable.
And please remember to check if first date is lower than second date
Technically you are already correct.
But it order to have the given script to run propely you may have edit it to
$total=0;
foreach($array as $ar)
{
$created_dt=strtotime($ar['start_date']);
$done_dt=strtotime($ar['end_date']);
$runing_time= $created_dt - $done_dt - 2*3600;
$runing_time= date('H.i',$runing_time);
$total+=$runing_time;
}
$runing_time=$total/3;
so I only changed 3 thing.
first your $start_date and $end_date was an variable that was not existing.
and second if you want the average instead of : put . so it can be read as a double.
and if you want the variable $total to be the real value i would do this instead.
$runing_time= date('H',$runing_time).'.'.date('i',$runing_time)/60*100;

Traversing arrays php

I was wondering if there is a better solution to loop through an array from mid to end then from start to mid. Particularly for an associative array.
So for example if there is an associative array with the keys
$dow = array(Mon => etc, Tue => etc, Wed => etc, Thr => etc .. to .. Sun => etc).
I would start searching the array from Thurs to find the next day with something specific which could be anyday but happens to be on Tues, I usually iterate from Thurs (by index) to Sunday then, reset and start again from Monday to Wed and find the target when reaching Tues.
I count the index via an id and when it reaches 6 reset the id to 0
$id = 3 // Found day is Thursday id
//Loop function starts here
$id++; // start search from one day above found day
if ($id >= 6){ //when reaching Sunday
$id = 0 // start search from monday
}
// check array here for that specific thing
So the question is to ask if there is a more simple solution than this, ie split array from index thursday to sunday and add it onto the beginning of the array and then do the loop without having to count an index or if there are any other solutions without using the count index.
You can try with array_splice:
$array = array(1,2,3,4,5,6,7);
$lastDays = array_splice($array, 3);
$firstDays = $array;
print_r(array('first days' => $firstDays, 'last days' => $lastDays));
If the day is not in $lastDays (use a boolean like $matchFound) then you would search in $firstDays.
Or just use with array_merge:
$array = array(1,2,3,4,5,6,7);
$array = array_merge(array_splice($array, 3), $array);
print_r($array);
where output is:
Array
(
[0] => 4
[1] => 5
[2] => 6
[3] => 7
[4] => 1
[5] => 2
[6] => 3
)
and then you can search with a foreach.
maybe a foreach would be more efficient. hope this helps.
$id = 3 // Found day is Thursday id
$i =0;
//Loop function starts here
foreach($dow as $idx)
if($id == $i):
else if($i > $id):
endif;
if($i == count($dow)){$i=0;}else{$i++;}
endforeach;
<?php
$dow = array("mon","tue","wed","thu","fri","sat","sun");
$pick_a_day = 2; // user input; NON zero based. monday = 1
$pick_a_day--; // make it zero based to use in code
foreach($dow as $inc => $an_element )
{
echo $dow[($pick_a_day+(count($dow))) % (count($dow))]." - loop number:".($inc+1)."\n";
$pick_a_day++;
}
?>
output
tue - loop number:1
wed - loop number:2
thu - loop number:3
fri - loop number:4
sat - loop number:5
sun - loop number:6
mon - loop number:7
Maybe not the best solution, but a solution:
function half($array) {
$h = sizeof($array) / 2;
$a1 = array_splice($array, $h);
$a2 = array_splice($array, 0);
$res = array_merge($a1, $a2);
return $res;
}
$dow = array('Mon' => 'etc1', 'Tue' => 'etc2', 'Wed' => 'etc3', 'Thr' => 'etc4', 'Fri' => 'etc5', 'Sat' => 'etc6', 'Sun' => 'etc7');
$b = half($dow);
Now you can go through with a foreach, or how would you like. Result of $b:
Array
(
[Thr] => etc4
[Fri] => etc5
[Sat] => etc6
[Sun] => etc7
[Mon] => etc1
[Tue] => etc2
[Wed] => etc3
)

Categories