Arrays and loops through days - php

I have a problem creating this function. My code is a mess and I'm stuck, so I'd rather not post it. I'd rather ask for a fresh solution.
I have an array (mysql rows), fetched with today's date as a condition. I want to create a new array based on data from the previous array and insert it into the database also by today's date. Limit is 15. So if there are already 10 rows by this date, insert only 5, and continue on the next date, for as long as there are rows from the first array.
I am using php and code igniter.

I don't know how you're fetching the data or how you are generating new data, but something like this;
//loop through days that you want to check/update
//fetch existing data for this day into $data
if( count( $data ) >= 15 ) continue; //skip days that already have 15+
for( $x = 0; $x < 15 - count( $data ); $x++ )
{
//insert one new row here
}
//end days loop

Here is how i did it and it does what i want it to do. Not sure if it is the best way to achieve this functionality. Here i was using a temporary array just for testing. Each element will be a new row for the database. Used 3 as maximum(instead of 15) for testing only.
$subscriptions = Subscription::all(array('conditions' => 'token != "" ', 'order' => 'id asc'));
$startTime = strtotime('2013-08-15');
$temparray = array();
$projects = Project::all(array('conditions' => 'end = "'.date("Y-m-d", $startTime).'" '));
if ($projects){$counter = count($projects);}else{$counter = 0;}
while (list($key, $value) = each($subscriptions))
{
if ($counter == 3)
{
do
{
$startTime = strtotime('+1 day', $startTime);
$projects = Project::all(array('conditions' => 'end = "'.date("Y-m-d", $startTime).'" '));
if (count($projects) < 3)
{
$counter = count($projects);
break;
}
} while ($counter <= 3);
$temparray[] = $value->date . " " . date("Y-m-d", $startTime);
continue;
}
$temparray[] = $value->date . " " . date("Y-m-d", $startTime);
$counter++;
}

Related

Laravel group data by odd amount of hours throughout days

