Create range of date with exception - php

Having two arrays of date (mm/dd/yyyy)
array(2) {
["useless_range"]=>
array(4) {
[0]=>
string(10) "08/15/2014"
[1]=>
string(10) "08/30/2014"
[2]=>
string(10) "09/10/2014"
[3]=>
string(10) "09/20/2014"
}
["range"]=>
array(2) {
[0]=>
string(10) "08/01/2014"
[1]=>
string(10) "10/31/2014"
}
}
Consider that the array range_useless is holiday, strike, or anything like this. It's sorted already.
How can I get a useful range from the given array range?
For that example, I want the range to be
'08/01/2014 to 08/14/2014 **and** 08/31/2014 to 09/09/2014 **and** 09/21/2014 to 10/31/2014'
My first solution was create an array of all dates between the ranges and then create the full string looping using in_array, but I think there might exists a better solution
Code that create all dates between all ranges
$strDateFrom = '2014/08/15';
$strDateTo = '2014/08/30';
// takes two dates formatted as YYYY-MM-DD and creates an
// inclusive array of the dates between the from and to dates.
$aryRange=array();
$iDateFrom=mktime(1,0,0,substr($strDateFrom,5,2), substr($strDateFrom,8,2),substr($strDateFrom,0,4));
$iDateTo=mktime(1,0,0,substr($strDateTo,5,2), substr($strDateTo,8,2),substr($strDateTo,0,4));
if ($iDateTo>=$iDateFrom) {
array_push($aryRange,date('Y-m-d',$iDateFrom)); // first entry
while ($iDateFrom<$iDateTo) {
$iDateFrom+=86400; // add 24 hours
array_push($aryRange,date('Y-m-d',$iDateFrom));
}
}
print_r($aryRange);

Interesting challenge :) ! So I tried something :
$myDates = array(
"useless_range" => array(
"01/08/2015",
"01/15/2015",
"09/10/2015",
"09/20/2015"
),
"range" => array(
"08/01/2015",
"10/31/2015"
)
);
// results of strotime
$dateStart = 1420066800; //01/01/2015
$dateTo = 1422658800; // 31/01/2015
$tab = $myDates['useless_range'];
$len = count($tab);
$result = array();
$previous = $dateStart;
for($i = 0; $i < $len; $i++){
$position = $tab[$i]; // we get the current element
$iPosition = strtotime($position); // strotime it
if($previous < $iPosition && $iPosition < $dateTo){ // if the useless date is in the range
if($i % 2 == 0){ //if start of range
$result[] = array("start" => date("Y-m-d", $previous + 3600*24), "end" => date("Y-m-d", $iPosition));
}
$previous = $iPosition;
}
else { // the date is out of range, we finish the result and leave the loop
$result[] = array("start" => date("Y-m-d", $previous), "end" => date("Y-m-d", $dateTo + 3600*24));
break; //end of search
}
}
print_r($result);
The output result :
Array
(
[0] => Array
(
[start] => 2015-01-01
[end] => 2015-01-08
)
[1] => Array
(
[start] => 2015-01-15
[end] => 2015-01-31
)
)
Now to create the sentence :
$sentences = array();
foreach($result as $elem){
$sentences[] = $elem['start']." to ".$elem['end'];
}
echo implode(' **and** ', $sentences);

OK, this is not very different from what you've got but it's another point of view and I'm using the strtotime() function instead of mktime() which at least looks nicer.
I'm assuming the inputs have been validated and I have ignored the corner cases such as when the start date of the desired range/period is inside the first 'useless' range, or the end date is in a 'useless' range, but this could be easily verified if necessary.
I just wanted you to share how I would go for solving this problem.
Here's my code:
<?php
$uselessRanges = [ "08/15/2014", "08/30/2014", "09/10/2014", "09/20/2014" ];
$range = [ "08/01/2014", "10/31/2014" ];
// Convert to timestamps for easier manipulation
$rangeTs = array_map( 'strtotime', $range );
$uselessRangesTs = array_map( 'strtotime', $uselessRanges );
// Let the first date be the start of the whole range
$finalResult = [ array_shift( $rangeTs ) ];
/**
* Assuming that the useless ranges array is of the following format:
* $useless = [ 'start_useless_range_1', 'end_useless_range_1', 'start_useless_range_2', 'end_useless_range_2', ... ]
*
* For each of the useless range entries we decide whether to take the previous or the next day based on the parity of the index
*/
for( $i = 0; $i < count( $uselessRangesTs); $i++ ){
// Whether we should take the previous or the next day
$diff = $i % 2 === 0 ? -86400 : 86400;
$finalResult[] = $uselessRangesTs[$i] + $diff;
}
// Finally add the end of the whole range
$finalResult[] = array_shift( $rangeTs );
foreach( $finalResult as $date ){
echo date('m/d/Y', $date) . '<br />';
}

