Date Range Google Chart Tools - php

I'm trying to display data on a line graph using Google Charts. The data displays fine, however I would like to set a date range to be displayed.
The data is sent from the database in a JSON literal format:
{
"cols": [
{"label": "Week", "type": "date"},
{"label": "Speed", "type": "number"},
{"type":"string","p":{"role":"tooltip"}},
{"type":"string","p":{"role":"tooltip"}},
{"type":"string","p":{"role":"tooltip"}},
{"type":"string","p":{"role":"tooltip"}},
],
"rows": [
{"c":[{"v": "Date('.$date.')"},{"v": null},{"v": null},{"v": null},{"v": null},{"v": null}]},
{"c":[{"v": "Date('.$date.')"},{"v": null},{"v": null},{"v": null},{"v": null},{"v": null}]}
]
}
Data is either displayed by week or month (null for easy reading) for example this week:
2012, 02, 06
2012, 02, 07
2012, 02, 09
Data isn't set for everyday of the week, therefore in this example only the dates above are shown. What I would like to be shown is the start of the week (2012, 02, 06) to the end of the week (2012, 02, 12) similar to the third example here.
I managed to get the whole week showing by checking if the date exists in the database and if not append an extra row will null data, this however meant the line was not continuous and the dates where not in order.
Could anyone offer any advice on how to I could go about doing this?
Thanks!

Did you try leaving the missing dates be missing dates (i.e. let the database return 2 values instead of 7)?
The continuous axis should handle missing dates, you just need to set the axis range from start to end of the week.
UPDATE
for interactive line chart the axis ranges can be set like this (as inspired by this thread):
hAxis: {...
viewWindowMode: 'explicit',
viewWindow: {min: new Date(2007,1,1),
max: new Date(2010,1,1)}
...}
see http://jsfiddle.net/REgJu/

"I managed to get the whole week showing by checking if the date exists in the database and if not append an extra row will null data, this however meant the line was not continuous and the dates where not in order."
I think you are on the right track you just need to do it in a slightly different way. I have the function like the below to make data continuous.
$data = array(
1 => 50,
2 => 75,
4 => 65,
7 => 60,
);
$dayAgoStart = 1;
$daysAgoEnd = 14;
$continuousData = array();
for($daysAgo=$daysAgoStart ; $daysAgo<=$daysAgoEnd ; $daysAgo++){
if(array_key_exists($daysAgo, $data) == true){
$continuousData[$daysAgo] = $data[$daysAgo];
}
else{
$continuousData[$daysAgo] = 0;
}
}
continuousData will now hold:
$data = array(
1 => 50,
2 => 75,
3 => 0,
4 => 65,
5 => 0,
6 => 0,
7 => 60,
8 => 0,
9 => 0,
10 => 0,
11 => 0,
12 => 0,
13 => 0,
14 => 0,
);
in that order, and then the data can be used in the charts without any gaps.

Perhaps you can use a different chart type? Dygraphs looks like it might be helpful.
Otherwise you may have to write your own custom chart type.

Related

Yii2 - Kartik Widget-Rating overwrite values

