Intereting Posts
Confusion PDO-only проблема: невозможно подключиться через сокет / Access denied / не удается подключиться к серверу (общий хост) memory_get_peak_usage () с "реальным использованием" Это хорошая идея использовать $ _SERVER в составе? Класс запроса Doctrine не в каталоге сущностей Только PHP Code Calculator с Clickable Button в качестве входа PHPExcel: ошибка «Невозможно прочитать файл» Преобразование Excel в PDF (.xlsx в .pdf) исключить аутентификацию аутентификации с помощью symfony2 Передать JSON с php на javascript Назначить debug_backtrace () переменной в PHP Отображение «требуется» только в одном флажке (динамический дисплей) Разница между эхом и print_r в php? не может нажимать элементы, отобранные другой страницей, используя ajax – $ (document) .on не работает Doctrine2: Невозможно выбрать сущность через идентификационные переменные, не выбирая хотя бы один псевдоним корня сущности Проблема при запросе создания сегментов API v3 с помощью PHP-оболочки заявка на компоновщик не указана в php-xsl

Найдите три предыдущих рабочих дня с определенной даты

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

Чтобы сделать вещи более интересными, давайте сделаем это конкурс. Я предлагаю 300 в качестве щедрости тому, кто придумал самое короткое, самое чистое решение, которое придерживается этой спецификации:

  • Напишите функцию, которая возвращает три предыдущих рабочих дня с определенной даты
  • Рабочий день определяется как любой день, который не является субботой или воскресеньем и не является праздником
  • Функция знает праздники за год данной даты и может учитывать их
  • Функция принимает один параметр, дату, в формате Ymd
  • Функция возвращает массив с тремя датами в формате Ymd , отсортированный от самого старого до самого нового.

Дополнительно:

  • Функция может также найти следующие три рабочих дня в дополнение к предыдущим трем

Пример массива праздников:

 $holidays = array( '2010-01-01', '2010-01-06', '2010-04-02', '2010-04-04', '2010-04-05', '2010-05-01', '2010-05-13', '2010-05-23', '2010-06-26', '2010-11-06', '2010-12-06', '2010-12-25', '2010-12-26' ); 

Обратите внимание, что в реальном сценарии праздники не жестко закодированы, а поступают из функции get_holidays($year) . Вы можете включить / использовать это в своем ответе, если хотите.

Поскольку я предлагаю щедрость, это означает, что будет как минимум три дня, прежде чем я смогу отметить ответ как принятый (2 дня, чтобы добавить щедрость, 1 день, пока я не смогу принять).


Заметка

Если вы используете фиксированную длину дня, например, 86400 секунд для перехода с одного дня на другой, вы столкнетесь с проблемами с летним временем. strtotime('-1 day', $timestamp) используйте strtotime('-1 day', $timestamp) .

Пример этой проблемы:

http://codepad.org/uSYiIu5w


Окончательное решение

Вот окончательное решение, которое я в конечном итоге использовал, адаптированный из идеи strtotime использовать last weekday strtotime . Определяет направление из пройденного счета, если оно отрицательное, выполняет поиск назад и вперед по положительному:

 function working_days($date, $count) { $working_days = array(); $direction = $count < 0 ? 'last' : 'next'; $holidays = get_holidays(date("Y", strtotime($date))); while(count($working_days) < abs($count)) { $date = date("Ymd", strtotime("$direction weekday", strtotime($date))); if(!in_array($date, $holidays)) { $working_days[] = $date; } } sort($working_days); return $working_days; } 

Вы можете использовать выражения типа «последний день недели» или «следующий четверг» в strtotime, например:

 function last_working_days($date, $backwards = true) { $holidays = get_holidays(date("Y", strtotime($date))); $working_days = array(); do { $direction = $backwards ? 'last' : 'next'; $date = date("Ymd", strtotime("$direction weekday", strtotime($date))); if (!in_array($date, $holidays)) { $working_days[] = $date; } } while (count($working_days) < 3); return $working_days; } 

Это должно сделать трюк:

  // Start Date must be in "Ymd" Format function LastThreeWorkdays($start_date) { $current_date = strtotime($start_date); $workdays = array(); $holidays = get_holidays('2010'); while (count($workdays) < 3) { $current_date = strtotime('-1 day', $current_date); if (in_array(date('Ym-d', $current_date), $holidays)) { // Public Holiday, Ignore. continue; } if (date('N', $current_date) < 6) { // Weekday. Add to Array. $workdays[] = date('Ym-d', $current_date); } } return array_reverse($workdays); } 

Я жестко закодирован в функции get_holidays (), но я уверен, что вы поймете эту идею и настроите ее. Остальное – это рабочий код.

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

 function last_workingdays($date, $forward = false, $numberofdays = 3) { $time = strtotime($date); $holidays = get_holidays(); $found = array(); while(count($found) < $numberofdays) { $time -= 86400 * ($forward?-1:1); $new = date('Ym-d', $time); $weekday = date('w', $time); if($weekday == 0 || $weekday == 6 || in_array($new, $holidays)) { continue; } $found[] = $new; } if(!$forward) { $found = array_reverse($found); } return $found; } 