Assuming you only need to generate a string, and the dates are already validated and in order, I wouldn't bother with date functions. Here is a quick example of how I would do it:
$dates = array(
"useless_range" => array(
"08/15/2014",
"08/30/2014",
"09/10/2014",
"09/20/2014"
),
"range" => array(
"08/01/2015",
"10/31/2015"
)
);
$str = array();
for ( $i = 0; $i < sizeof($dates['useless_range']); $i += 2 ) {
$str[] = sprintf('%s and %s', $dates['useless_range'][$i], $dates['useless_range'][$i+1]);
}
array_unshift($str, $dates['range'][0]);
$str[] = $dates['range'][1];
$str = implode(' to ',$str);
echo $str;
// 08/01/2015 to 08/15/2014 and 08/30/2014 to 09/10/2014 and 09/20/2014 to 10/31/2015
Explanations:
The general idea is to build a sentence, so I do not bother with dates or range or anything. Just imagine words that need to be pieced together. The final result needs to be :
"range0 to useless_range0 and useless_range1 to useless_range2 and useless_range3 to range1"
This can be broken down like this:
[range0] to [useless_range0 and useless_range1] to [useless_range2 and useless_range3] to [range1]
This can be achieved very easily with implode using " to " as a separator. We just need to generate the array with range0 as first element, range1 as last element, and every piece of sentence (two consecutives dates + " and ") in between. This is a simple loop + array_shift/unshift.
Note: if your dates are not validated, or if you need to check for overlap, or anything fancy like that, then you will very probably have to build an array of dates like you are doing.

Related

How to associate and filter two related arrays?

I have two arrays and I want to link together when processing them.
$dat = array(
"2020-02-01",
"2020-02-05",
"2020-02-10",
"2020-02-12",
"2020-02-15"
);
$word = array(
"Attend To,Explore,Unaided,dull,bad"
);
//User input
$start = "2020-01-01";
$end = "2020-02-07";
I want the input to affect the second array too, so when the first array gets its result from first 2, then the second array too should have it from the first 2.
//Filter out dates between start and end date
$result = array_filter($dat, function($data_item) use($start, $end) {
return $data_item >= $start && $data_item <= $end;
});
and the result is
Array
(
[0] => 2020-02-01
[1] => 2020-02-05
)
I want it to be able to link $dat and $word so that the result for word too will be
Array
(
[0] => Attend To
[1] => Explore
)
I don't find functional programming to be very readable/attractive for this case. Just use a simple foreach loop and conditionally grab the words related by the shared indices.
Since the two arrays share common indices, it is unnecessary work to combine the two arrays -- just reference the indices.
Code: (Demo)
$dat = ["2020-02-01", "2020-02-05", "2020-02-10", "20-02-12", "2020-02-15"];
$word = ["Attend To,Explore,Unaided,dull,bad"];
$words = explode(',', $word[0]);
//User input
$start = "2020-01-01";
$end = "2020-02-07";
$result = [];
foreach ($dat as $index => $date) {
if ($date >= $start && $date <= $end) {
$result[] = $words[$index];
}
}
var_export($result);
Output:
array (
0 => 'Attend To',
1 => 'Explore',
)
The original keys will be maintained after array_filter, so get the entries for keys that are the same by computing the intersection. It appears that $word is a one element array with a string, so just explode it:
$word_result = array_intersect_key(explode(',', $word[0]), $result);
See a Demo.
If one of the arrays has unique values, you can combine the array and just operate on that.
$comb = array_combine(explode(',', $word[0]), $dat);
$result = array_filter($comb, function($data_item) use($start,$end) {
return $data_item >= $start && $data_item <= $end;
});
This yields:
Array
(
[Attend To] => 2020-02-01
[Explore] => 2020-02-05
)
You can use the array as is or use array_keys to get the keys as the $word array.
If it's not guaranteed to be $word[0] then you can use reset($word) or current($word).
A possible solution assuming that the arrays have the same keys (I changed it to reflect this), is to use the constant ARRAY_FILTER_USE_BOTH to array_filter, so that the key is available in the callback function.
here i fill a second array $result2 with the words while filtering the data (note that things are added in use and $result2 is passed by reference):
$dat = array("2020-02-01","2020-02-05","2020-02-10","20-02-12","2020-02-15");
$word = array("Attend To","Explore","Unaided","dull","bad");
//User input
$start = "2020-01-01";
$end = "2020-02-07";
//Filter out dates between start and end date
$result2 = [];
$result = array_filter($dat, function($data_item, $key) use($start, $end, $word, &$result2) {
if($data_item >= $start && $data_item <= $end){
$result2[$key] = $word[$key];
return true;
}
return false;
}, ARRAY_FILTER_USE_BOTH);
AbraCadaver's answer is perfect fit when only the filtering is needed, but this can be useful in case someone has to do extra operations in the filter callback..