it's possible overwrite the values of each star?
i need define step = 3.75, min = 0 and max = 15, and prevent from selecting half a star.
the values I want are:
star 1 => 0; star 2 => 3.75; star 3 => 7.5; star 4 => 11,25; star 5
=> 15.
form.php
echo $form->field($model, 'rating')->widget(StarRating::classname(), [
'pluginOptions' => [
'stars' => 5,
'step' => 3.75,
'min' => 0,
'max' => 15,
]
]);
but when i make this, the selection of each star not display correctly, half of star is selected.
I thought you only want to display the client side (with manual numbers). And you will not get numbers from DB
the correct way is to overwrite the star-rating file. Which should be referred to the Kartik forum.
You must overwrite that the first star has a zero score (highlight first star = 0)
However, you can use the following code in your model.
public function beforeSave($insert)
{
// if ($insert) { // only for Save (No Update)
if (!empty($this->Your_field)) {
$this->setAttribute('Your_field', $this->Your_field-3.75);
}
// }
return parent::beforeSave($insert);
}
Instead of your_field, enter your field name (field of the rate).
You have made the settings incorrectly.
Refer the plugin documentation and demos for details.
quotation krajee:
The logic for highlighting stars depends on the stars, min, max, and
step configurations. The percentage of each star to be highlighted for
each step, will be evaluated using the following expression:
STAR_HIGHLIGHT_PERCENT = (max - min) * step * 100 / stars
For example:
If min = 0, max = 5, step = 0.5, and stars = 5, then
STAR_HIGHLIGHT_PERCENT will evaluate to 50% of each star for each
step.
If min = 1, max = 5, step = 0.5, and stars = 5, then
STAR_HIGHLIGHT_PERCENT will evaluate to 40% of each star for each
step.
So, for example 2 above, the stars will not be completely highlighted
as desired. It is therefore important you set the configuration of
stars, min, max, and step correctly.
Refer the plugin documentation and demos for details.
for You:
'stars' => 4,
'step' => 3.75,
'min' => 0,
'max' => 15,
or
echo StarRating::widget(['name' => 'rating',
'pluginOptions' => [
'stars' => 5,
'step' => 3.75,
'min' => 0,
'max' => 18.75,
'starCaptions' => new JsExpression("function(val){return val-3.75 + ' hearts';}")
]
]);
1
If your field values are integers 1 to 5
To display half stars and full stars, You must use round numbers.
After putting the numbers in the following formula:
(max - min) * step * 100 / stars
Equal to: 50% or 100%
And to display only a full star, Equal to: 100%
2
However, it is better that the rate and step are equal.
For example, if step is 2, is better than max equal to 10 (5 Stars)
3
All of this varies according to the value of your field:
for example:
values: .5, 1, 1.5 , ... 5
Default (stars=5,max=5,step=0.5)
values: 1, 2, 3 , ... 5
(stars=5,max=5,step=1)
values: 1, 2, 3 , ... 12
(stars=6,max=12,step=1) => Half star = one point
values: 2, 4, 6 , ... 12
(stars=6,max=12,step=1) => one star = 2 point

Symfony/Doctrine/MongoDB Get every Nth item