Вот мой подход к нему, используя PHP-класс DateTime. Что касается праздников, то он учитывает, что вы можете начинаться через год и заканчивать другим.

 function get_workdays($date, $num = 3, $next = false) { $date = DateTime::createFromFormat('Ym-d', $date); $interval = new DateInterval('P1D'); $holidays = array(); $res = array(); while (count($res) < $num) { $date->{$next ? 'add' : 'sub'}($interval); $year = (int) $date->format('Y'); $formatted = $date->format('Ym-d'); if (!isset($holidays[$year])) $holidays[$year] = get_holidays($year); if ($date->format('N') <= 5 && !in_array($formatted, $holidays[$year])) $res[] = $formatted; } return $next ? $res : array_reverse($res); } 

Редактировать:

Изменено 86400 на -1 day хотя я не совсем понимаю, действительно ли это было проблемой.

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

 // ----------------------- // Previous 3 working days # this is almost the same that someone already posted function getWorkingDays($date){ $workdays = array(); $holidays = getHolidays(); $date = strtotime($date); while(count($workdays) < 3){ $date = strtotime("-1 day", $date); if(date('N',$date) < 6 && !in_array(date('Ym-d',$date),$holidays)) $workdays[] = date('Ym-d',$date); } krsort($workdays); return $workdays; } // -------------------------------- // Previous and Next 3 working days function getWorkingDays2($date){ $workdays['prev'] = $workdays['next'] = array(); $holidays = getHolidays(); $date = strtotime($date); $start_date = $date; while(count($workdays['prev']) < 3){ $date = strtotime("-1 day", $date); if(date('N',$date) < 6 && !in_array(date('Ym-d',$date),$holidays)) $workdays['prev'][] = date('Ym-d',$date); } $date = $start_date; while(count($workdays['next']) < 3){ $date = strtotime("+1 day", $date); if(date('N',$date) < 6 && !in_array(date('Ym-d',$date),$holidays)) $workdays['next'][] = date('Ym-d',$date); } krsort($workdays['prev']); return $workdays; } function getHolidays(){ $holidays = array( '2010-01-01', '2010-01-06', '2010-04-02', '2010-04-04', '2010-04-05', '2010-05-01', '2010-05-13', '2010-05-23', '2010-06-26', '2010-11-06', '2010-12-06', '2010-12-25', '2010-12-26' ); return $holidays; } echo '<pre>'; print_r( getWorkingDays( '2010-04-04' ) ); print_r( getWorkingDays2( '2010-04-04' ) ); echo '</pre>'; 

Выходы:

 Array ( [2] => 2010-03-30 [1] => 2010-03-31 [0] => 2010-04-01 ) Array ( [next] => Array ( [0] => 2010-04-06 [1] => 2010-04-07 [2] => 2010-04-08 ) [prev] => Array ( [2] => 2010-03-30 [1] => 2010-03-31 [0] => 2010-04-01 ) ) 