I'm trying to group some data in my Laravel project by a date format that is a bit different to the norm. I've got a database that and a query that fetches "Uptime Checks" for a user's website based on the period they want to look over, I then need to display this to the user as some kind of timeline.
In order to reduce "noise" in the data (where there may not be enough uptime checks for a given period) I'd like to group all of my results within say a 3 hour period throughout the day, so I'd have all of the data for:
2021-05-02 03:00:00
2021-05-02 06:00:00
2021-05-02 09:00:00
and so on, right now I'm bringing back data by the hour, but not sure how to modify this to achieve the desired outcome
// get the uptime checks for past X hours
$uptimeData = UptimeChecks::where('user_id', 1)
->where('monitor_id', 1)
->where('checked_at', '>=', '2021-05-02 13:00:00')
->where('checked_at', '<=', '2021-05-03 13:00:00')
->orderBy('checked_at', 'asc')
->select('event', 'checked_at')
->get();
$uptimeDataTimeline = $uptimeData->groupBy(function ($item, $key) {
$date = Carbon::parse($item->checked_at);
// group by hour, how can I get say every 3 hours worth of data?
return $date->format('Y-m-d H:00:00');
});
$uptimeDataTimeline = $uptimeDataTimeline->map(function ($checksInPeriod, $key) {
$down = 0;
$up = 0;
$total = 0;
$uptime = 0;
$fill = '#1fc777'; // green
// $checksInPeriod is all of the data for a given hour at the moment
// I need to group by a bigger period, say, every 3 hours
// add our events
foreach ($checksInPeriod as $key => $value) {
$total++;
if (strtolower($value['event']) == 'down') $down++;
if (strtolower($value['event']) == 'up') $up++;
}
// calculate uptime
$uptime = floatval(number_format(round($up / $total, 5) * 100, 2, '.', ','));
// fill colours
if ($uptime < 100) $fill = '#9deab8'; // lighter green
if ($uptime < 99) $fill = '#fbaa49'; // amber
if ($uptime < 98) $fill = '#e0465e'; // red
return [
'total_events' => $total,
'down_events' => $down,
'up_events' => $up,
'uptime' => $uptime,
'fill' => $fill
];
});
Not sure how to modify the groupBy function which returns the format since my understanding is that it's not possible to do that? I'm using Carbon by the way.
Update
I've been digging, and have come across the CarbonInterval feature, which allows me to generate some intervals, and I've tried implementing this, I seem to get an equally spaced time period, but my data is out and doesn't contain all of the data between two intervals (see attached image)
$intervals = CarbonInterval::hours(2)->toPeriod($from, $to);
$uptimeDataTimeline = $uptimeData->groupBy(function ($item, $key) use ($intervals) {
$date = Carbon::parse($item->checked_at);
foreach ($intervals as $key => $interval) {
if ($date->hour == Carbon::parse($interval)->addHours(1)->hour) {
$actualHour1 = Carbon::parse($interval)->hour;
if (strlen($actualHour1) == 1) $actualHour1 = "0$actualHour1";
return $date->format("Y-m-d $actualHour1:00:00");
} else if ($date->hour == Carbon::parse($interval)->addHours(2)->hour) {
$actualHour2 = Carbon::parse($interval)->subHours(2)->hour;
if (strlen($actualHour2) == 1) $actualHour2 = "0$actualHour2";
return $date->format("Y-m-d $actualHour2:00:00");
}
}
return $date->format('Y-m-d H:00:00');
});
For instance, I should be seeing all of the checks for the hours 7 and 8 within the 07 key, but instead I'm seeing data for just one hour (hour 11)?
The best thing to use whenever you need time slice(s) is DateInterval or better CarbonInterval. What they give you is the ability to loop over those slices and do equality/unequlity operation of your sample data this way you can easily organise your data by those time slices to their respective "slots"
Here is an general idea on how to
$intervals = \Carbon\CarbonInterval::hours(3)->toPeriod('2021-05-02 13:00:00', '2021-05-03 13:00:00');
//we get time slots of 3 hours between provided datetimes
foreach ($intervals as $date) {
$dtArr[] = strtotime($date->format('Y-m-d H:i:s')); //we collect those "time markers"
}
$result = [
'first'=> 0,
'second'=>0.
'third'=>0,
'forth'=>0,
'fifth'=>0,
'sixth'=>0,
'seventh'=>0,
'eighth'=>0
]; //array to accumulate your aggregations to correct time slot
foreach ($uptimeData as $sample) {
//loop over sample set
$ordinality = getSlotNo($sample->checked_at); //eg. third
//read the accumulated total in $result and add this too
$result[$ordinality] += 1;
}
function getSlotNo($dt){
$ts = strtotime($dt);
//eg. say greater than or equal to "13:00" but smaller than "16:00" -> You go in first slot
if($ts>=$dtArr[0] && $ts<$dtArr[1]){
//first slot
return 'first';
}
elseif($ts>=$dtArr[1] && $ts<$dtArr[2]){
//eg. say greater than or equal to "16:00" but smaller than "19:00" -> You go in second slot
//second slot
return 'second';
}
elseif($ts>=$dtArr[2] && $ts<$dtArr[3]){
//third slot
return 'third';
}
// and so on
}
UPDATE
Try something like this may be, modify the slot getter to "look ahead" and decide the result
$i=0;
foreach ($intervals as $date) {
$dtArr[] = strtotime($date->format('Y-m-d H:i:s')); //we collect those "time markers"
$result['int_'.$i] = 0;
$i++;
}
//fake data
$uptimeData=collect([
(object)['checked_at'=>'2021-05-03 10:10:00'],
(object)['checked_at'=>'2021-05-03 11:20:00'],
(object)['checked_at'=>'2021-05-03 12:20:00'],
(object)['checked_at'=>'2021-05-03 13:20:00'],
(object)['checked_at'=>'2021-05-03 14:20:00'],
]);
foreach ($uptimeData as $sample) {
//loop over sample set
$ordinalInfo = getSlotNo($sample->checked_at, $dtArr); //eg. third
//read the accumulated total in $result and add this too
if($ordinalInfo['match']){
$result['int_'.$ordinalInfo['index']] += 1;
}
}
/**
* #param $dt
* #return int index in $dtArr this value belongs to
*/
function getSlotNo($dt, $dtArr){
$ts = strtotime($dt);
$info = [];
for($i =0; $i<count($dtArr); $i++){
if(!empty($dtArr[$i+1])){ // if not reached the last item ie. still there's a next
if($ts>=$dtArr[$i] && $ts<$dtArr[$i+1]){
//i'th slot
$info=['match'=>true,'index'=>$i];
break;
}
}else{
// at last item ie. ( $i == count($dtArr)-1 )
if($ts<=$dtArr[$i])
$info=['match'=>true,'index'=>$i];
else
$info=['match'=>false,'index'=>NULL];
}
}
return $info;
}