I'm having a dataset which contains datapoints for every 5 seconds per day. This would result in a dataset of 17280 items a day.
This set is way too big and i want it smaller (i'm using these items to draw a graph).
Since the graph's x-axis is over time i decided a gap of 5 minutes per datapoint is good enough. This will return into 288 datapoints a day. A lot less and good enough to make a graph.
My MongoCollection looks like this:
{
"timestamp":"12323455",
"someKey":123,
"someOtherKey": 345,
"someOtherOtherKey": 6789
}
The data gets posted every 5 seconds into the database. So the timestamp will differ 5 seconds for each result.
As my x-axis is divided in 5 minutes sequences I'd love to calculate the average values of someKey, someOtherKey and someOtherOtherkey over these 5 minutes.
This new average will be one of the datapoints in my graph.
How would one get all the datapoints from 1 day with each average 5 minutes apart from eachother? (288 datapoints per day).
As for now i'm selecting every document from midnight this day:
$result = $collection
->createQueryBuilder()
->field('timestamp')->gte($todayMidnight)
->sort('timestamp', 'DSC')
->getQuery()
->execute();
How would one filter this list of data (within the same query) to get the datapoints for every 5 minutes (and the datapoint being an average of the points within these 5 minutes)?
It would be nice to have this query built with doctrine as i'll need it in my symfony application.
EDIT
I've tried to get my query first within the mongoshell working.
As in the comments suggested i should start using aggregation.
The query i've made so far is based upon another question asked here at stackoverflow
This is the current query:
db.Pizza.aggregate([
{
$match:
{
timestamp: {$gte: 1464559200}
}
},
{
$group:
{
_id:
{
$subtract: [
"$timestamp",
{"$mod": ["$timestamp", 300]}
]
},
"timestamp":{"$first":"$timestamp"},
"someKey":{"$first":"$someKey"},
"someOtherKey":{"$first":"$someOtherKey"},
"someOtherOtherKey":{"$first":"$someOtherOtherKey"}
}
}
])
This query will give me the last result for each 300 seconds (5 minutes) from today Midnight.
I want it to get all documents within those 300 seconds and calculate an average over the columns someKey, someOtherKey, someOtherOtherKey
So if we take this example dataset:
{
"timestamp":"1464559215",
"someKey":123,
"someOtherKey": 345,
"someOtherOtherKey": 6789
},
{
"timestamp":"1464559220",
"someKey":54,
"someOtherKey": 20,
"someOtherOtherKey": 511
},
{
"timestamp":"1464559225",
"someKey":654,
"someOtherKey": 10,
"someOtherOtherKey": 80
},
{
"timestamp":"1464559505",
"someKey":90,
"someOtherKey": 51,
"someOtherOtherKey": 1
}
The query should return 2 rows namely:
{
"timestamp":"1464559225",
"someKey":277,
"someOtherKey": 125,
"someOtherOtherKey": 2460
},
{
"timestamp":"1464559505",
"someKey":90,
"someOtherKey": 51,
"someOtherOtherKey": 1
}
The first result is calculated like this:
Result 1 - someKey = (123+54+654)/3 = 277
Result 1 - someOtherKey = (345+20+10)/3 = 125
Result 1 - someOtherOtherKey = (6789+511+80)/3 = 2460
How would one make this calculation within the mongoshell with the aggregation function?
Based on the given answeres here on stackoverflow i've managed to get exactly what i wanted.
This is the big aggregation query i have to make to get all my results back:
db.Pizza.aggregate([
{
$match:
{
timestamp: {$gte: 1464559200}
}
},
{
$group:
{
_id:
{
$subtract: [
'$timestamp',
{$mod: ['$timestamp', 300]}
]
},
timestamp: {$last: '$timestamp'},
someKey: {$avg: '$someKey'},
someOtherKey: {$avg: '$someOtherKey'},
someOtherOtherKey: {$avg: '$someOtherOtherKey'}
}
},
{
$project:
{
_id: 0,
timestamp: '$timestamp',
someKey: '$someKey',
someOtherKey:'$someOtherKey',
someOtherOtherKey:'$someOtherOtherKey'
}
}
])
The Match part is for getting every result after Today Midnight (timestamp of today midnight).
The Group part is the most interesting part. Here we're looping through every document we've found and calculate a modulus for every 300 seconds (5 minutes) then we fill the property timestamp with the last result of the modulus operations.
The Project part is necessary to remove the _id from the actual result as the result doesn't represent something in the database anymore.
Given answeres where this answere is based on:
MongoDB - Aggregate max/min/average for multiple variables at once
How to subtract in mongodb php
MongoDB : Aggregation framework : Get last dated document per grouping ID
Doctrine Solution
$collection->aggregate([
[
'$match' => [
'timestamp' => ['$gte' => 1464559200]
]
],
[
'$group' => [
'_id' => [
'$subtract' => [
'$timestamp',
[
'$mod' => ['$timestamp',300]
]
]
],
'timestamp' => [
'$last' => '$timestamp'
],
$someKey => [
'$avg' => '$'.$someKey
],
$someOtherKey => [
'$avg' => '$'.$someOtherKey
],
$someOtherOtherKey => [
'$avg' => '$'.$someOtherOtherKey
]
]
]
]);

Plot user transactions for every day of week

In my DB there is Transaction table. This table contains information about user money transactions. My task is to generate two diagrams(graphs) showing transaction amount per day, and per month.
Example of the diagram that I should implement is displayed on the image below:
As you can see from the diagram, when there are no transactions per day, the total value should be 0.
In the database, I group my transactions by day using the following query:
select DATE_FORMAT(t.transactionDate ,"%b %d") as dayName, sum(t.amount) total from `Transaction` t, t.transactionStatus = 1 and t.user=17
group by dayName
order by t.transactionDate asc;
This is working like a charm when there are transactions for every day, but this is not working fine when there is a day without any transaction. Let's say in DB we have the following transactions:
May 1st 20$
May 1st 30$
May 1st 38$
May 2nd 20$
May 4th 100$
May 4th 50$
So no transactions on May 3rd.
When I group these transactions I get the following result:
May 1st 88$
May 2nd 20$
May 4th 150$
What I want now is to generate May 3rd 0$. Is it possible to do it directly in DB, or I have to process it using PHP?
If I have to process it using PHP, any ideas?
For plotting I'm using Chart JS library, and here is example of data input:
var areaChartData = {
labels: ["May 01", "May 02", "May 04"],
datasets: [
{
label: "Transactions",
fillColor: "rgba(210, 214, 222, 1)",
strokeColor: "rgba(210, 214, 222, 1)",
pointColor: "rgba(210, 214, 222, 1)",
pointStrokeColor: "#c1c7d1",
pointHighlightFill: "#fff",
pointHighlightStroke: "rgba(220,220,220,1)",
data: [88,20,150]
},
]
};
As you can see, my idea is to use values from the database directly in the chart, where transaction date is my X-axis, and total is my Y-axis.
One approach is, to have all possible dates and values first as an PHP array:
$labels = array(
"May 1st" => 0,
"May 2nd" => 0,
"May 3rd" => 0,
"May 4th" => 0,
"May 6th" => 0,
"May 6th" => 0,
....
);
Then fill all the values from the database to their according date key in the array
...
foreach ( $rows as $row )
{
$labels[$row['date']] = $row['agg_val'];
...
Now you can iterate over the $labels array and put out the values. Those, which are not in the DB (no transactions) are initialized to zero.
Looking at your query, it will generate $result like below,
array (size=3)
0 =>
array (size=2)
'dayName' => string 'May 01' (length=6)
'total' => int 88
1 =>
array (size=2)
'dayName' => string 'May 02' (length=6)
'total' => int 20
2 =>
array (size=2)
'dayName' => string 'May 04' (length=6)
'total' => int 150
This script will prepare array for chartdata,
$begin = reset($result) ['dayName']; // 1st date in result
$end = end($result) ['dayName']; // last date in result
$begin = new DateTime($begin);
$end = new DateTime($end);
$end->modify('+1 day');
$interval = DateInterval::createFromDateString('1 day');
$period = new DatePeriod($begin, $interval, $end);
function search($input, $day)
{
foreach($input as $k => $v)
{
if ($v['dayName'] == $day) return $k;
}
return false;
}
$chartdata = []; // array for chartdata
foreach($period as $dt) // loop for everyday from 1st to last day
{
$format = $dt->format('M d');
$flag = search($result, $dt->format('M d'));
$chartdata[$format] = is_int($flag) ? $result[$flag]['total'] : 0;
}
Now, your $chartdata contains the missing day May 03 from transactions table.
Now, use this as
labels: <?php echo json_encode(array_keys($chartdata))?>,
and
data: <?php echo json_encode(array_values($chartdata))?>

MongoDB - Aggregation Framework, PHP and averages

First time here - please go easy… ;)
I'm starting off with MongoDB for the first time - using the offical PHP driver to interact with an application.
Here's the first problem I've ran into with regards to the aggregation framework.
I have a collection of documents, all of which contain an array of numbers, like in the following shortened example...
{
"_id": ObjectId("51c42c1218ef9de420000002"),
"my_id": 1,
"numbers": [
482,
49,
382,
290,
31,
126,
997,
20,
145
],
}
{
"_id": ObjectId("51c42c1218ef9de420000006"),
"my_id": 2,
"numbers": [
19,
234,
28,
962,
24,
12,
8,
643,
145
],
}
{
"_id": ObjectId("51c42c1218ef9de420000008"),
"my_id": 3,
"numbers": [
912,
18,
456,
34,
284,
556,
95,
125,
579
],
}
{
"_id": ObjectId("51c42c1218ef9de420000012"),
"my_id": 4,
"numbers": [
12,
97,
227,
872,
103,
78,
16,
377,
20
],
}
{
"_id": ObjectId("51c42c1218ef9de420000016"),
"my_id": 5,
"numbers": [
212,
237,
103,
93,
55,
183,
193,
17,
346
],
}
Using the aggregation framework and PHP (which I think is the correct way), I'm trying to work out the average amount of times a number doesn't appear in a collection (within the numbers array) before it appears again.
For example, the average amount of times the number 20 doesn't appear in the above example is 1.5 (there's a gap of 2 collections, followed by a gap of 1 - add these values together, divide by number of gaps).
I can get as far as working out if the number 20 is within the results array, and then using the $cond operator, passing a value based on the result. Here’s my PHP…
$unwind_results = array(
'$unwind' => '$numbers'
);
$project = array (
'$project' => array(
'my_id' => '$my_id',
'numbers' => '$numbers',
'hit' => array('$cond' => array(
array(
'$eq' => array('$numbers',20)
),
0,
1
)
)
)
);
$group = array (
'$group' => array(
'_id' => '$my_id',
'hit' => array('$min'=>'$hit'),
)
);
$sort = array(
'$sort' => array( '_id' => 1 ),
);
$avg = $c->aggregate(array($unwind_results,$project, $group, $sort));
What I was trying to achieve, was to setup up some kind of incremental counter that reset everytime the number 20 appeared in the numbers array, and then grab all of those numbers and work out the average from there…But im truly stumped.
I know I could work out the average from a collection of documents on the application side, but ideally I’d like Mongo to give me the result I want so it’s more portable.
Would Map/Reduce need to get involved somewhere?
Any help/advice/pointers greatly received!
As Asya said, the aggregation framework isn't usable for the last part of your problem (averaging gaps in "hits" between documents in the pipeline). Map/reduce also doesn't seem well-suited to this task, since you need to process the documents serially (and in a sorted order) for this computation and MR emphasizes parallel processing.
Given that the aggregation framework does process documents in a sorted order, I was brainstorming yesterday about how it might support your use case. If $group exposed access to its accumulator values during the projection (in addition to the document being processed), we might be able to use $push to collect previous values in a projected array and then inspect them during a projection to compute these "hit" gaps. Alternatively, if there was some facility to access the previous document encountered by a $group for our bucket (i.e. group key), this could allow us to determine diffs and compute the gap span as well.
I shared those thoughts with Mathias, who works on the framework, and he explained that while all of this might be possible for a single server (were the functionality implemented), it would not work at all on a sharded infrastructure, where $group and $sort operations are distributed. It would not be a portable solution.
I think you're best option is to run the aggregation with the $project you have, and then process those results in your application language.

