PHP确定多个(n)日期时间范围何时相互重叠

Ray*_*Ray 7 php datetime

我有一段时间试图解决以下问题:

这是一个日历程序,给出了一组来自多个人的可用日期时间集,我需要弄清楚每个人在PHP中可用的日期时间范围

可用性集:

p1: start: "2016-04-30 12:00", end: "2016-05-01 03:00"

p2: start: "2016-04-30 03:00", end: "2016-05-01 03:00"

p3: start: "2016-04-30 03:00", end: "2016-04-30 13:31"
    start: "2016-04-30 15:26", end: "2016-05-01 03:00"
Run Code Online (Sandbox Code Playgroud)

我在寻找,我可以打电话,会告诉我一个功能是什么日期时间范围内的所有(p)的人都可以在同一时间.

在上面的例子中,答案应该是:

2016-04-30 12:00 -> 2016-04-30 13:31
2016-04-30 15:26 -> 2016-05-01 03:00
Run Code Online (Sandbox Code Playgroud)

我确实找到了类似的问题和答案 日期时间 - 确定R中多个(n)日期时间范围是否相互重叠

但我不知道那是什么语言,并且不得不在答案中翻译逻辑.

iai*_*inn 5

嗯,那很有趣。可能有一种比每分钟循环更优雅的方法,但我不知道 PHP 是否是适合它的语言。请注意,当前需要管理单独搜索的开始时间和结束时间,尽管根据可用班次计算它们相当简单。

<?php
$availability = [
    'Alex' => [
        [
            'start' => new DateTime('2016-04-30 12:00'),
            'end'   => new DateTime('2016-05-01 03:00'),
        ],
    ],

    'Ben' => [
        [
            'start' => new DateTime('2016-04-30 03:00'),
            'end'   => new DateTime('2016-05-01 03:00'),
        ],
    ],

    'Chris' => [
        [
            'start' => new DateTime('2016-04-30 03:00'),
            'end'   => new DateTime('2016-04-30 13:31')
        ],

        [
            'start' => new DateTime('2016-04-30 15:26'),
            'end'   => new DateTime('2016-05-01 03:00')
        ],
    ],
];

$start = new DateTime('2016-04-30 00:00');
$end   = new DateTime('2016-05-01 23:59');
$tick  = DateInterval::createFromDateString('1 minute');

$period = new DatePeriod($start, $tick, $end);

$overlaps = [];
$overlapStart = $overlapUntil = null;

foreach ($period as $minute)
{
    $peopleAvailable = 0;

    // Find out how many people are available for the current minute
    foreach ($availability as $name => $shifts)
    {
        foreach ($shifts as $shift)
        {
            if ($shift['start'] <= $minute && $shift['end'] >= $minute)
            {
                // If any shift matches, this person is available
                $peopleAvailable++;
                break;
            }
        }
    }

    // If everyone is available...
    if ($peopleAvailable == count($availability))
    {
        // ... either start a new period...
        if (!$overlapStart)
        {
            $overlapStart = $minute;
        }

        // ... or track an existing one
        else
        {
            $overlapUntil = $minute;
        }
    }

    // If not and we were previously in a period of overlap, end it
    elseif ($overlapStart)
    {
        $overlaps[] = [
            'start' => $overlapStart,
            'end'   => $overlapUntil,
        ];

        $overlapStart = null;
    }
}

foreach ($overlaps as $overlap)
{
    echo $overlap['start']->format('Y-m-d H:i:s'), ' -> ', $overlap['end']->format('Y-m-d H:i:s'), PHP_EOL;
}
Run Code Online (Sandbox Code Playgroud)


Mat*_*nes 3

此实现存在一些错误,请参阅评论。我无法删除它,因为它是已接受的答案。请使用 iainn 或 fusion3k 的非常好的答案,直到我解决它。

实际上不需要使用任何日期/时间处理来解决这个问题。您可以利用这种格式的日期按字母顺序和时间顺序排列的事实。

我不确定这会使解决方案变得不那么复杂。这种方式可能可读性较差。但它比每分钟迭代要快得多,因此如果考虑性能,您可以选择它。

您还可以使用 每个数组 函数 这很好。

当然,由于我没有使用任何日期/时间函数,如果需要处理夏令时或不同时区的用户,它可能不起作用。

$availability = [
    [
        ["2016-04-30 12:00", "2016-05-01 03:00"]
    ],
    [
        ["2016-04-30 03:00", "2016-05-01 03:00"]
    ],
    [
        ["2016-04-30 03:00", "2016-04-30 13:31"],
        ["2016-04-30 15:26", "2016-05-01 03:00"]
    ]
];

// Placeholder array to contain the periods when everyone is available.
$periods = [];

// Loop until one of the people has no periods left.
while (count($availability) &&
       count(array_filter($availability)) == count($availability)) {
    // Select every person's earliest date, then choose the latest of these
    // dates.
    $start = array_reduce($availability, function($carry, $ranges) {
        $start = array_reduce($ranges, function($carry, $range) {
            // This person's earliest start date.
            return !$carry ? $range[0] : min($range[0], $carry);
        });
        // The latest of all the start dates.
        return !$carry ? $start : max($start, $carry);
    });

    // Select each person's range which contains this date.
    $matching_ranges = array_filter(array_map(function($ranges) use($start) {
        return current(array_filter($ranges, function($range) use($start) {
            // The range starts before and ends after the start date.
            return $range[0] <= $start && $range[1] >= $start;
        }));
    }, $availability));

    // Find the earliest of the ranges' end dates, and this completes our
    // first period that everyone can attend.
    $end = array_reduce($matching_ranges, function($carry, $range) {
        return !$carry ? $range[1] : min($range[1], $carry);
    });

    // Add it to our list of periods.
    $periods[] = [$start, $end];

    // Remove any availability periods which finish before the end of this
    // new period.
    array_walk($availability, function(&$ranges) use ($end) {
        $ranges = array_filter($ranges, function($range) use($end) {
            return $range[1] > $end;
        });
    });
}

// Output the answer in the specified format.
foreach ($periods as $period) {
    echo "$period[0] -> $period[1]\n";
}
/**
 * Output:
 * 
 * 2016-04-30 12:00 -> 2016-04-30 13:31
 * 2016-04-30 15:26 -> 2016-05-01 03:00
 */
Run Code Online (Sandbox Code Playgroud)