PHP - Finding consecutive day counts in an array - php

Let's say I have an array called $selectedDates containing the following
$selectedDates = [
"2000-01-31",
"2000-02-01",
"2000-02-02",
"2000-02-20"
];
Is there some way in PHP to calculate out what # each day is consecutively.
So that the first one returns 1, the 2nd one would be returning 2, the 3rd would be 3 but the 4th would be 1 again.
I'm trying to loop through them all at the moment but am not really getitng anywhere.
<?php foreach ($selectedDates as $date): ?>
Seriously lost about what to put here.
<?php echo $consecDayCount; ?>
<?php endforeach; ?>
I'm thinking inside of the loop I may need to loop through it again? And start at the first one and check each day going up one more, adding to some incrementer for each day that says the previous date in the array was the previous day in time. I'm just kind of banging my head against a wall figuring out that part.

One way to Rome...
# We assume $selectedDates is sorted at this point.
$selectedDates = [
"2000-01-31",
"2000-02-01",
"2000-02-02",
"2000-02-20"
];
# The array we will print.
$coll = array();
# We use array_reverse to start from the last entry.
foreach(array_reverse($selectedDates) as $date) {
$tmp = $date;
$con = 1;
do $tmp = date('Y-m-d', strtotime("$tmp -1day"));
# ++$con is used to count only up if in_array gives true.
while(in_array($tmp, $selectedDates) && (++$con));
$coll[$date] = $con;
}
print_r($coll);
Result: Array ( [2000-02-20] => 1 [2000-02-02] => 3 [2000-02-01] => 2 [2000-01-31] => 1 )

Here you go:
$selectedDates = [
'2000-01-31',
'2000-02-01',
'2000-02-02',
'2000-02-20',
'2000-02-21',
];
$format = 'Y-m-d';
$consecutive = [];
$length = count($selectedDates);
$streak = false;
$last = null;
for($index = 0; $index < $length - 1; $index++) {
$firstDate = DateTime::createFromFormat($format, $selectedDates[$index]);
$secondDate = DateTime::createFromFormat($format, $selectedDates[$index + 1]);
$diff = $firstDate->diff($secondDate);
if ($diff->days === 1) {
// consecutive dates
if ($streak) {
$consecutive[$last]++; // we have another consecutive day to the current streak
} else {
$consecutive[$selectedDates[$index]] = 2; // we already have 2 consecutive days
$last = $selectedDates[$index];
$streak = true;
}
} else {
$streak = false;
}
}
var_dump($consecutive);
This will give you an array in the date => number of consecutive days starting on that date format.

Another way
$dates = array(
new DateTime('2000-01-31'),
new DateTime('2000-02-01'),
new DateTime('2000-02-02'),
new DateTime('2000-02-20'),
);
// process the array
$lastDate = null;
$ranges = array();
$currentRange = array();
foreach ($dates as $date) {
if (null === $lastDate) {
$currentRange[] = $date;
} else {
// get the DateInterval object
$interval = $date->diff($lastDate);
// DateInterval has properties for
// days, weeks. months etc. You should
// implement some more robust conditions here to
// make sure all you're not getting false matches
// for diffs like a month and a day, a year and
// a day and so on...
if ($interval->days === 1) {
// add this date to the current range
$currentRange[] = $date;
} else {
// store the old range and start anew
$ranges[] = $currentRange;
$currentRange = array($date);
}
}
// end of iteration...
// this date is now the last date
$lastDate = $date;
}
// messy...
$ranges[] = $currentRange;
// print dates
foreach ($ranges as $range) {
// there'll always be one array element, so
// shift that off and create a string from the date object
$startDate = array_shift($range);
$str = sprintf('%s', $startDate->format('D j M'));
// if there are still elements in $range
// then this is a range. pop off the last
// element, do the same as above and concatenate
if (count($range)) {
$endDate = array_pop($range);
$str .= sprintf(' to %s', $endDate->format('D j M'));
}
echo "<p>$str</p>";
} ?>
output::: Mon 31 Jan to Wed 2 Feb
Sun 20 Feb

