查找给定日期之前的三个工作日

时间:2010-06-22 12:52:33

标签: php date

我需要在给定日期找到前三个工作日,省略周末和假日。这本身并不是一项艰巨的任务,但似乎我要这么做的方式会过于复杂,所以我想我会首先征求你的意见。

为了让事情变得更有趣,让我们把它作为一个比赛。我提供300作为赏金,无论谁提出最符合本规范的最简洁,最干净的解决方案:

  • 编写一个返回给定日期前三个工作日的函数
  • 工作日定义为星期六或星期日且不是假期的任何一天
  • 该函数知道给定日期年份的假期,并可以考虑这些因素
  • 该函数以Y-m-d格式
  • 接受一个参数,即日期
  • 该函数返回一个数组,其中包含三个Y-m-d格式的日期,从最旧到最新排序。

额外:

  • 除了前三个工作日之外,该功能还可以找到 next 三个工作日

假期数组的一个例子:

$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


最终解决方案

这是我最终使用的最终解决方案,改编自Keith Minkler关于使用strtotime的{​​{1}}的想法。检测通过计数的方向,如果为负,则向后搜索,并在正数上转发:

last weekday

12 个答案:

答案 0 :(得分:10)

这应该可以解决问题:

    // 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()函数中进行了硬编码,但我相信你会得到这个想法并调整它以适应它。其余的都是工作代码。

答案 1 :(得分:8)

您可以在strtotime中使用“last weekday”或“next thursday”等表达式,例如:

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;
}

答案 2 :(得分:3)

传递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;
}

答案 3 :(得分:3)

这是我使用PHP的DateTime类对它的看法。关于假期,考虑到你可以在一年内开始,在另一年结束。

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);
}

答案 4 :(得分:1)

这是我的理由:

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;
}

答案 5 :(得分:1)

编辑:

将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('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
        )

)

答案 6 :(得分:1)

我正在添加另一个答案,因为它采用了与我之前发布的方法不同的方法:

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
)

答案 7 :(得分:0)

你的意思就像Excel中的WORKDAY()函数

如果您查看PHPExcel中的WORKDAYS函数,您将找到如何编写此类函数的示例

答案 8 :(得分:0)

试试这个(公平警告 - 我无权测试这个,所以请更正任何语法错误)。

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);
}

基本上创建一个“大块”日期,然后使用数组差异从中删除假期。仅返回顶部(最后)三个项目。显然,与以前的答案相比,它需要更多的存储空间和计算时间,但代码要短得多。

可以调整“块”大小以进一步优化。理想情况下,这将是连续假期的最大数量加上2加3,但这假设是真实的假日场景(整周假期不可能,等等)。

代码也可以“展开”,以使一些技巧更容易阅读。总的来说,一些PHP功能更好一些 - 虽然可以与其他想法结合使用。

答案 9 :(得分:0)

/**
  * @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
)

答案 10 :(得分:0)

这是我的提交;)

/**
 * 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);
}

答案 11 :(得分:0)

这是我的看法。如果您在年初输入日期,则此功能(与大多数其他发布的功能不同)不会失败。如果您只在一年内调用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;
}