Я должен найти три предыдущих рабочих дня с данной даты, опуская выходные и праздничные дни. Это не трудная задача сам по себе, но кажется, что способ, которым я собирался сделать это, будет чрезмерно сложным, таким образом, я думал, что попрошу Ваше мнение сначала.
Для создания вещей более интересными давайте сделаем это конкурсом. Я предлагаю 300 как щедрость тому, кто бы ни предлагает самое короткое, самое чистое решение, которое придерживается этой спецификации:
Y-m-d
форматY-m-d
формат, отсортированный от самых старых до новейших.Дополнительный:
Пример праздничного массива:
$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'
);
Обратите внимание, что в реальном сценарии, праздники не являются hardcoded, но прибывают из get_holidays($year)
функция. Можно включать / использование, что в ответе, если Вы желаете.
Поскольку я предлагаю щедрость, которая означает, что будет по крайней мере за три дня до того, как я смогу отметить ответ, как принято (2 дня для добавления щедрости, 1 день, пока я не могу принять).
Примечание:
При использовании фиксированного формата дня, такого как 86 400 секунд для спрыгивания со дня другому, Вы столкнетесь с проблемами с летним временем. Использовать strtotime('-1 day', $timestamp)
вместо этого.
Пример этой проблемы:
Конечное решение
Вот конечное решение, которое я закончил тем, что использовал, адаптированный от идеи Keith Minkler использовать strtotime
last weekday
. Обнаруживает направление от переданного количества, если отрицательный, поиски назад, и вперед на положительном:
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("Y-m-d", 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("Y-m-d", strtotime("$direction weekday", strtotime($date)));
if (!in_array($date, $holidays))
{
$working_days[] = $date;
}
}
while (count($working_days) < 3);
return $working_days;
}
Передайте 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('Y-m-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;
}
Вы имеете в виду функцию РАБДЕНЬ () в Excel
Если вы посмотрите на функцию РАБДНИ в PHPExcel , вы найдете пример того, как кодировать такую функцию
Редактировать:
Изменил 86400 на -1 день
, хотя я не совсем понимаю, действительно ли это было проблемой.
Внесены некоторые изменения в исходные функции, но в целом они остались прежними.
// -----------------------
// 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('Y-m-d',$date),$holidays))
$workdays[] = date('Y-m-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('Y-m-d',$date),$holidays))
$workdays['prev'][] = date('Y-m-d',$date);
}
$date = $start_date;
while(count($workdays['next']) < 3){
$date = strtotime("+1 day", $date);
if(date('N',$date) < 6 && !in_array(date('Y-m-d',$date),$holidays))
$workdays['next'][] = date('Y-m-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('Y-m-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('Y-m-d',$next);
$count++;
}
}
sort($out);
return $out;
}
Попробуйте это (справедливое предупреждение - у меня нет доступа для проверки, поэтому исправьте все синтаксические ошибки).
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('Y-m-d', $currentdate);
return array_slice(array_reverse(array_diff($workdays, $holidays)), 0, 3);
}
Обычно создайте «кусок» дат, а затем используйте массив diff для удаления из него праздников. Верните только три верхних (последних) предмета. Очевидно, что для вычислений требуется немного больше места для хранения и времени, чем в предыдущих ответах, но код намного короче.
Размер «фрагмента» может быть изменен для дальнейшей оптимизации. В идеале это должно быть максимальное количество выходных дней подряд плюс 2 плюс 3, но это предполагает реалистичные сценарии праздников (целая неделя праздников невозможна и т. Д.).
Код также можно «развернуть», чтобы облегчить чтение некоторых уловок. В целом демонстрирует некоторые функции PHP немного лучше - хотя их можно комбинировать с другими идеями.
Я добавляю еще один ответ, поскольку он следует подходу, отличному от тех, которые я опубликовал ранее:
function getWorkDays($date){
list($year,$month,$day) = explode('-',$date);
$holidays = getHolidays();
$dates = array();
while(count($dates) < 3){
$newDate = date('Y-m-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
)
Это должно помочь:
// Start Date must be in "Y-m-d" 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('Y-m-d', $current_date), $holidays)) {
// Public Holiday, Ignore.
continue;
}
if (date('N', $current_date) < 6) {
// Weekday. Add to Array.
$workdays[] = date('Y-m-d', $current_date);
}
}
return array_reverse($workdays);
}
Я жестко вписал функцию get_holidays(), но я уверен, что вы поймете идею и подстроите ее под себя. Остальное - рабочий код.
/**
* @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('Y-m-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;
}
В этих функциях мы используем parallelWorkingDays ()
функция:
// 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
)
Вот мой вариант, использующий класс DateTime в PHP. Что касается праздников, то он учитывает, что вы можете начать в одном году, а закончить в другом.
function get_workdays($date, $num = 3, $next = false)
{
$date = DateTime::createFromFormat('Y-m-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('Y-m-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);
}
вот мое сообщение;)
/**
* 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('Y-m-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('Y-m-d', $current_date);
}
} while (count($workdays) < $limit)
return array_reverse($workdays);
}
Вот мое мнение. Эта функция (в отличие от большинства других опубликованных) не даст сбой, если вы введете дату в начале года. Если бы вы вызывали функцию 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('Y-m-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;
}