Related

How to calculate the time passed in an array of time slots in PHP

Have time slots which are in ascending order:
// 1st slot
$timeslot[] = '07:00-08:00';
// total = 1 hr
// 2nd slot
$timeslot[] = '07:15-07:30'; // not considered since it lies between the first slot ie 7 to 8
// total = 1 hr
// 3rd slot
$timeslot[] = '07:30-08:30'; // 1 hr of the first slot + remaining 30 minutes of this slot = 1:30 hrs
// total = 1:30 hrs
// 4rth slot
$timeslot[] = '10:45-11:45'; // 1:30 hrs + 1 hr
// total = 2:30 hrs
so far i have tried like this but no hope; what i'm trying to get is the time passed between the slots. for example we have two time slots 07:00-08:00 and 07:30-08:30, the time travelled in these two time slot is 1:30 hours. so something like this i'm calculating. My code goes like this:-
function addtimespend($dumparray = '', $i, $diff)
{
$arr1 = explode("-", $dumparray[0]);
if (isset($dumparray[$i])) {
$arr2 = explode("-", $dumparray[$i]);
if (strtotime($arr2[1]) > strtotime($arr1[1]) && strtotime($arr2[0]) < strtotime($arr1[1])) {
$diff = $diff + (strtotime($arr2[1]) - strtotime($arr1[1]));
return $diff;
} else {
$diff = $diff + (strtotime($arr1[1]) - strtotime($arr1[0]));
}
$i++;
return addtimespend($dumparray, $i, $diff);
} else {
$diff = $diff + (strtotime($arr1[1]) - strtotime($arr1[0]));
return $diff;
}
}
$flag = $diff = 0;
$diff = addtimespend($event, 1, 0);
function convertToHoursMins($time, $format = '%02d:%02d')
{
if ($time < 1) {
return;
}
$hours = floor($time / 60);
$minutes = ($time % 60);
return sprintf($format, $hours, $minutes);
}
echo convertToHoursMins($diff / 60, '%02d hours %02d minutes');
<?php
$timeslot = [];
$timeslot[] = '07:00-08:00';
$timeslot[] = '07:15-07:30';
$timeslot[] = '07:30-08:30';
$timeslot[] = '10:45-11:45';
$min_time = -1;
$max_time = -1;
$total_minutes = 0;
foreach($timeslot as $slot){
list($start_time,$end_time) = explode("-",$slot);
$start_time = explode(":",$start_time);
$start_time = intval($start_time[0]) * 60 + intval($start_time[1]); // converting to minutes
$end_time = explode(":",$end_time);
$end_time = intval($end_time[0]) * 60 + intval($end_time[1]);// converting to minutes
if($min_time == -1){// or max time for that matter (just basic initialization of these 2 variables)
$min_time = $start_time;
$max_time = $end_time;
$total_minutes += $max_time - $min_time;
}else{
if($start_time >= $max_time) $total_minutes += $end_time - $start_time;
else if($start_time < $max_time && $end_time > $max_time) $total_minutes += $end_time - $max_time;
$min_time = min($min_time,$start_time);
$max_time = max($max_time,$end_time);
}
}
echo intval($total_minutes / 60),":",($total_minutes % 60)," hrs";
Demo: https://3v4l.org/nvjDq
Algorithm:
Since your data is sorted according to start times, we can just keep track of min and max times of timeslots.
For simplicity, we can convert the timeslot in minutes.
We add to our total only under these 2 conditions:
If the current slot collides with the time range we maintain.
If the current slot is completely out of bounds of the current time range.
In the end, we print the answer in hours format.
i made a little script to calculate your timeslots, which works also fine with UNSORTED timeslots:
<?php
$timeslots = [];
// 2nd slot
$timeslots[] = '07:00-08:00'; // not considered since it lies between the first slot ie 7 to 8 // total = 1 hr
$timeslots[] = '07:15-08:00'; // 1st slot
$timeslots[] = '07:30-08:00'; // 1st slot
$timeslots[] = '07:30-08:30'; // 3rd slot
$timeslots[] = '07:45-08:45'; // 1 hr of the first slot + remaining 30 minutes of this slot = 1:30 hrs // total = 1:30 hrs // remove duplicate one's
// // 4rth slot
$timeslots[] = '10:45-11:45';
$test = new test;
foreach ($timeslots as $timeslot) {
$test->checkInBetween($timeslot);
}
$totalDiff = 0;
foreach ($test->sequences as $key => $sequence) {
$sequenceDifference = strtotime($sequence['latestEnd']) - strtotime($sequence['earliestStart']);
$totalDiff += $sequenceDifference;
}
echo "<pre>";
var_dump($totalDiff);
die();
class test {
public $sequences = [
0 => [
'earliestStart' => '',
'latestEnd' => '',
],
];
public function checkInBetween($timeslot) {
$exploded = explode('-', $timeslot);
$isEarliest = false;
$isLatest = false;
$isBetweenFirst = false;
$isBetweenSecond = false;
$sequenceFound = false;
foreach ($this->sequences as $key => $sequence) {
// Check if the first number is the earliest
if (($exploded[0] < $sequence['earliestStart'])) {
$isEarliest = true;
}
// Check if the last number is the latest
if (($exploded[1] > $sequence['latestEnd'])) {
$isLatest = true;
}
if ($exploded[0] > $sequence['earliestStart'] && $exploded[0] < $sequence['latestEnd']) {
$isEarliest = false;
$isBetweenFirst = true;
}
if ($exploded[1] > $sequence['earliestStart'] && $exploded[1] < $sequence['latestEnd']) {
$isLatest = false;
$isBetweenSecond = true;
}
if (($isEarliest && $isLatest) || ($isEarliest && $isBetweenSecond)) {
$this->sequences[$key]['earliestStart'] = $exploded[0];
$sequenceFound = true;
}
if (($isEarliest && $isLatest) || ($isLatest && $isBetweenFirst)) {
$this->sequences[$key]['latestEnd'] = $exploded[1];
$sequenceFound = true;
}
}
if (!$sequenceFound) {
$this->sequences[] = [
'earliestStart' => $exploded[0],
'latestEnd' => $exploded[1],
];
}
}
}
Feel free to ask questions. Please mind that the output (totalDiff) contains seconds!
A few words to the script:
The script checks every value inside the timeslots array and tries to merge it into a sequence if the starting time is in between an existing sequence or the ending time is in between an existing sequence. If one of those conditions are met, the sequence is updated with the new value.
If none of those conditions are met, the script adds a new sequence, as the current values are not matching any existing conditions.
After iterating every value inside the timeslot, the sequences will be calculated in terms of difference in seconds, which will be added to the totalDiff.
This code will work if the time slots are shorted by their start time in ascending order.
<?php
$timeslots[] = '07:00-08:00';
$timeslots[] = '07:15-07:30';
$timeslots[] = '07:30-08:30';
$timeslots[] = '10:45-11:45';
$slots=array();
foreach($timeslots as $timeslot){
$timeslot=explode("-",$timeslot);
$start=toMinutes($timeslot[0]);
$end=toMinutes($timeslot[1]);
$slots[]=["start"=>$start,"end"=>$end];
$starts[]=$start;
$ends[]=$end;
}
function toMinutes($time){
$arr= explode(":",$time);
return ($arr[0] * 60) + $arr[1];
}
function toTime($minutes){
return floor($minutes / 60) .":". $minutes % 60;
}
function totalGapMinutes($slots){
$count=count($slots);
$i=0;
$gap=0;
for($i; $i<$count-1; $i++){
if($slots[$i]['end']<$slots[$i+1]['start']){
$gap+=$slots[$i+1]['start']-$slots[$i]['end'];
}
}
return $gap;
}
var_dump(toTime(max($ends)-min($starts) - totalGapMinutes($slots)));

