2010-06-22 49 views
5

我需要查找給定日期之前的三個工作日,而不是週末和假日。這本身並不是一項艱鉅的任務,但似乎我打算這麼做的方式過於複雜,所以我想我會首先徵求您的意見。查找給定日期之前的三個工作日

爲了讓事情更有趣,讓我們來舉辦一場比賽。我出價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' 
); 

請注意,在真實場景中,節假日不是硬編碼d但來自get_holidays($year)的功能。如果你願意,你可以在你的答案中包含/使用它。

由於我提供的是賞金,這意味着至少有三天才能將答案標記爲已接受(2天添加賞金,1天直到我可以接受)。


注意

如果你使用一個固定的日長如86400秒從白天跳到另一個,你會碰到日光節約時間的問題。改爲使用strtotime('-1 day', $timestamp)

這個問題的一個例子:

http://codepad.org/uSYiIu5w


最終解決

下面是最終解決方案,我結束了使用,改編自使用的基思·明克勒的想法strtotimelast 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; 
} 
+0

一些答案有一個功能,可以按年份獲得假期 - 但是他們從輸入中獲得年份 - 對於那些在1月1日是星期六,星期五是假期的年份美國但美聯儲2011年的數據顯示2010年12月31日爲'2011'假期,所以部分原因是需要提前正確生成表格:http://www.opm.gov/operating_status_schedules/fedhol/2011.asp – Joe 2010-06-25 01:08:30

+0

有效點喬。理想情況下,「get_holidays」應該接受日期,因爲它是參數。這樣,它可以在該日期的任何一側返回所有節假日(即使該周恰好跨越一年)。 – Wireblue 2010-06-25 03:48:29

回答

8

您可以使用像的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; 
} 
+0

哇!現在這是一個優雅的解決方案。我清理了你的代碼並添加了一些功能。新版本在這裏。 HTTP://鍵盤。org/D3zU1DSA – Wireblue 2010-06-25 11:10:26

+0

@wireblue,謝謝,你說得對,我應該把「$ direction = ...」這一行放在循環之外,因爲沒有必要繼續定義它,謝謝修正! 但是,我認爲你的「回報($方向)?...:...;」是不必要的,因爲$方向總是計算爲TRUE(這是一個非零長度的字符串),你可能在這裏意味着$向後。 – 2010-06-25 13:06:12

+0

哎呀,是的,你是對的! $方向應該是$向後。 – Wireblue 2010-06-25 13:36:28

0

你的意思是像在Excel

工作日()函數,如果你看一看在工作日功能PHPExcel,你會發現如何編寫這樣一個函數的例子

+0

啊,我看不到你的編輯WORKDAY()...你需要一組返回的日期,而不僅僅是最後一個日期 – 2010-06-22 13:22:53

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; 
} 
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()函數,但我敢肯定你會得到的想法,並調整它來滿足。其餘的都是工作代碼。

+0

一個好的,簡潔的答案。讓我們來看看是否有人提出了更簡單的答案,否則您的信譽將翻兩番:) – 2010-06-22 17:05:59

+0

如果您輸入接近年初的日期並且接近上年年末有假期,因爲你的功能永遠不會檢查這些假期。 – 2010-06-26 22:15:45

+0

@josh。對,那是正確的。正如我在其他評論中提到的那樣,理想情況下,您希望將您工作的日期作爲參數傳遞給函數。那樣,在該日期兩週的所有假期(例如)都將被退回(即使它們跨越年底)。我同意你的觀點,雖然......以一年爲參數並不是一個好主意。我在我的回答中提到了這一點。 ;) – Wireblue 2010-07-04 08:58:18

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

) 
+1

請注意,通過在一天中使用固定的86400秒,您將遇到夏令時問題。這同樣適用於其他答案,所以我更新了我的問題以包含這一點。 – 2010-06-22 17:03:46

+0

嗨塔圖,你能解釋一下好嗎?不是一天總是24 * 60 * 60?爲什麼夏令時會改變這種假設?無論如何感謝提醒! :-) – acm 2010-06-22 17:17:02

+2

檢查此:http://codepad.org/uSYiIu5w。基本上,如果給定的日期落在DST內,並且前一天在外,則您的日期會關閉一次。這當然只適用於DST正在使用的時區。 – 2010-06-23 10:04:15

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 
) 
+0

好的答案和新穎的思維與日減法。迄今爲止,最緊湊(仍然可讀)的工作仍然完成。 – 2010-06-24 14:54:06

+0

謝謝。事實上,我不知道爲什麼我沒有早點考慮它,但是好,遲於永遠。 :-) – acm 2010-06-24 15:04:20

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); 
} 
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函數更好一些 - 可以與其他想法結合使用。

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 
) 
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); 
} 
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; 
} 
相關問題