使用 PHP 从 start_time 和 end_time 的多维数组中查找总小时数?
Find total hours from multi-dimensional array with start_time and end_time using PHP?
我正在尝试使用 start_time
和 end_time
计算正确的总小时数
这里我使用的算法是@Pilan提供的:
- 对每组时间预订执行以下操作
- 找到最小的
start_time
- 将
start_time
和 end_time
之间的 duration
添加到 sum
- 通过
start_time
查找下一个最小的时间预订
- IF
start_time
< previous_end_time
从 sum
中减去差异 END IF
- 在
start_time
和end_time
之间添加duration
- 跳转到 4 直到没有匹配的元素为止。
使用上面的代码我已经成功地创建了下面的代码:
<?php
class Helper
{
public static function minToHour($minutes)
{
if (empty($minutes)) {
return 0;
}
$hours = floor($minutes / 60);
$min = $minutes - ($hours * 60);
return $hours . ":" . $min;
}
public static function getMinsBetweenTwoTimes($start, $end)
{
$datetime1 = strtotime($start);
$datetime2 = strtotime($end);
$interval = abs($datetime2 - $datetime1);
$minutes = round($interval / 60);
return $minutes;
}
}
$array =array (
'2020-07-14' =>
array (
array (
'start_time' => '09:00:00',
'end_time' => '13:00:00',
'hours' => '4 hours 0 mins',
),
1 =>
array (
'start_time' => '13:30:00',
'end_time' => '16:30:00',
'hours' => '3 hours 0 mins',
),
2 =>
array (
'start_time' => '09:00:00',
'end_time' => '14:00:00',
'hours' => '5 hours 0 mins',
),
),
'2020-07-15' =>
array (
array (
'start_time' => '13:30:00',
'end_time' => '17:00:00',
'hours' => '3 hours 30 mins',
),
1 =>
array (
'start_time' => '09:00:00',
'end_time' => '14:00:00',
'hours' => '5 hours 0 mins',
),
),
);
$newArray = [];
foreach ($array as $key => $dateArr) {
array_multisort(
array_map('strtotime', array_column($dateArr, 'start_time')),
array_column($dateArr, 'start_time'),
$dateArr
);
$newArray[$key] [] = $dateArr;
}
$sumArr = [];
foreach ($newArray as $date => $items) {
$sum = 0;
foreach ($items[0] as $key => $item) {
$sum += Helper::getMinsBetweenTwoTimes($item['start_time'], $item['end_time']);
if ($key > 0) {
$previous_end_time = $items[0][$key - 1]['end_time'] ?? null;
if (!empty($previous_end_time)) {
if (($item['start_time']) < strtotime($previous_end_time)) {
$sum -= Helper::getMinsBetweenTwoTimes($item['start_time'], $item['end_time']);
}
}
}
}
$newArray[$date] ['total_attended_hours'] = Helper::minToHour($sum);
}
echo "<pre>";
print_r($newArray);
exit;
?>
我更改了 2 行,81 和 82(如下所示)。你不需要 strtotime($previous_end_time) 来比较它,因为时间是 24 小时制,而且当你减去你想要减去 $previous_end_time 而不是 $item['end_time' ].
if (($item['start_time']) < $previous_end_time) {
$sum -= Helper::getMinsBetweenTwoTimes($item['start_time'], $previous_end_time);
}
这是我对你的问题的解决方案。
我不知道我是否理解正确,但基本上是这样的算法:
1- 将所有时间字符串转换为整数并对参加时段的每个日期列表进行排序。
2- 合并每个日期的重叠时段,例如,如果一个时段从 9 点到 12 点,另一个从 11 点到 13 点,则将其合并为一个从 9 点到 13 点的时段。
3- 对每个日期的所有参加时间求和。
<?php
$array = [
'2020-07-14' =>[
[
'start_time' => '09:00:00',
'end_time' => '13:00:00',
'hours' => '4 hours 0 mins',
],
[
'start_time' => '13:30:00',
'end_time' => '16:30:00',
'hours' => '3 hours 0 mins',
],
[
'start_time' => '09:00:00',
'end_time' => '14:00:00',
'hours' => '5 hours 0 mins',
],
[
'start_time' => '17:00:00',
'end_time' => '18:00:00',
'hours' => '1 hours 0 mins',
]
],
'2020-07-15' => [
[
'start_time' => '09:00:00',
'end_time' => '14:00:00',
'hours' => '5 hours 0 mins',
],
[
'start_time' => '13:30:00',
'end_time' => '17:00:00',
'hours' => '4 hours 30 mins',
]
],
];
// Convert all times strings into integers and sort each day list
// by the start time
$r = parseTimesAndSort($array);
// Combine overlaping periods in a single period
$r = flatternOverlaps($r);
// Sum all the periods in each date
$r = sumPeriods($r);
// Applly the result to the original array
foreach($r as $date => $item){
$array[$date]['total_attended_hours'] = $item;
}
print_r($array);
/**
* Given a time string returns the number of seconds from 00:00:00 as integer.
* example: 09:30:10 => 34210 (9*3600 + 30*60 + 10)
* @param $time
* @return int
*/
function timeToSeconds($time){
$list = explode(":", $time);
return $list[0] * 3600 + $list[1] * 60 + $list[2];
}
/**
* Given an integer as seconds returns the time string in 00:00:00 format.
* example: 34210 => 09:30:10
* @param $value
* @return string
*/
function secondsToTime($value){
$hours = floor($value/3600);
$min = floor(($value%3600) / 60);
$secods = floor($value % 60);
return str_pad($hours, 2, "0", STR_PAD_LEFT)
.":".str_pad($min, 2, "0", STR_PAD_LEFT)
.":".str_pad($secods, 2, "0", STR_PAD_LEFT);
}
/**
* Function to compare two periods
* @param $a
* @param $b
* @return int
*/
function sortByStartTime($a, $b){
if ($a['start_time'] == $b['start_time']){
return 0;
}
return $a['start_time'] < $b['start_time'] ? -1 : 1;
}
/**
* Parses the periods string times to integers and sorts them
* @param $array
* @return array
*/
function parseTimesAndSort($array){
$r = [];
foreach($array as $date => $list){
$current = [];
foreach($list as $item){
$current[] = [
'start_time' => timeToSeconds($item['start_time']),
'end_time' => timeToSeconds($item['end_time']),
];
}
usort($current, 'sortByStartTime');
$r[$date] = $current;
}
return $r;
}
/**
* Finds overlapping periods and combines them
* @param $array
* @return array
*/
function flatternOverlaps($array){
$r = [];
foreach($array as $date => $list){
$currentList = [];
$prev = null;
foreach($list as $item){
if ($prev && $item['start_time'] < $prev['end_time']){
if ($item['end_time'] > $prev['end_time']) {
$prev['end_time'] = $item['end_time'];
}
}
else{
$currentList[] = $item;
}
// Point prev to the last item in the current list
$prev = &$currentList[count($currentList)-1];
}
unset($prev);
$r[$date] = $currentList;
}
return $r;
}
/**
* Sums the periods of each date
* @param $array
* @return array
*/
function sumPeriods($array){
$r = [];
foreach($array as $date => $list){
$seconds = array_reduce($list, function($carry, $item){ return $carry + $item['end_time'] - $item['start_time']; }, 0);
$r[$date] = secondsToTime($seconds);
}
return $r;
}
我正在尝试使用 start_time
和 end_time
这里我使用的算法是@Pilan提供的:
- 对每组时间预订执行以下操作
- 找到最小的
start_time
- 将
start_time
和end_time
之间的duration
添加到sum
- 通过
start_time
查找下一个最小的时间预订
- IF
start_time
<previous_end_time
从sum
中减去差异 END IF - 在
start_time
和end_time
之间添加duration
- 跳转到 4 直到没有匹配的元素为止。
使用上面的代码我已经成功地创建了下面的代码:
<?php
class Helper
{
public static function minToHour($minutes)
{
if (empty($minutes)) {
return 0;
}
$hours = floor($minutes / 60);
$min = $minutes - ($hours * 60);
return $hours . ":" . $min;
}
public static function getMinsBetweenTwoTimes($start, $end)
{
$datetime1 = strtotime($start);
$datetime2 = strtotime($end);
$interval = abs($datetime2 - $datetime1);
$minutes = round($interval / 60);
return $minutes;
}
}
$array =array (
'2020-07-14' =>
array (
array (
'start_time' => '09:00:00',
'end_time' => '13:00:00',
'hours' => '4 hours 0 mins',
),
1 =>
array (
'start_time' => '13:30:00',
'end_time' => '16:30:00',
'hours' => '3 hours 0 mins',
),
2 =>
array (
'start_time' => '09:00:00',
'end_time' => '14:00:00',
'hours' => '5 hours 0 mins',
),
),
'2020-07-15' =>
array (
array (
'start_time' => '13:30:00',
'end_time' => '17:00:00',
'hours' => '3 hours 30 mins',
),
1 =>
array (
'start_time' => '09:00:00',
'end_time' => '14:00:00',
'hours' => '5 hours 0 mins',
),
),
);
$newArray = [];
foreach ($array as $key => $dateArr) {
array_multisort(
array_map('strtotime', array_column($dateArr, 'start_time')),
array_column($dateArr, 'start_time'),
$dateArr
);
$newArray[$key] [] = $dateArr;
}
$sumArr = [];
foreach ($newArray as $date => $items) {
$sum = 0;
foreach ($items[0] as $key => $item) {
$sum += Helper::getMinsBetweenTwoTimes($item['start_time'], $item['end_time']);
if ($key > 0) {
$previous_end_time = $items[0][$key - 1]['end_time'] ?? null;
if (!empty($previous_end_time)) {
if (($item['start_time']) < strtotime($previous_end_time)) {
$sum -= Helper::getMinsBetweenTwoTimes($item['start_time'], $item['end_time']);
}
}
}
}
$newArray[$date] ['total_attended_hours'] = Helper::minToHour($sum);
}
echo "<pre>";
print_r($newArray);
exit;
?>
我更改了 2 行,81 和 82(如下所示)。你不需要 strtotime($previous_end_time) 来比较它,因为时间是 24 小时制,而且当你减去你想要减去 $previous_end_time 而不是 $item['end_time' ].
if (($item['start_time']) < $previous_end_time) {
$sum -= Helper::getMinsBetweenTwoTimes($item['start_time'], $previous_end_time);
}
这是我对你的问题的解决方案。
我不知道我是否理解正确,但基本上是这样的算法:
1- 将所有时间字符串转换为整数并对参加时段的每个日期列表进行排序。
2- 合并每个日期的重叠时段,例如,如果一个时段从 9 点到 12 点,另一个从 11 点到 13 点,则将其合并为一个从 9 点到 13 点的时段。
3- 对每个日期的所有参加时间求和。
<?php
$array = [
'2020-07-14' =>[
[
'start_time' => '09:00:00',
'end_time' => '13:00:00',
'hours' => '4 hours 0 mins',
],
[
'start_time' => '13:30:00',
'end_time' => '16:30:00',
'hours' => '3 hours 0 mins',
],
[
'start_time' => '09:00:00',
'end_time' => '14:00:00',
'hours' => '5 hours 0 mins',
],
[
'start_time' => '17:00:00',
'end_time' => '18:00:00',
'hours' => '1 hours 0 mins',
]
],
'2020-07-15' => [
[
'start_time' => '09:00:00',
'end_time' => '14:00:00',
'hours' => '5 hours 0 mins',
],
[
'start_time' => '13:30:00',
'end_time' => '17:00:00',
'hours' => '4 hours 30 mins',
]
],
];
// Convert all times strings into integers and sort each day list
// by the start time
$r = parseTimesAndSort($array);
// Combine overlaping periods in a single period
$r = flatternOverlaps($r);
// Sum all the periods in each date
$r = sumPeriods($r);
// Applly the result to the original array
foreach($r as $date => $item){
$array[$date]['total_attended_hours'] = $item;
}
print_r($array);
/**
* Given a time string returns the number of seconds from 00:00:00 as integer.
* example: 09:30:10 => 34210 (9*3600 + 30*60 + 10)
* @param $time
* @return int
*/
function timeToSeconds($time){
$list = explode(":", $time);
return $list[0] * 3600 + $list[1] * 60 + $list[2];
}
/**
* Given an integer as seconds returns the time string in 00:00:00 format.
* example: 34210 => 09:30:10
* @param $value
* @return string
*/
function secondsToTime($value){
$hours = floor($value/3600);
$min = floor(($value%3600) / 60);
$secods = floor($value % 60);
return str_pad($hours, 2, "0", STR_PAD_LEFT)
.":".str_pad($min, 2, "0", STR_PAD_LEFT)
.":".str_pad($secods, 2, "0", STR_PAD_LEFT);
}
/**
* Function to compare two periods
* @param $a
* @param $b
* @return int
*/
function sortByStartTime($a, $b){
if ($a['start_time'] == $b['start_time']){
return 0;
}
return $a['start_time'] < $b['start_time'] ? -1 : 1;
}
/**
* Parses the periods string times to integers and sorts them
* @param $array
* @return array
*/
function parseTimesAndSort($array){
$r = [];
foreach($array as $date => $list){
$current = [];
foreach($list as $item){
$current[] = [
'start_time' => timeToSeconds($item['start_time']),
'end_time' => timeToSeconds($item['end_time']),
];
}
usort($current, 'sortByStartTime');
$r[$date] = $current;
}
return $r;
}
/**
* Finds overlapping periods and combines them
* @param $array
* @return array
*/
function flatternOverlaps($array){
$r = [];
foreach($array as $date => $list){
$currentList = [];
$prev = null;
foreach($list as $item){
if ($prev && $item['start_time'] < $prev['end_time']){
if ($item['end_time'] > $prev['end_time']) {
$prev['end_time'] = $item['end_time'];
}
}
else{
$currentList[] = $item;
}
// Point prev to the last item in the current list
$prev = &$currentList[count($currentList)-1];
}
unset($prev);
$r[$date] = $currentList;
}
return $r;
}
/**
* Sums the periods of each date
* @param $array
* @return array
*/
function sumPeriods($array){
$r = [];
foreach($array as $date => $list){
$seconds = array_reduce($list, function($carry, $item){ return $carry + $item['end_time'] - $item['start_time']; }, 0);
$r[$date] = secondsToTime($seconds);
}
return $r;
}