Date function to bypass weekends and specific dates

I am trying to implement the code below to force dates to skip weekends and holiday array. The weekend part is working but I am having difficulty with the holiday section, here is my code:
$holidayDates = array(
'10-10-2017', '11-10-2017'
);
$fcount = 0;
$temp = strtotime("9-10-2017");
while ($fcount < 1) {
$nextfcount = strtotime('+1 weekday', $temp);
$nextfcount2 = date('d-m-Y', $temp);
if (!in_array($nextfcount2, $holidayDates)) {
$fcount++;
}
$temp = $nextfcount;
}
$newDater = date("d-m-Y", $temp);
Let me debug this for you:
$holidayDates = array(
'10-10-2017', '11-10-2017'
);
$fcount = 0;
// 1. $temp is a terrible name for a variable
$temp = strtotime("9-10-2017");
// 2. in other words - if $fcount == 1, exit loop
while ($fcount < 1) {
// 3. $temp is now the 9th
$nextfcount = strtotime('+1 weekday', $temp);
// 4. $nextfcount2 is now the 10th
$nextfcount2 = date('d-m-Y', $temp);
if (!in_array($nextfcount2, $holidayDates)) {
// 5. Yes, found your date. $fcount is now 1
$fcount++;
}
// 6. $temp is now the 10th
$temp = $nextfcount;
// 7. $fcount is now 1, `while` condition is met, exit loop
}
// 8. The date is now the 10th
$newDater = date("d-m-Y", $temp);
You question is much too vague for me to provide you with a quality answer.
I will assume, you want to get 12-10-2017 as the result of your loop. This code will do the trick:
<?php
$holidayDates = [
'10-10-2017',
'11-10-2017',
];
$foundCounter = 0;
$theDate = strtotime("9-10-2017");
while ($foundCounter < 1) {
$theDate = strtotime('+1 weekday', $theDate);
if (!in_array(date('d-m-Y', $theDate), $holidayDates)) {
$foundCounter++;
}
}
$newDater = date("d-m-Y", $theDate);
var_dump($newDater);
// string(10) "12-10-2017"
https://3v4l.org/DcWBZ

