Intereting Posts
PHP – Как убедиться, что прокси-соединение curl_multi было успешным? Как я могу узнать, на какой странице установлено приложение Facebook или какая страница загружает мое приложение? Как получить данные из разных связанных таблиц, используя сдерживаемые в CakePHP? Передать переменные из одного файла в другой в PHP Может ли вредоносный пользователь в веб-приложении манипулировать входами (помимо данных формы), которые отправляются передним интерфейсом веб-приложения? Запросы к существующим файлам выполняются index.php передача переменной javascript в php Лучший способ включения представлений в представлениях в CodeIgniter PHPMyAdmin – общее количество записей как добавить динамический активный класс на выбранную страницу Zend Application из подкаталога, в котором правильны ссылки на Zend Можно ли использовать массив без его инициализации? jQuery ajax call возвращает пустую ошибку в браузерах Mac Safari и Chrome PHP проверяет, существует ли дата между двумя датами codeigniter простая проверка входа не работает

PHP Определите, когда несколько (n) диапазонов 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" 

Я ищу функцию, которую я могу назвать, которая скажет мне, что число в датах охватывает все (p) людей одновременно.

В приведенном выше примере ответ должен быть:

 2016-04-30 12:00 -> 2016-04-30 13:31 2016-04-30 15:26 -> 2016-05-01 03:00 

Я нашел этот похожий вопрос и ответ Datetime. Определите, перекрываются ли множественные (n) диапазоны datetime друг с другом в R

Но я понятия не имею, какой язык есть, и не должен переводить логику в ответ.

На самом деле нет необходимости использовать обработку даты и времени для решения этой проблемы. Вы можете использовать тот факт, что даты в этом формате находятся в алфавитном порядке, а также в хронологическом порядке.

Я не уверен, что это делает решение менее сложным. Это, вероятно, менее читаемо. Но это значительно быстрее, чем повторение в течение каждой минуты, поэтому вы можете выбрать его, если производительность является проблемой.

Вы также можете использовать каждую функцию массива , что приятно.

Конечно, поскольку я не использовал функции даты и времени, это может не сработать, если нужно использовать Daylight Savings Time или пользователей в разных часовых поясах.

 $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 = []; while (true) { // 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)); // If anybody doesn't have a range containing the date, we're finished // and can exit the loop. if (count($matching_ranges) < count($availability)) { break; } // 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 */ 

Ну, это было весело. Вероятно, это более элегантный способ сделать это, чем перебирать каждую минуту, но я не знаю, является ли 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('Ymd H:i:s'), ' -> ', $overlap['end']->format('Ymd H:i:s'), PHP_EOL; } 

Другой подход к вашему вопросу – использовать побитовые операторы. Преимущества этого решения – использование памяти, скорость и короткий код. Недостатком является то, что в вашем случае мы не можем использовать целое число php, потому что мы работаем с большими числами (1 день в минутах – 2 24 * 60 ), поэтому нам нужно использовать расширение GMP , которое по умолчанию недоступно в большинстве php. Однако, если вы используете apt-get или любой другой менеджер пакетов, установка очень проста .

Чтобы лучше понять мой подход, я буду использовать массив с общим периодом 30 минут, чтобы упростить двоичное представление:

 $calendar = [ 'p1' => [ ['start' => '2016-04-30 12:00', 'end' => '2016-04-30 12:28'] ], 'p2' => [ ['start' => '2016-04-30 12:10', 'end' => '2016-04-30 12:16'], ['start' => '2016-04-30 12:22', 'end' => '2016-05-01 12:30'] ] ]; 

Прежде всего, мы находим минимальные и максимальные даты всех элементов массива, затем мы запускаем свободную (временную) переменную с разницей в минутах между макс и мин. В приведенном выше примере (30 минут) получаем 2 30 -2 0 = 1 073 741 823, то есть двоичный код с 30 '1' (или с набором из 30 бит):

 111111111111111111111111111111 

Теперь для каждого человека мы создаем соответствующую переменную времени с тем же методом. Для первого человека легко (у нас есть только один временной интервал): разница между начальным и минимальным значениями равна 0, разница между концом и минусом равна 28, поэтому мы имеем 2 28 -2 0 = 268435455, то есть:

 001111111111111111111111111111 

