Мне нужно найти три предыдущих рабочих дня с определенной даты, опуская выходные и праздничные дни. Это не сложная задача сама по себе, но похоже, что я собирался это сделать, было бы слишком сложно, поэтому я подумал, что сначала попрошу ваше мнение.
Чтобы сделать вещи более интересными, давайте сделаем это конкурс. Я предлагаю 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; }