Вот мой вопрос:

 function business_days($date) { $out = array(); $day = 60*60*24; //three back $count = 0; $prev = strtotime($date); while ($count < 3) { $prev -= $day; $info = getdate($prev); $holidays = get_holidays($info['year']); if ($info['wday'] == 0 || $info['wday'] == 6 || in_array($date,$holidays)) continue; else { $out[] = date('Ym-d',$prev); $count++; } } $count = 0; $next = strtotime($date); while ($count < 3) { $next += $day; $info = getdate($next); $holidays = get_holidays($info['year']); if ($info['wday']==0 || $info['wday']==6 || in_array($date,$holidays)) continue; else { $out[] = date('Ym-d',$next); $count++; } } sort($out); return $out; } 

Я добавляю еще один ответ, поскольку он следует другому подходу из тех, которые я опубликовал раньше:

 function getWorkDays($date){ list($year,$month,$day) = explode('-',$date); $holidays = getHolidays(); $dates = array(); while(count($dates) < 3){ $newDate = date('Ym-d',mktime(0,0,0,$month,--$day,$year)); if(date('N',strtotime($newDate)) < 6 && !in_array($newDate,$holidays)) $dates[] = $newDate; } return array_reverse($dates); } print_r(getWorkDays('2010-12-08')); 

Вывод:

 Array ( [0] => 2010-12-02 [1] => 2010-12-03 [2] => 2010-12-07 ) 

Вы имеете в виду функцию WORKDAY () в Excel

Если вы посмотрите на функцию WORKDAYS в PHPExcel , вы найдете пример того, как закодировать такую ​​функцию

Попробуйте это (справедливое предупреждение – у меня нет доступа, чтобы проверить это, поэтому, пожалуйста, исправьте любые синтаксические ошибки).

 function LastThreeWorkdays($start_date) { $startdateseed = strtotime($start_date); $workdays = array(); $holidays = get_holidays('2010'); for ($counter = -1; $counter >= -10; $counter--) if (date('N', $current_date = strtotime($counter.' day', $startdateseed)) < 6) $workdays[] = date('Ym-d', $currentdate); return array_slice(array_reverse(array_diff($workdays, $holidays)), 0, 3); } 

В основном создайте «кусок» дат, а затем используйте array diff, чтобы удалить праздничные дни из него. Верните только верхний (последний) три элемента. Очевидно, что требуется меньше памяти и времени для вычисления, чем предыдущие ответы, но код намного короче.

Размер «куска» может быть изменен для дальнейшей оптимизации. В идеале это будет максимальное количество выходных дней плюс 2 плюс 3, но это предполагает реалистичные праздничные сценарии (целая неделя праздников невозможна и т. Д.).

Код может быть «развернут», чтобы сделать некоторые из трюков более легкими для чтения. В целом, некоторые из функций PHP немного улучшены – их можно сочетать с другими идеями.

 /** * @param $currentdate like 'YYYY-MM-DD' * @param $n number of workdays to return * @param $direction 'previous' or 'next', default is 'next' **/ function adjacentWorkingDays($currentdate, $n, $direction='next') { $sign = ($direction == 'previous') ? '-' : '+'; $workdays = array(); $holidays = get_holidays(); $i = 1; while (count($workdays) < $n) { $dateinteger = strtotime("{$currentdate} {$sign}{$i} days"); $date = date('Ym-d', $dateinteger); if (!in_array($date, $holidays) && date('N', $dateinteger) < 6) { $workdays[] = $date; } $i++; } return $workdays; } // you pass a year into get_holidays, make sure folks // are accounting for the fact that adjacent holidays // might cross a year boundary function get_holidays() { $holidays = array( '2010-01-01', '2010-01-06', '2010-04-02', '2010-04-04', '2010-04-05', '2010-05-01', '2010-05-13', '2010-05-23', '2010-06-26', '2010-11-06', '2010-12-06', '2010-12-25', '2010-12-26' ); return $holidays; } 

В этих функциях мы используем функцию adjacentWorkingDays() :

 // next $n working days, in ascending order function nextWorkingDays($date, $n) { return adjacentWorkingDays($date, $n, 'next'); } // previous $n workind days, in ascending order function previousWorkingDays($date, $n) { return array_reverse(adjacentWorkingDays($date, $n, 'previous')); } 

Вот тестирование:

 print "<pre>"; print_r(nextWorkingDays('2010-06-24', 3)); print_r(previousWorkingDays('2010-06-24', 3)); print "<pre>"; 

Результаты:

 Array ( [0] => 2010-06-25 [1] => 2010-06-28 [2] => 2010-06-29 ) Array ( [0] => 2010-06-21 [1] => 2010-06-22 [2] => 2010-06-23 ) 

вот мое представление;)

 /** * Helper function to handle year overflow */ function isHoliday($date) { static $holidays = array(); // static cache $year = date('Y', $date); if(!isset($holidays["$year"])) { $holidays["$year"] = get_holidays($year); } return in_array(date('Ym-d', $date), $holidays["$year"]); } /** * Returns adjacent working days (by default: the previous three) */ function adjacentWorkingDays($start_date, $limit = 3, $direction = 'previous') { $current_date = strtotime($start_date); $direction = ($direction === 'next') ? 'next' : 'previous'; // sanity $workdays = array(); // no need to verify the count before checking the first day. do { // using weekday here skips weekends. $current_date = strtotime("$direction weekday", $current_date); if (!isHoliday()) { // not a public holiday. $workdays[] = date('Ym-d', $current_date); } } while (count($workdays) < $limit) return array_reverse($workdays); } 

Вот мое занятие. Эта функция (в отличие от большинства других опубликованных) не будет терпеть неудачу, если вы введете дату в начале года. Если вы хотите только вызвать функцию get_holidays в get_holidays одного года, результирующий массив может включать даты, которые относятся к праздникам с предыдущего года. Мое решение снова вызовет get_holidays если мы вернемся назад в предыдущий год.

 function get_working_days($date) { $date_timestamp = strtotime($date); $year = date('Y', $date_timestamp); $holidays = get_holidays($year); $days = array(); while (count($days) < 3) { $date_timestamp = strtotime('-1 day', $date_timestamp); $date = date('Ym-d', $date_timestamp); if (!in_array($date, $holidays) && date('N', $date_timestamp) < 6) $days[] = $date; $year2 = date('Y', $date_timestamp); if ($year2 != $year) { $holidays = array_merge($holidays, get_holidays($year2)); $year = $year2; } } return $days; }