Calculate opening times with a part of closed time

This is what is given to me:
$openinghours = ['09:00-18:00'];
Now there can be multiple pieces of "closed time", for example:
$closed[] = '11:30-12:15';
$closed[] = '16:00-16:30';
I need to get the new opening times, like so:
$openinghours = ['09:00-11:30,'12:15-16:00','16-30-18:00'];
So it now has the gaps of the closed time in it.
Where do I start? I'm quite at loss on how to calculate this and get the expected result.
By exploding all time ranges on hyphens, you can manually piece together the open/close times.
My solution may or may not be robust enough for your project data. My solution performs no validation, doesn't sort the closed spans, and doesn't check if a span matches an open/close time OR exceeds the open/close time. My snippet is relying heavily on trustworthy data.
Code: (Demo)
$openinghours = ['09:00-18:00'];
$closed[] = '11:30-12:15';
$closed[] = '16:00-16:30';
[$start, $end] = explode('-', $openinghours[0]);
foreach ($closed as $span) {
[$shut, $reopen] = explode('-', $span);
$result[] = $start . '-' . $shut;
$start = $reopen;
}
$result[] = $start . '-' . $end;
var_export($result);
Output:
array (
0 => '09:00-11:30',
1 => '12:15-16:00',
2 => '16:30-18:00',
)
If your php version doesn't support array destructuring, you can call list() with explode().
This might not be the quickest way to achieve this, but you can see it working here.
$openingHours = ['09:00-18:00'];
$closedHours = ['11:30-12:15', '16:00-16:30'];
$openStart = explode("-", $openingHours[0])[0]; # 09:00
$openFinish = explode("-", $openingHours[0])[1]; # 18:00
$hours = [];
foreach($closedHours as $closed)
$hours[] = explode('-', $closed);
$dynamicHours = ["{$openStart}-"];
for($i = 0; $i <= count($hours) -1; $i++) {
$dynamicHours[$i] .= $hours[$i][0];
$dynamicHours[] = "{$hours[$i][1]}-";
}
$dynamicHours[count($dynamicHours) -1] .= $openFinish;
var_dump($dynamicHours);
This gives you an output of
array(3) {
[0]=>
string(11) "09:00-11:30"
[1]=>
string(11) "12:15-16:00"
[2]=>
string(11) "16:30-18:00"
}

sort csv data by date and time columns using php

Background:
I have a php script that reads a csv file. This csv file contain columns. The last two column are date and time
What I need to do is order this data by date and time columns in ascending order
My PHP script:
$data = file("myfile.csv");
$string = "";
for($i = 1; $i < count ( $data ); $i ++)
{
$info1 = str_getcsv($data [$i],",",'');
$string .= $info1 [4] . "," ; //colomns 1 in position 4 in csv file
$string .= $info1 [6] . "," ; //colomns 2 in position 6 in csv file
$string .= $info1 [7] . "," ; //colomns 3 in position 7 in csv file
//colomns 4 --this colomn contain date like this format (2/8/2016)
$string .= $info1 [2] . "," ;
//colomns 5 --this colomn contain timelike this format (12:30 AM)
$string .= $info1 [10];
$string .= "\n";
}
this generates the following:
mark,mark,456345,5/10/2016,9:00 AM
mordl,mordl,23564,5/10/2016,1:00 PM
corten,corten,3216589,5/10/2016,12:00 PM
jack,jack,123645,5/10/2016,8:00 AM
olemn,olemn,29845155,5/10/2016,2:00 PM
jab,jab,457362,5/10/2016,10:45 AM
monk,monk,326251,5/10/2016,3:00 PM
I need it to be sorted like so:
jack,jack,123645,5/10/2016,8:00 AM
mark,mark,456345,5/10/2016,9:00 AM
jab,jab,457362,5/10/2016,10:45 AM
corten,corten,3216589,5/10/2016,12:00 PM
mordl,mordl,23564,5/10/2016,1:00 PM
olemn,olemn,29845155,5/10/2016,2:00 PM
monk,monk,326251,5/10/2016,3:00 PM
What can I do to achieve this?
Use usort:
usort() is a function native to PHP that is used for sorting PHP arrays. It can be used to help you.
Firstly in order to use usort(), you need an array, so you need to push the data into an array first instead of pushing it to a string.
Setting up an Array:
This part is easy. All you need to do is initialize an array and push the data into it 2 dimensionally with array_push()
Start the script like this:
$data = file( "myfile.csv" );
$first_dimension = array();
$second_dimension = array();
for($i = 1; $i < count ( $data ); $i ++)
{
$info1 = str_getcsv( $data[ $i ], "," , '' );
$second_dimension = array( $info1[4], $info1[6], $info1[7], $info1[2], $info1[10] );
array_push( $first_dimension, $second_dimension );
}
Explanation
The $first_dimension array will contain all of the data, and we will use this array when sorting the data
The $second_dimension array will temporarily contain all data from one row. For each iteration in the for loop second_dim will be update for the current row
The $second_dim = array() assignment inside the for loop will make an array out of the data in the 4th, 6th, 7th, 2nd, and 10th element in $info1 respectively
The array_push( $first_dimension, $second_dimension ) inside the for loop adds the $second_dim as an element of $first_dim, therefore creating a 2-dimensional array.
The point of 2 dimensions is so that there is a physical separation between each row, so when we go to sort the $first_dimension array, we will have rows to actually sort.
It will also come in handy when pushing new line characters to the string output!
Prepare for Sorting by Defining a Compare Function:
usort() requires 2 arguments: an array to sort, and a callback comparing function used for determining the order.
The comparing function needs to return a negative number for a preceding element and a positive number for a succeeding element. This means that when comparing 2 rows, the row that comes first needs to be a negative number, because it precedes the row that comes second, and vice versa
This would be a good comparator to use in your script (add it after the for loop):
function compare($row_a, $row_b)
{
$date_a = date_create( $row_a[3] ) //$info1[2]
$date_b = date_create( $row_b[3] ) //$info1[2]
$time_a = date_create( $row_a[4] ) //$info1[10]
$time_b = date_create( $row_b[4] ) //$info1[10]
$day_diff = intval( date_diff( $date_a, $date_b )->format( "%R%a" ) )
$hour_diff = intval( date_diff( $time_a, $time_b )->format("%R%h"))
$min_diff = intval( date_diff( $time_a, $time_b )->format("%R%i"))
$time_diff = $hour_diff * 60 + $min_diff;
if( datediff !== 0 ){
//if $date_b is a day later than $date_a, then $day_diff will be positive
//otherwise $day_diff will be negative
return $day_diff;
}else{
//if $time_b is a time later than $time_a, then $time_diff will be positive
//otherwise $time_diff will be negative
//if the dates and times are the same, it will return 0
return $time_diff;
}
}
Dissecting this Comparator:
In this comparator, we are comparing 2 rows inside $first_dimension side by side and determining if one should precede or succeed the other.
We do this by obtaining the dates (which are in column 3) and times (which are in column 4) from that row.
We then use PHP's date_diff() function to compare each of the times.
Note: date_diff() requires the arguments to be php date objects; we create those with the date_create() function.
Sort with usort:
API:
usort ( $array , $comparator_name )
Implementation:
usort( $first_dim, "compare" );
Dumping the Array to a String:
To dump the contents, just use PHP's native implode() function to concatenate the elements of each row into a single string with each element separated by commas
var $string = "";
for($i = 1; $i < count ( $first_dimension ); $i ++)
{
$string .= implode( ",", $first_dimension[$i] );
$string .= "\n"
}

nearest date in php strtotime

I have several date(strtotime) in a Variable and want the first nearest date that is after the specified date(my date) with php. what do i do?
Variable:
$varD = "1481691600,1482642000,1482037200";
my date:
1481778000 => (2016-12-15)
several date(strtotime):
1481691600 => (2016-12-14)
1482642000 => (2016-12-25)
1482037200 => (2016-12-18) //result
result:
1482037200 => (2016-12-18)
$varD = "1481691600,1482037200,1482642000";
$myDate = "1481778000";
After you explode the string of timestamps ($varD), you can filter them and return the minimum value of the result. Here is one way to do that using array_filter and min.
$comp = function($x) use ($myDate) { return $x > $myDate; };
$firstDateAfterYours = min(array_filter(explode(',', $varD), $comp));
But if you already know that the timestamps in the string will be in ascending order, it will probably be faster not to convert the whole thing to an array and sort through it. You can use strtok to go through it piece by piece and just stop as soon as you get to a timestamp larger than your target.
$ts = strtok($varD, ',');
while ($ts !== false) {
$ts = strtok(',');
if ($ts > $myDate) break;
}
$firstDateAfterYours = $ts;

Double explode a text field containing date and time in PHP

I have a lengthy text field containing multiple date and time separated by comma. Following is the example
2013-08-26 10:00:00,2013-08-26 15:00:00,2013-08-26 20:00:00
2013-08-27 01:00:00,2013-08-27 06:00:00,2013-08-27 11:00:00
2013-08-27 16:00:00,2013-08-27 21:00:00,2013-08-28 02:00:00
2013-08-28 07:00:00,2013-08-28 12:00:00,2013-08-28 17:00:00
From the above example I need the following structure
Step 1: Split comma separated values into one array.
Step 2: From the above obtained array push all 2013-08-26 dates to one array, the 2013-08-27 to another array and then 2013-08-28.
I successfully exploded the main text to one array but did not succeed to obtain list of values of one particular date.
foreach (explode(PHP_EOL, $dates) as $line)
{
foreach (explode(',', $line) as $dt)
{
$dateVal = date('Y-m-d', strtotime($dt));
switch ($dateVal)
{
case '2013-08-26':
$arr1[] = $dt;
break;
case '2013-08-27':
$arr2[] = $dt;
break;
case '2013-08-28':
$arr3[] = $dt;
break;
}
}
}
Online demo
$dates = "2013-08-26 10:00:00,2013-08-26 15:00:00,2013-08-26 20:00:00
2013-08-27 01:00:00,2013-08-27 06:00:00,2013-08-27 11:00:00
2013-08-27 16:00:00,2013-08-27 21:00:00,2013-08-28 02:00:00
2013-08-28 07:00:00,2013-08-28 12:00:00,2013-08-28 17:00:00";
$dates = explode("\n", $dates);
foreach($dates as $date) {
$date = explode(",", $date);
var_dump($date);
// i.e. with $date[1] I will get the middle date of particular line.
}
$date inside foreach, as you can see becomes an array (after exploding) containing all dates separated by commas in one line.
it's been a while since the last time I used PHP, but this is my rusty solution:
$map = array();
$records = explode(',', $input);
foreach( $records as $record )
{
$a = explode(" ", $record);
if(!array_key_exists( $a[0], $map ))
$map[$a[0]] = array();
$map[$a[0]][] = $a[1];
}
var_dump($map);
I haven't tested it, sorry. But it might work...
it should give you something like:
array(2)
(
['2014-03-14'] => array(2)
(
[0] => '04:03:24'
[1] => '04:03:25'
)
['2014-03-14'] => array(2)
(
[0] => '04:03:24'
[1] => '04:03:25'
)
)

Categories