Найти три предыдущих рабочих дня с указанной даты - PullRequest
6 голосов
/ 22 июня 2010

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

Чтобы сделать вещи более интересными, давайте сделаем это соревнованием. Я предлагаю 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'
);

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

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


Примечание

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

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

http://codepad.org/uSYiIu5w


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

Вот окончательное решение, которое я в итоге использовал, адаптированное из идеи Кейта Минклера об использовании 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;
}

Ответы [ 12 ]

10 голосов
/ 22 июня 2010

Это должно сработать:

    // 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 (), но я уверен, что вы поймете идею и подстроите ее под себя. Остальное весь рабочий код.

8 голосов
/ 25 июня 2010

Вы можете использовать выражения типа «последний день недели» или «следующий четверг» в 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;
}
3 голосов
/ 24 июня 2010

Вот мой пример использования класса 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);
}
3 голосов
/ 22 июня 2010

Передайте 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;
}
1 голос
/ 24 июня 2010

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

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
)
1 голос
/ 22 июня 2010

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

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

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

<code>// -----------------------
// 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 '
';

Выходы:

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
        )

)
1 голос
/ 22 июня 2010

Вот мой пример:

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;
}
0 голосов
/ 27 июня 2010

Вот мой дубль.Эта функция (в отличие от большинства других опубликованных) не завершится ошибкой, если вы введете дату в начале года.Если бы вы вызывали функцию 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;
}
0 голосов
/ 26 июня 2010

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

/**
 * 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);
}
0 голосов
/ 25 июня 2010
/**
  * @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;
}

В этих функциях мы используем функцию 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
)
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...