Laravel 按几天内的奇数小时数对数据进行分组

Laravel group data by odd amount of hours throughout days

我正在尝试按照与规范略有不同的日期格式对我的 Laravel 项目中的一些数据进行分组。我有一个数据库和一个查询,可以根据用户想要查看的时间段获取用户网站的“正常运行时间检查”,然后我需要将其作为某种时间线显示给用户。

为了减少数据中的“噪音”(在给定时间段内可能没有足够的正常运行时间检查)我想将我的所有结果分组假设一天中有 3 个小时,那么我将获得以下所有数据:

等等,现在我按小时带回数据,但不确定如何修改它以达到预期的结果

// 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
  ];
});

不确定如何修改 returns 格式的 groupBy 函数,因为我的理解是不可能这样做?顺便说一下,我正在使用 Carbon。

更新

我一直在挖掘,并且遇到了 CarbonInterval 功能,它允许我生成一些间隔,并且我已经尝试实现它,我似乎得到了一个等间隔的时间段,但是我的数据已用完并且不包含两个间隔之间的所有数据(见附图)

$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');
});

例如,我应该在 07 键中看到第 7 小时和第 8 小时的所有检查,但我只看到一个小时(11 小时)的数据?

当您需要时间片时,最好使用 DateInterval 或更好的 CarbonInterval。他们给你的是循环遍历这些切片并对样本数据进行 equality/unequlity 操作的能力,这样你就可以通过这些时间片轻松地将数据组织到它们各自的“槽”

这里是关于如何

的一般思路
$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
}

更新

可能会尝试这样的事情,将插槽 getter 修改为“向前看”并决定结果

$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;
}