Calculate opening times with a part of closed time - php

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"
}

Related

Subtract two arrays in PHP using Raspberry

I have two arrays that each contains one mysql select from different days (24h interval). Arrays contains 1440 entries with another sub-array(temp1, temp2, temp3 and date). I tried foreach($array as $key=>$val) but I can't subtract $val with or from another, let's say, $val2. The most legit way to do this is (based on web search):
foreach($yesterdayValues as $key=>$val){
if(substr($key, 0, 5) == 'temp2')
$todayValues[$key] -= $val;
}
But it does not work.
The main idea is to subtract temp2 values from today with the same temp2 values from yesterday in order to see changes in temperature. And display them nicely.
EDIT : Something like that ?
$yesterdayValues['temp1'] = 18.145666122437;
$yesterdayValues['temp2'] = 19.1425666122437;
$yesterdayValues['temp3'] = 20.845666122437;
$difference = array();
$i = 2;
foreach($yesterdayValues as $key=>$val){
if(isset($yesterdayValues['temp'.$i])) {
$difference[$key] = (float)($val - $yesterdayValues['temp'.$i]);
}
$i++;
}
var_dump($difference);
show : array(2) { ["temp1"]=> float(-0.9969004898067) ["temp2"]=> float(-1.7030995101933) }
The answer is this (but it's kind of static for the moment):
for($i=0;$i<1440;$i++){
(float)$yesterdayValues[$i]['temp2'] = (float)$last24hourValues[$i]['temp2'] - (float)$yesterdayValues[$i]['temp2'];
}
There are 1440 entries in 24h, and the key cand be acessed manually in a simple for. Maybe this can work with foreach instead of simple for. I will check later.
may this work for you
$yesterdayValues = array(
array('temp1'=>18.145666122437,'temp2'=>14.875, 'temp3'=>18.104000091553, 'date'=>'2016-02-29 10:47:10'),
array('temp1'=>19.245666022437,'temp2'=>14.875, 'temp3'=>19.104000091553, 'date'=>'2016-02-29 11:47:10'),
array('temp1'=>20.145666122437,'temp2'=>14.875, 'temp3'=>20.104000091553, 'date'=>'2016-02-29 12:47:10'),
) ;
$todaysValues = array(
array('temp1'=>18.145666122437,'temp2'=>12.875, 'temp3'=>18.104000091553, 'date'=>'2016-02-29 10:47:10'),
array('temp1'=>19.145666122437,'temp2'=>14.075, 'temp3'=>19.104000091553, 'date'=>'2016-02-29 11:47:10'),
array('temp1'=>20.145666122437,'temp2'=>17.175, 'temp3'=>20.104000091553, 'date'=>'2016-02-29 12:47:10'),
) ;
$diffArr = array();
foreach ($yesterdayValues AS $key => $dataArr) {
$diffArr[$key] = (float)($dataArr['temp2'] - $todaysValues[$key]['temp2']);
}

Create range of date with exception

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.

What is the most efficient way to parse the specific segments of a URL path?

Say my url is www.example.com/usa/california/redding/
What is the most efficient way to return the following:
$urls = array ( 0 => '/usa/', 1 => '/usa/california/', 2 => '/usa/california/redding/' );
The actual URL will be unknown and the length / number of segments will be unknown.
The most efficient way to do this would be to loop over the string, looking each consecutive / character and then pushing them onto an array as you go. This algorithm will be O(n) assuming string concatenation is also O(n).
$url = "www.example.com/usa/california/redding/";
$next = "";
$urls = array();
// we use the strpos function to get position of the first /
// this let's us ignore the host part of the url
$start = strpos($url, "/");
// just in case PHP uses C strings or something (doubtful)
$length = strlen($url);
// loop over the string, taking one character at a time
for ($i = $start; $i < $length; $i++) {
// append the character to our temp string
$next .= $url[$i];
// skip the first slash, but after that push the value of
// next onto the array every time we see a slash
if ($i > $start && $url[$i] == "/") {
array_push($urls, $next);
}
}
Not too elegant, but this gets the job done:
<?php
$link = 'www.example.com/usa/california/redding/';
$parts = explode('/',$link);
$results = array();
for ($i = 1; $i < count($parts) - 1; $i++) {
$results[] = '/'.implode('/', array_slice($parts, 1,$i)).'/';
}
print_r($results);
?>
To use the regular expression is the first though came to me, but i know it might not be efficient:
$str = 'www.example.com/usa/california/redding/';
$patten = '/(((\/.[0-9A-Za-z]+\/).[0-9A-Za-z]+\/).[0-9A-Za-z]+\/)/';
$ret = preg_match($patten, $str, $matches);
var_export($matches);
the output will be:
array (
0 => '/usa/california/redding/',
1 => '/usa/california/redding/',
2 => '/usa/california/',
3 => '/usa/',
)
first is the whole match, the remain 3 is the capture.

Random But Unique Pairings, with Conditions

I need some help/direction in setting up a PHP script to randomly pair up items in an array.
The items should be randomly paired up each time.
The items should not match themselves ( item1-1 should not pair up with item1-1 )
Most of the items have a mate (ie. item1-1 and item1-2). The items should not be paired with their mate.
I've been playing around with the second script in this post but, I haven't been able to make any progress. Any help is appreciated.
Very simple approach, but hopefully helpful to you:
(mates, if grouped in an array (e.g. array('a1', 'a2')), will not be paired.)
function matchUp($array) {
$result = array();
while($el = array_pop($array)) {
shuffle($array);
if (sizeof($array) > 0) {
$candidate = array_pop($array);
$result[] = array(
array_pop($el),
array_pop($candidate)
);
if (sizeof($el) > 0) {
$array[] = $el;
}
if (sizeof($candidate) > 0) {
$array[] = $candidate;
}
}
else {
$result[] = array(array_pop($el));
}
}
return $result;
}
$array = array(
array('a1', 'a2'),
array('b1', 'b2'),
array('c1'),
array('d1'),
array('e1', 'e2'),
array('f1'),
array('g1', 'g2'),
);
Update:
foreach(matchUp($array) as $pair) {
list($a, $b) = $pair + array(null, null);
echo '<div style="border: solid 1px #000000;">' . $a . ' + ' . $b . '</div>';
}
With the randomness, there is no guarantee that a full correct solution will be reached.
Certain problem sets are more likely to be solved than others. Some will be impossible.
You can configure how many times it will try to achieve a good solution. After the specified number of tries it will return the best solution it could find.
function pairUp (array $subjectArray) {
// Config options
$tries = 50;
// Variables
$bestPaired = array();
$bestUnpaired = array();
for($try = 1; $try <= 50; $try++) {
$paired = array();
$unpaired = array();
$toBePaired = $subjectArray;
foreach($subjectArray as $subjectIndex => $subjectValue) {
// Create array without $thisValue anywhere, from the unpaired items
$cleanArray = array();
foreach($toBePaired as $index => $value) {
if($value != $subjectValue) {
array_push($cleanArray, array(
'index' => $index,
'value' => $value
));
}
}
sort($cleanArray); // reset indexes in array
// See if we have any different values left to match
if(count($cleanArray) == 0) {
array_push($unpaired, $subjectValue);
continue;
}
// Get a random item from the clean array
$randomIndex = rand(0,count($cleanArray)-1);
// Store this pair
$paired[$subjectIndex] = $subjectValue . '-' . $cleanArray[$randomIndex]['value'];
// This item has been paired, remove it from unpairedItems
unset($toBePaired[$cleanArray[$randomIndex]['index']]);
sort($toBePaired);
}
// Decide if this is our best try
if(count($paired) > count($bestPaired)) {
$bestPaired = $paired;
$bestUnpaired = $unpaired;
}
// If we had no failures, this was a perfect try - finish
if(count($unpaired) == 0) { $break; }
}
// We're done, send our array of pairs back.
return array(
'paired' => $bestPaired,
'unpaired' => $bestUnpaired
);
}
var_dump(pairUp(array('a','b','c','d','e','a','b','c','d','e')));
/*
Example output:
array(2) {
["paired"]=>
array(10) {
[0]=>
string(3) "a-b"
[1]=>
string(3) "b-c"
[2]=>
string(3) "c-d"
[3]=>
string(3) "d-e"
[4]=>
string(3) "e-a"
[5]=>
string(3) "a-b"
[6]=>
string(3) "b-e"
[7]=>
string(3) "c-d"
[8]=>
string(3) "d-c"
[9]=>
string(3) "e-a"
}
["unpaired"]=>
array(0) {
}
}
*/
Case 1: if all elements had a mate
If all elements had a mate, the following solution would work, although I don't know if it would be perfectly random (as in, all possible outputs having the same probability):
Shuffle the list of elements, keeping mates together
original list = (a1,a2),(b1,b2),(c1,c2),(d1,d2)
shuffled = (c1,c2),(d1,d2),(a1,a2),(b1,b2)
Shift the second mate to the right. The matches have been formed.
shifted = (c1,b2),(d1,c2),(a1,d2),(b1,a2)
(Edit1: if applied exactly as described, there is no way a1 ends up matched with b1. So, before shifting, you may want to throw a coin for each pair of mates to decide whether they should change their order or not.)
Case 2: if only some elements have a mate
Since in your question only some elements will have a mate, I guess one could come up with the following:
Arbitrarily pair up those elements who don't have a mate. There should be an even number of such elements. Otherwise, the total number of elements would be odd, so no matching could be done in the first place.
original list = (a1,a2),(b1,b2),c1,d1,e1,f1 // c1,d1,e1 and f1 don't have mates
list2 = (a1,a2),(b1,b2),(c1,d1),(e1,f1) // pair them up
Shuffle and shift as in case 1 to form the matches.
shuffled = (e1,f1),(a1,a2),(c1,d1),(b1,b2)
shifted = (e1,b2),(a1,f1),(c1,a2),(b1,d1)
Again, I don't know if this is perfectly random, but I think it should work.
(Edit2: simplified the solution)
(Edit3: if the total number of elements is odd, someone will be left without a match, so pick an element randomly at the beginning to leave it out and then apply the algorithm above).

Parsing Text File

I need an advice.
I need to scrap and parse text file (using for currency exchange rates). Here we are, a small snippet from this file:
c057z110323
h057z110323
a057z110323
b012z110323
c058z110324
h058z110324
a058z110324
c059z110325
h059z110325
a059z110325
c060z110328
h060z110328
a060z110328
c061z110329
h061z110329
a061z110329
c062z110330
h062z110330
a062z110330
b013z110330
c063z110331
h063z110331
a063z110331
c064z110401
h064z110401
a064z110401
c065z110404
h065z110404
a065z110404
c066z110405
h066z110405
a066z110405
c067z110406
h067z110406
a067z110406
b014z110406
c068z110407
h068z110407
a068z110407
c069z110408
h069z110408
a069z110408
As you may see there's a lot of lines (in original file there are about 80000 of lines (few lines per day are being added).
String format is as following:
A000112233
where
A - type
000 - number of the file (created this year)
11 - year
22 - month
33 - day
I'm getting 25 latest lines from from the file using following snippet:
$file = "http://www.nbp.pl/kursy/xml/dir.txt";
$data = file($file);
$count = count($data);
for($i = $count - 25; $i < $count; $i++)
{
if( substr($data[$i], 0, 1) === 'a' )
{
$latest[] = $data[$i];
}
}
I need to get only lines starting with "a". The output array looks as following:
array(8) {
[0]=>
string(13) "a062z110330
"
[1]=>
string(13) "a063z110331
"
[2]=>
string(13) "a064z110401
"
[3]=>
string(13) "a065z110404
"
[4]=>
string(13) "a066z110405
"
[5]=>
string(13) "a067z110406
"
[6]=>
string(13) "a068z110407
"
[7]=>
string(13) "a069z110408
"
}
Now I need to compare every array element to get the latest item from the latest working day before current date. I'm acheiving it this way:
$i = 1;
foreach($latest as $row)
{
$plural = ($i > 1) ? 's' : null;
if( substr(trim($row), -6) === date("ymd", strtotime("-" . $i . " day" . $plural) )
{
$filename = $row;
break;
}
$i++;
}
It's working quite OK, however I'm facing one big problem. I'm unable to sort $latest array by the latest six characters. I tried doing this using sort(), rsort(). None of them worked good for me.
Can anybody help me with this issue or maybe has better approach to do what I'm looking for.
When you do
for($i = $count - 25; $i < $count; $i++)
{
if( substr($data[$i], 0, 1) === 'a' )
{
$latest[] = $data[$i];
}
}
use date as a key in $latest array:
for($i = $count - 25; $i < $count; $i++)
{
if( substr($data[$i], 0, 1) === 'a' )
{
$key = (int) substr($data[$i], -6);
$latest[$key] = $data[$i];
}
}
Then you can sort by key like:
ksort($latest);
You need to use a custom sort method. You can use usort to write your own comparison function : http://php.net/manual/en/function.usort.php
From the manual
function cmp($a, $b) {
if ($a == $b) {
return 0;
}
return ($a < $b) ? -1 : 1;
}
$a = array(3, 2, 5, 6, 1);
usort($a, "cmp");
The comparison function must return an
integer less than, equal to, or
greater than zero if the first
argument is considered to be
respectively less than, equal to, or
greater than the second.
Since you're only asking how to sort an array of strings by their last six characters:
Use usort:
function sortfunc($a, $b) {
return strcmp(substr($a, -6), substr($b, -6));
}
usort($latest, 'sortfunc');
You may need to trim() your lines first or the newlines and/or carriage return characters will be part of the last 6 characters.

Categories