Get all months from a query including zero counts

I have this query now:
SELECT DATE_FORMAT(`dataNl`, \'%Y%m\') AS `Ym`, COUNT(*) AS `totale`
FROM `noleggio`
GROUP BY `Ym`
This help to get data for each month, but if a month with 0 value, this doesn't exist in the database, so I can't get it. I need a query that add remaining month setting the COUNT field to 0.
I made a PHP code to add months with 0 value into the array, but it only works if the year is only one, if I want to get more, this needs a lot of tricky code, I think there could be a solution with SQL.
This is the PHP code:
$t = array();
$m = array();
foreach ($months as $val) {
$t[] = $val['totale'];
$m[] = $val['Ym'];
}
for ($i = 0; $i < 12; ++$i) {
if (in_array($i + 201801, $m) == false) {
array_splice($t, $i, 0, 0);
}
}
Here is a PHP solution which requires min and max dates from the database:
// use the query SELECT MIN(dataNl), MAX(dataNl) FROM ... to
// find the first and last date in your data and use them below
$dates = new DatePeriod(
DateTime::createFromFormat('Y-m-d|', '2018-01-15')->modify('first day of this month'),
new DateInterval('P1M'),
DateTime::createFromFormat('Y-m-d|', '2018-12-15')->modify('first day of next month')
);
// assuming $rows contain the result of the GROUP BY query...
foreach ($dates as $date) {
$datestr = $date->format('Ym');
$index = array_search($datestr, array_column($rows, 'Ym'));
if ($index === false) {
echo $datestr . ' -> 0' . PHP_EOL;
} else {
echo $datestr . ' -> ' . $months[$index]['totale'] . PHP_EOL;
}
}
Try the below query:
SELECT DATE_FORMAT(`dataNl`, \'%Y%m\') AS `Ym`, COUNT(*) AS `totale`
FROM `noleggio`
GROUP BY MONTH(`dataNl`)

Comparing values from a database using a while loop

I have a MySql table where I saved all workers names and the dates workers have to work on. I want to show a list containg all days of the current month and the worker names who have to work on the day that corresponds to them. Example:
February
1
2
3 - John Wick
5
6 - Martha Beck
etc.
This is the code I have in PHP but the loop is not working. I just get a list from 1 to 30 but it is not showing the data from database. If I run the loop without the (while ($n < 31)), I get all the records from database but I want to show the names just beside the day that correspond.
<?php
mysql_select_db($database_nineras, $nineras);
$query_res = sprintf("SELECT res_id, res_dateini, res_datefin, res_name FROM reservas ORDER BY res_dateini DESC");
$reservas = mysql_query($query_res, $nineras) or die(mysql_error());
$rreser = mysql_fetch_assoc($reservas);
$treser = mysql_num_rows($reservas);
$n = 1;
while ($n < 31) {
do {
++$n;
if ($n == date('d', strtotime($rreser['res_dateini']))) {
echo $n . ' - ' . $rreser['res_name'];
}
else {
echo $n;
}
} while ($rreser = mysql_fetch_assoc($reservas));
}
?>
The problem with your code is that the do-while loop is fetching all the rows returned by the query. So when you get to the second iteration of the while loop there's nothing left to fetch.
Rather than fetch the rows from the database each time through the loop, you can fetch them once and put them into an array whose index is the day numbers. Then you can loop through the days and print all the rows for each day.
Use date('j', ...) to get the date without a leading zero. Or change your SQL query to return DAY(res_dateini).
$results = array();
$reservas = mysql_query($query_res, $nineras) or die(mysql_error());
while ($rreser = mysql_fetch_assoc($reservas)) {
$d = date('j', strtotime($rreser['res_dateini'])));
$results[$d][] = $rreser['res_name'];
}
for ($day = 1; $day <= 31; $day++) {
echo "$day - " . (isset($results[$day]) ? implode(", ", $results[$day]) : "") . "<br>\n";
}
DEMO

PHP: How to fill an array with dates (Y-m-d) as keys [duplicate]

This question already has answers here:
I have 2 dates in PHP, how can I run a foreach loop to go through all of those days?
(13 answers)
Closed 1 year ago.
I want to fill an array with values. The keys of this array should be readable dates in the format 'YEAR-MONTH-DAY'. Starting point is '2010-5-25'.
The process should abort on the current date. Obviously, all dates should be valid dates.
I thought about doing this loop. But it seems that PHP is not able to check the condition of more than one in a 'for' loop. It does not give me any warnings or errors, though.
for ($d = 25, $m = 5, $y = 2010,
$this_day = date('j'),
$this_month = date('n'),
$this_year = date('Y');
($y <= $this_year) && ($m <= $this_month) && ($d <= $this_day);
$d++)
{
$values[$y.'-'.$m.'-'.$d] = 0; //fill array
$d++;
if(!checkdate($m, $d, $y)){
$d = 1;
$m++;
if($m > 12) { $m = 1; $y++; }
}
}
Doing this with nested loops would be rather painful.
One solution would be to use integer times as keys and then convert them later in another loop into the readable dates.
Is there a more efficient way?
Here is code that does some error checking, for example, valid dates provided and start date cannot be bigger than end date:
function arrayKeyDates($start, $end='now') {
// can use DateTime::createFromFormat() instead
$startDate = new DateTime($start);
$endDate = new DateTime($end);
if ($startDate === false) {
// invalid start date.
return;
}
if ($endDate === false) {
// invalid end date.
return;
}
if ($startDate > $endDate) {
// start date cannot be greater than end date.
return;
}
$dates = array();
while($startDate <= $endDate) {
$dates[$startDate->format('Y-n-j')] = 0;
$startDate->modify('+1 day');
}
return $dates;
}
print_r(arrayKeyDate('2014-11-30'));
I get the following output:
Array
(
[2014-11-30] => 0
[2014-12-1] => 0
[2014-12-2] => 0
[2014-12-3] => 0
[2014-12-4] => 0
[2014-12-5] => 0
[2014-12-6] => 0
[2014-12-7] => 0
)
Error handling code is left to you.
UPDATE (DateTime::createFromFormat)
If you want to create the DateTime objects using a custom format you can, in my function, you can do something like this:
$startDate = DateTime::createFromFormat('Y-n-j', $start);
Where $start would have the value 2010-5-25.
For more information, see: http://php.net/manual/en/datetime.createfromformat.php
$startDate = new \DateTime('2010-05-25');
$endDate = new \DateTime();
$interval = new \DateInterval('P1D');
$period = new \DatePeriod ($startDate, $interval, $endDate);
$dates = array();
foreach ($period as $key => $date) {
$dates[$date->format('Y-m-d')] = null;
}
var_dump($dates);
Simply you can try using strtotime(). Example:
$values = array();
$oldDate = strtotime('2010-05-25');
while($oldDate <= time()){
$values[date('Y-m-d', $oldDate)] = 'Your value';
$oldDate += 86400;
//Other codes
}
I know this is an old question, but might be helpful for new viewers a shorter version
$dummyArray = array_fill(1, 7, 0);
$dates = array_flip(array_map(function($val, $idx) {
return date_create('2010-5-25')->modify('-' . $idx . ' days')->format('Y-m-d');
}, $dummyArray, array_keys($dummyArray)));
I'm basically generating a dummy array which is going to have the numbers of days I want to extract as index, and then converting those to dates with array_map, after which I just flip the array to have the dates as keys instead of values
I took the liberty to clean up your code a little to make it readable:
<?php
$this_day = date('j');
$this_month = date('n');
$this_year = date('Y');
echo sprintf("Today: d-m-y: %s-%s-%s\n", $this_day, $this_month, $this_year);
for ($d = 25, $m = 5, $y = 2010;
($y <= $this_year) && ($m <= $this_month) && ($d <= $this_day);
$d++) {
echo sprintf("Date: d-m-y: %s-%s-%s\n", $d, $m, $y);
$values[$y.'-'.$m.'-'.$d] = 0; //fill array
$d++;
if(!checkdate($m, $d, $y)){
$d = 1;
$m++;
if($m > 12) { $m = 1; $y++; }
}
}
This shows that the code works perfectly well. That is if you chose the correct condition!
Today is the 07th, but your initial values start with the 25th which falsifies the condition. To verify chose a start day of '02' and see the output...
I guess you want to re-check your condition. Most likely it is something else you want to express...
First of all; the loop doesn't execute because you are checking separately if year number is lower then current year number, etc. But today is the 7th, and you start at the 25th of may 2010:
$d = 25;
$this_day = date('j'); // today: 7
$loop = $d <= $this_day; // evaluates to false
Because the 'day-check' evaluates to false, the whole expression evaluates to false. So the loop will only start to run on december the 25th.
You can better use the DateTime object to construct the dates and perform modifications on the created object. This will also safe you a lot of sweat with stuff like leap years etc. Example:
for (
$start = new DateTime('2010-05-25'),
$today = new DateTime('now') ;
$start->diff($today)->format('%a') >= 0 ;
$start->modify('+1 day')
) {
$values[$start->format('Y-m-d')] = 0;
}
easy does it!

php strtotime first day of next month returns nothing

I've been reading about problems in php with strtotime and "next month" issues. What i want to make is counter of months between two dates.
For example if I have start date 01.02.2012 and stop date 07.04.2012 I'd like to get return value - 3 months. Also 3 months would be the result if start date i 28.02.2012 and 07.04.2012. I am not counting exact number of days/months, just a number of months I have between two dates. It's not a big deal to make it with some strange date, mktime and strtotime usage, but unfortunatelly start and stop dates might be in two different years so
mktime(0,0,0,date('m')+1,1,date('Y');
isnt going to work (i do not now the year and if it changes between start and stop date. i can calculate it but it is not nice solution). Perfect solution would be to use:
$stat = Array('02.01.2012', '07.04.2012')
$cursor = strtotime($stat[0]);
$stop = strtotime($stat[1]);
$counter = 0;
while ( $cursor < $stop ) {
$cursor = strtotime("first day of next month", $cursor);
echo $cursor . '<br>';
$counter++;
if ( $counter > 100) { break; } // safety break;
}
echo $counter . '<br>';
Unfortunatelly strtotime isnt returning proper values. If I use it is returning empty string.
Any ideas how to get timestamp of the first day of next month?
SOLUTION
$stat = Array('02.01.2012', '01.04.2012');
$start = new DateTime( $stat[0] );
$stop = new DateTime( $stat[1] );
while ( $start->format( 'U') <= $stop->format( 'U' ) ) {
$counter ++;
echo $start->format('d:m:Y') . '<br>';
$start->modify( 'first day of next month' );
}
echo '::' . $counter . '..<br>';
<?php
$stat = Array('02.01.2012', '07.04.2012');
$stop = strtotime($stat[1]);
list($d, $m, $y) = explode('.', $stat[0]);
$count = 0;
while (true) {
$m++;
$cursor = mktime(0, 0, 0, $m, $d, $y);
if ($cursor < $stop) $count ++; else exit;
}
echo $count;
?>
the easy way :D

Categories