Stuck on trying to turn input into a string of certain format

So I have a field in the db for delivery times, and it is of certain format (not my design for sure!):
Here are a couple of examples:
M, T, W, TH ; MTWTH 9 TO 4 CLOSED 12 TO 1
T, W, TH, F ; TTHF 9 TO 5 CLOSED 12 TO 2, W 9 TO 12
T, W, TH, F ; T 10 TO 7 CLOSED 2 TO 3, WTH 9 TO 6 CLOSED 1 TO 2, F 8 TO 5 CLOSED 1 TO 2
So basically, you have a list of days, separated by a comma, followed by semi-colon.
After semi-colon there is a list of days, not separated, and followed by delivery times including break times.
The problem is, delivery times can be different for each day, same for all days, or same for let's say Monday and tuesday, then different for Wednesday, and same for Thursday and Friday (but Thursday and Friday have different delivery times then Monday and Tuesday).
And if delivery times match for some days, those days should be grouped together, with delivery times listed after the group days, then other matched or single days listed after that. First day for which there are delivery hours specified must go first.
I know, the format is ridiculous and delivery days/times should be in a separate table linked by organization id, but it's the format they use now for some download files.
I hope to change it later, but for now...
I can't figure out how to transform input from 4 drop-downs (From, To, Closed From, Closed To) into that format.
Any of the days may not have closing times at all. At least one day must have delivery times specified (I check for that).
I of course got the first part (before semi-colon) - that's easy, after that I'm totally stuck.
If all days for which there's delivery had the same hours, I wouldn't have issues. But since it can be totally different from day to day, I don't know how to do it.
So this is the processing block so far:
$trimmed is the array of POST values that has been trimmed,
$trimmed['1Monday'] is Monday From, $trimmed['2Monday'] is Monday To,
$trimmed['3Monday'] is Monday Closed From, $trimmed['4Monday'] is Monday Closed To.
So $M_times variable will have open/closing times for Monday.
$days_del array is used to later create a string that is the first part of required format.
I can get the first day with some times specified and I can try to catch any matches after that, but that isn't useful if first delivery day has different values from the rest, yet there are some matching values after that. I'm not even sure that I need to use anoither (numbered) days array or I can do it with just the main one with M-F for keys.
<?php
$days = array('M' => 'Monday', 'T' => 'Tuesday', 'W' => 'Wednesday', 'TH' => 'Thursday', 'F' => 'Friday');
$days_numbered = array(1 => 'M', 'T', 'W', 'TH', 'F');
$days_del = $matches = $full_list = array();
$days_delivery2 = '';
$count_no_del = 0; # initialize count for days with no delivery
$times_6 = '';
foreach ($days as $k => $v){ ##### Beginning of days loop
if (isset($trimmed["1$v"]) && isset($trimmed["2$v"])){ # If both closing and opening times have been specified
$days_del[] = $k;
${$k.'_times'} = $trimmed["1$v"] . ' TO ' . $trimmed["2$v"];
if ((!isset($trimmed["3$v"]) && isset($trimmed["4$v"])) || (isset($trimmed["3$v"]) && !isset($trimmed["4$v"]))){ # If only beginning or only end of lunch(?) break has been specified
$errors[] = 'Delivery times for $v - please specify both start and end of closing time.';
}elseif (isset($trimmed["3$v"]) && isset($trimmed["4$v"]){ # If both beginning and end of lunch(?) breakh have been specified
${$k.'_times'} .= 'CLOSED' . $trimmed["3$v"] . ' TO ' . $trimmed["4$v"];
}
}elseif ((!isset($trimmed["1$v"]) && isset($trimmed["2$v"])) || (isset($trimmed["1$v"]) && !isset($trimmed["2$v"]))){ # If only closing or only opening time has been specified
$errors[] = 'Delivery times for $v - please specify both start and end of delivery time.';
${$k.'_times'} = NULL;
}elseif (!isset($trimmed["1$v"]) && !isset($trimmed["2$v"])){ # No delivery times specified for this day
${$k.'_times'} = NULL;
$count_no_del++;
}
$full_list["$k"] = ${$k.'_times'};
} ##### End of days loop
if ($count_no_del > 0){ # If there are no delivery days specified
$errors[] = 'You must specify delivery hours for at least one day of the week.';
}
$days_delivery1 = implode(',', $days_del);
$days_delivery1 = $days_delivery1 . ' ; ';
foreach ($days_numbered as $num => $val){ # Getting first day for which delivery hours have been specified
if (isset(${$val.'_times'}) && (${$val.'_times'} != NULL)){
${'times_'.$num} = ${$val.'_times'};
$first_day = $num;
break;
}
}
$check_array = array_keys($full_list, ${'times_'.$first_day})); # checking how many other days match the delivery hours for the first specified day.
foreach ($check_array as $array_key){
$days_delivery2 .= $array_key;
}
$days_delivery2 .= " " . ${'times_'.$first_day};
$note_line = $days_delivery1 . " " ; # second part, something like 'MTH 9 To 5, TW 10 TO 5 CLOSED 1 TO 2, F 10 TO 2' should go a s the second part of the string.
?>
As you can see, after getting the first part of the string in that format ($days_delivery1) I'm stuck and don't know what the hell am I doing. I have a vouge idea of using 2 different arrays (main one and numbered one) and using array_keys to find matching values, but any time I try to work on it I just run into a wall. Any ideas would be much apreciated.
The thing that is potentially difficult here is grouping like entries for the second section. In order to overcome this, I suggest you convert the times to 4 digit representations to normalise them and concatenate the patterns, so create a "signature" for each pattern.
In the below example, I will be working on the principal that the following $_POST arrays will result in the corresponding result strings:
$_POST1 = array(
'1Monday' => 9,
'2Monday' => 5,
'3Monday' => 12,
'4Monday' => 1,
'1Tuesday' => 9,
'2Tuesday' => 5,
'3Tuesday' => 12,
'4Tuesday' => 1,
'1Wednesday' => 9,
'2Wednesday' => 5,
'3Wednesday' => 12,
'4Wednesday' => 1,
'1Thursday' => 9,
'2Thursday' => 5,
'3Thursday' => 12,
'4Thursday' => 1,
'1Friday' => 9,
'2Friday' => 4,
'3Friday' => 11,
'4Friday' => 12
);
$result1 = "M, T, W, TH, F ; MTWTH 9 TO 5 CLOSED 12 TO 1, F 9 TO 4 CLOSED 11 TO 12";
$_POST2 = array(
'1Monday' => '',
'2Monday' => '',
'3Monday' => '',
'4Monday' => '',
'1Tuesday' => 9,
'2Tuesday' => 5,
'3Tuesday' => 12,
'4Tuesday' => 1,
'1Wednesday' => 9,
'2Wednesday' => 5,
'3Wednesday' => 12,
'4Wednesday' => 1,
'1Thursday' => 9,
'2Thursday' => 5,
'3Thursday' => 12,
'4Thursday' => 1,
'1Friday' => 9,
'2Friday' => 5,
'3Friday' => 12,
'4Friday' => 1
);
$result2 = "T, W, TH, F ; TWTHF 9 TO 5 CLOSED 12 TO 1";
Now let's take a look at how we'd process that data. I'm going to assume that we are working directly with the unprocessed POST array.
<?php
// Obviously we will need this map if the inputs use the full day names
$days = array('M' => 'Monday', 'T' => 'Tuesday', 'W' => 'Wednesday', 'TH' => 'Thursday', 'F' => 'Friday');
// A couple of arrays to hold the results of the loop iterations
$resultDays = $resultTimes = array();
// First we iterate over the days
foreach ($days as $dayCode => $dayFull) {
// Data about this day
$dayData = array(
'open' => 0,
'close' => 0,
'lunchClose' => 0,
'lunchOpen' => 0,
'days' => array()
);
// First get the open/close times
$open = $_POST["1$dayFull"];
$close = $_POST["2$dayFull"];
if (empty($open) || empty($close)) {
// If we don't have both open/close times, skip this day
continue;
}
// We definitely open on this day
$resultDays[] = $dayCode;
$dayData['open'] = $open;
$dayData['close'] = $close;
// Pad the strings to make the signature
$openPadded = str_pad($open, 2, "0", STR_PAD_LEFT);
$closePadded = str_pad($close, 2, "0", STR_PAD_LEFT);
// Now look at lunch times
$lunchClose = $_POST["3$dayFull"];
$lunchOpen = $_POST["4$dayFull"];
if (!empty($lunchClose) || !empty($lunchOpen)) {
// If we have both open/close times, add a lunch break
// Add data to $dayData
$dayData['lunchClose'] = $lunchClose;
$dayData['lunchOpen'] = $lunchOpen;
// Pad the strings to make the signature
$lunchClosePadded = str_pad($lunchClose, 2, "0", STR_PAD_LEFT);
$lunchOpenPadded = str_pad($lunchOpen, 2, "0", STR_PAD_LEFT);
} else {
// So we don't break this signature
$lunchClosePadded = $lunchOpenPadded = '';
}
// Build the signature
$signature = $openPadded.$closePadded.$lunchClosePadded.$lunchOpenPadded;
// Add day data to result times array
if (!isset($resultTimes[$signature])) {
$resultTimes[$signature] = $dayData;
}
$resultTimes[$signature]['days'][] = $dayCode;
}
// Now we can build the string
// Like you say, first part is easy
$firstPart = implode(', ', $resultDays);
// Loop $resultTimes and construct to more sensible arrangements
$secondPart = array();
foreach ($resultTimes as $block) {
$str = implode('', $block['days'])." {$block['open']} TO {$block['close']}";
if (!empty($block['lunchClose']) && !empty($block['lunchOpen'])) {
$str .= " CLOSED {$block['lunchClose']} TO {$block['lunchOpen']}";
}
$secondPart[] = $str;
}
// Now we can construct the final string
$finalResult = $firstPart." ; ".implode(', ', $secondPart);
See it working.
Well, that was fun.

Categories