На этом этапе мы обновляем глобальное свободное время с помощью побитовой операции AND между глобальным свободным временем и свободным временем человека. Оператор OR устанавливает биты, если они установлены в обоих сравниваемых значениях:

 111111111111111111111111111111 global free time 001111111111111111111111111111 person free time ============================== 001111111111111111111111111111 new global free time 

Для второго человека у нас есть два временных интервала: мы вычисляем каждый временной интервал с помощью метода know, затем мы составляем глобальное свободное время человека с помощью оператора OR , который устанавливает биты, если они установлены в первом или втором значении:

 000000000000001111110000000000 12:10 - 12:16 111111110000000000000000000000 12:22 - 12:30 ============================== 111111110000001111110000000000 person total free time 

Теперь мы обновляем глобальное свободное время с помощью того же метода, который используется для оператора первого лица ( AND ):

 001111111111111111111111111111 previous global free time 111111110000001111110000000000 person total free time ============================== 001111110000001111110000000000 new global free time └────┘ └────┘ :28-:22 :16-:10 

Как вы можете видеть, в конце у нас есть целое число с битами, установленным только в минутах, когда все доступны (вам нужно начинать отсчет справа). Теперь вы можете преобразовать это целое число в datetimes. К счастью, расширение GMP имеет способ найти смещение 1/0, поэтому мы можем избежать выполнения цикла for / foreach через все цифры (что в реальном случае намного больше 30).


Давайте рассмотрим полный код для применения этой концепции к вашему массиву:

 $calendar = [ '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'] ] ]; /* Get active TimeZone, then calculate min and max dates in minutes: */ $tz = new DateTimeZone( date_default_timezone_get() ); $flat = call_user_func_array( 'array_merge', $calendar ); $min = date_create( min( array_column( $flat, 'start' ) ) )->getTimestamp()/60; $max = date_create( max( array_column( $flat, 'end' ) ) )->getTimestamp()/60; /* Init global free time (initially all-free): */ $free = gmp_sub( gmp_pow( 2, $max-$min ), gmp_pow( 2, 0 ) ); /* Process free time(s) for each person: */ foreach( $calendar as $p ) { $pf = gmp_init( 0 ); foreach( $p as $time ) { $start = date_create( $time['start'] )->getTimestamp()/60; $end = date_create( $time['end'] )->getTimestamp()/60; $pf = gmp_or( $pf, gmp_sub( gmp_pow( 2, $end-$min ), gmp_pow( 2, $start-$min ) ) ); } $free = gmp_and( $free, $pf ); } $result = []; $start = $end = 0; /* Create resulting array: */ while( ($start = gmp_scan1( $free, $end )) >= 0 ) { $end = gmp_scan0( $free, $start ); if( $end === False) $end = strlen( gmp_strval( $free, 2 ) )-1; $result[] = [ 'start' => date_create( '@'.($start+$min)*60 )->setTimezone( $tz )->format( 'Ymd H:i:s' ), 'end' => date_create( '@'.($end+$min)*60 )->setTimezone( $tz )->format( 'Ymd H:i:s' ) ]; } print_r( $result ); 

Вывод:

 Array ( [0] => Array ( [start] => 2016-04-30 12:00:00 [end] => 2016-04-30 13:31:00 ) [1] => Array ( [start] => 2016-04-30 15:26:00 [end] => 2016-05-01 03:00:00 ) ) 

Демо-версия 3v4l.org

Некоторые дополнительные примечания:

  • В начале мы устанавливаем $tz в текущий часовой пояс: мы будем использовать его позже, в конце, когда мы создадим окончательные даты из временных меток. Даты, созданные из временных меток, находятся в формате UTC, поэтому мы должны установить правильный часовой пояс.
  • Чтобы получить начальные значения $min и $max за считанные минуты, сначала мы имеем плоский исходный массив, затем получаем min и max date с помощью array_column .
  • gmp_sub вычитает второй аргумент из первого аргумента, gmp_pow число (arg 1) к мощности (arg 2).
  • В последнем цикле while мы используем gmp_scan1 и gmp_scan0 для извлечения каждого интервала «111 ….», затем создаем возвращаемые элементы массива с использованием позиции gmp_scan1 для ключа start и позиции gmp_scan0 для end ключа.