Check if weekday is between weekdays in string

So i have a list of weekdays in which i need to check if they are the current day. The list varies between either being 'Monday/Tuesday' and 'Wednesday - Friday'
My current solution is string comparison, so it only detects if the current weekday is written in the list item.
Heres the current solution (in PHP btw):
setlocale(LC_ALL, "danish");
$day = get_sub_field('dag'); // the field containing the user input day
$currentDay = strftime('%a', mktime());
$currentDayLower = strtolower($currentDay);
$dayLowercase = strtolower($day);
$class = '';
if(strpos($dayLowercase, $currentDayLower) !== false ){
$class = ' current-day';
} else{
$class = '';
}
I was thinking about having an array of all weekdays and comparing the user field to current day position in the array, but im not sure if that would be efficient or even possible.
Is there any obvious or alternative method that could be easier than what I'm currently doing?
Any inputs are greatly appreciated.
EDIT:
I found a working solution, which i posted as an answer (I can't choose it as the answer for 2 days though). Thanks for the inputs!
Yes, your idea is feasible and it should not affect performance badly unless you do this many times in a short period of time, where many starts at a scale of hundreds of thousands. Notice that I have created an array of $weekdays and notice that when you assign a value for $class, that is an end sign for the cycle as well.
setlocale(LC_ALL, "danish");
$day = get_sub_field('dag'); // the field containing the user input day
$dayLowercase = strtolower($day);
$weekdays = array("monday", "tuesday", "wednesday", "thursday", "friday");
$class = '';
for ($index = 0, (!$class) && ($index < 5); $index++) {
if(strpos($dayLowercase, $weekdays[$index]) !== false ){
$class = ' current-day';
} else{
$class = '';
}
}
If you want to check that the range of days includes the current day, you can do the following.
$period = 'Wednesday-Friday';
$limitDays = explode('-', $period);
$startDayName = trim(strtolower($limitDays[0]));
$endDayName = trim(strtolower($limitDays[1]));
$today = new DateTime();
$todayName = strtolower($today->format('l'));
// Check if the startDay or endDay is today.
echo "$startDayName $endDayName $todayName\n";
if ($startDayName === $todayName || $endDayName === $todayName) {
echo "Same day\n";
$class = 'current-day';
} else {
// Get a date time representing the start day.
$startDay = new \DateTime();
$startDay->modify("next $startDayName");
// Based on the start day, get the next current day.
$thisDay = new \DateTime();
$thisDay->modify("next $startDayName");
$thisDay->modify("next $todayName");
// Based on the start day, get the next end day.
$endDay = new \DateTime();
$endDay->modify("next $startDayName");
$endDay->modify("next $endDayName");
// Check if thisDay is between the startDay and endDay.
if ($startDay < $thisDay && $thisDay < $endDay) {
$class = 'current-day';
} else {
$class = '';
}
}
echo $class . "\n";
I'm not sure that this is easier or more efficient, but it's not a bad way to do this.
So as i found out, its rather complicated making a datetime from a Danish weekday string. A lot easier to go from English and format to Danish.
So i had to make it a bit different, by stitching parts of you guys' answers together.
<?php while ( have_rows('aabningstider_repeater', 'option') ) : the_row();
// vars
$day = get_sub_field('dag');
$timer = get_sub_field('aabne_timer');
$dayLower = strtolower($day);
$weekArray = array('mandag', 'tirsdag', 'onsdag', 'torsdag', 'fredag', 'lørdag', 'søndag' );
$dayArray = preg_split('/[\s,-]+/', $dayLower);
$today = date('w') - 1;
$class = '';
if(is_array($dayArray)){
$days = [];
foreach ($dayArray as $daySingle ) {
array_push($days, array_search($daySingle, $weekArray));
}
if($today > $days[0] && $today < $days[1]){
$class = ' current-day in-between-days';
} elseif($today == $days[0] || $today == $days[1]){
$class = ' current-day';
} else{
$class = '';
}
}
endwhile; ?>
I'm pretty sure this will work untill i one day detect a major flaw. Until then it'll do.
Any feedback or optimization is greatly appreciated. And thanks for all the inputs!
setlocale(LC_ALL, "danish");
$day = get_sub_field('dag'); // the field containing the user input day
$currentDayLower = strtolower(date("D"));
$dayLowercase = strtolower($day);
$class = '';
if($dayLowercase === $currentDayLower)
$class = ' current-day';

Average day of month from list of dates in PHP

I'm trying to find the average date and guess the next one.
The input is a list of dates that looks like this:
$completeDate = array(
'2015-04-13T00:00:00-0800',
'2015-03-20T00:00:00-0800',
'2015-02-17T00:00:00-0800',
'2015-01-10T00:00:00-0800'
);
I'm trying to scan a list of x amount of dates, and output an average of the dates overall.
So in the above example I think the output would be 2015-5-15 is expected average date.
How would I tackle this?
If you're looking for the average of those dates you can simply get the day of the year for each of those dates, average them out, and use that date:
$completeDate = array(
'2015-04-13T00:00:00-0800',
'2015-03-20T00:00:00-0800',
'2015-02-17T00:00:00-0800',
'2015-01-10T00:00:00-0800'
);
$first = null;
$last = null;
foreach($completeDate as $date) {
$dayOfYear = (new DateTime($date))->format('z');
if (is_null($first)) {
$first = $last = $dayOfYear;
}
else {
if ($dt < $first) {
$first = $dayOfYear;
}
if ($dt > $last) {
$last = $dayOfYear;
}
}
}
$avg = round(($first + $last) / 2);
$averageDate = DateTime::createFromFormat('z', $avg);
echo $averageDate->format('Y-m-d'); // 2015-02-26
Demo
If your looking for the average of the day of the month for the dates in that array and then use that day of the next month, you just need to average out the days of the month and then use that with the next month:
$completeDate = array(
'2015-04-13T00:00:00-0800',
'2015-03-20T00:00:00-0800',
'2015-02-17T00:00:00-0800',
'2015-01-10T00:00:00-0800'
);
$month = 0;
$days = 0;
foreach($completeDate as $date) {
$dt = new DateTime($date);
$month_num = $dt->format('n');
if ($month_num > $month) {
$month = $month_num;
}
$days += $dt->format('j');
}
$avg = round($days / count($completeDate));
$date = new DateTime(sprintf('%d-%01d-%01d', $dt->format('Y'), ++$month, $avg));
echo $date->format('Y-m-d'); // 2015-05-15
Demo

Find discontinuities in an array of date in php

I have a sorted array of dates that looks like so :
$dates = Array('2014-10-01','2014-10-01','2014-10-02','2014-10-03','2014-10-05');
In this array we have two times (sometimes more, but it's normal in my script) the date Oct 1st but it's missing the 4th.
How can I detect that there is a discontinuity between the first value of the array, and the last value of the array (again, my array is sorted by ascending dates) ?
$dates = array('2014-10-01','2014-10-01','2014-10-02','2014-10-03','2014-10-05');
$continued = true;
foreach ($dates as $key => $date) {
if (!empty($dates[$key+1])) {
$cDate = new DateTime($date);
$next = new DateTime($dates[$key+1]);
$interval = $next->diff($cDate);
if ($interval->d > 1) {
$continued = false;
}
}
}
$continued = false then discontinuity present.
It's probably worth looking into the DateTime class, and its goodies. In particular DateTime::diff, which returns a DateInterval instance. This enables you to safely subtract 2 dates, and determine what the time difference between those dates actually is. cf the manual.
In short, I've put together a little function that can be used to determine oddities in an array of date strings:
$dates = array('2014-10-01','2014-10-01','2014-10-02','2014-10-03','2014-10-05');
$obj = array();
foreach ($dates as $date)
{//create an array of DateTime instances from the $dates values
$obj[] = new DateTime($date);
}
/**
* #param array $dates (an array of DateTime instances)
* #param string $intervalProperty = 'd' (valid valus are y, m, d, h, i, s)
* #param int $interval = null (the value $intervalProperty should have)
* #return array of null|DateInterval
*/
function getOddDiffs(array $dates, $intervalProperty = 'd', $interval = null)
{
$diffs = array();
for($i=0, $j=count($dates)-1;$i<$j;++$i)
{//iterate $dates
$diff = $dates[$i]->diff($dates[$i+1]);//compute diff
if ($interval === null && $diff->{$intervalProperty})
$interval = $diff->{$intervalProperty};//set $interval if needed/possible
if ($diff->{$intervalProperty} !== $interval)
{//if interval value !== $interval (type+value check required in case $interval is null)
$diffs[] = $diff;//return the diff
}
else
{
$diffs[] = null;//null means ok
}
}
return $diffs;//return results
}
If you want to see it in action:
Demo here
function hasDiscontinuities($dates)
{
// remove duplicates
$dates = array_unique($dates);
// get the last day
$parts = explode('-', end($dates));
$lastDay = (int) $parts[2];
// if the amount of dates is smaller than the last day, there must be discontinuities
if (count($dates) < $lastDay) {
return true;
}
return false;
}
Edit regarding Elias Van Ootegem's comment, duplicates now are threated as discontinuities:
function hasDiscontinuities($dates)
{
// duplicates count as discontinuities
if ($dates != array_unique($dates)) {
return true;
}
// get the last day
$parts = explode('-', end($dates));
$lastDay = (int) $parts[2];
// if the amount of dates is smaller than the last day, there must be discontinuities
if (count($dates) < $lastDay) {
return true;
}
return false;
}
Second edit because of the added details in the comments:
function hasDiscontinuities($dates)
{
// duplicates count as discontinuities
if ($dates != array_unique($dates)) {
return true;
}
$firstDate = new DateTime($dates[0]);
$lastDate = new DateTime(end($dates));
// if the difference in days is not the same as the amount of elements in the array, there are discontinuities
if (count($dates) != $lastDate->diff($firstDate)->days + 1) {
return true;
}
return false;
}

Categories