2010-06-22 68 views
5

Tôi cần tìm ba ngày làm việc trước đó từ một ngày nhất định, bỏ qua các ngày cuối tuần và ngày lễ. Đây không phải là một nhiệm vụ khó khăn trong chính nó, nhưng có vẻ như cách tôi sẽ làm nó sẽ quá phức tạp, vì vậy tôi nghĩ tôi sẽ hỏi ý kiến ​​của bạn trước.Tìm ba ngày làm việc trước đó từ một ngày cụ thể

Để làm cho mọi thứ thú vị hơn, hãy thực hiện cuộc thi này. Tôi đang cung cấp 300 như một tiền thưởng để bất cứ ai đi lên với các giải pháp ngắn nhất, sạch nhất mà tuân thủ các đặc điểm kỹ thuật này:

  • Viết một hàm trả về ba ngày làm việc trước đó từ một ngày nhất định
  • ngày làm việc được xác định như bất kỳ ngày nào không phải là thứ bảy hay chủ nhật và không phải là ngày lễ
  • Chức năng biết ngày lễ cho năm của ngày cụ thể và có thể đưa các tài khoản này vào tài khoản
  • Hàm này chấp nhận một tham số, ngày, trong Y-m-d định dạng
  • Hàm trả về một mảng có ba ngày ở định dạng Y-m-d, được sắp xếp từ cũ nhất đến mới nhất.

Extra:

  • Chức năng có thể tìm thấy cũng là tiếp theo ba ngày làm việc, thêm vào trước ba

Một ví dụ về mảng lễ:

$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' 
); 

Lưu ý rằng trong kịch bản thực tế, ngày lễ không phải là mã cứng d nhưng đến từ hàm get_holidays($year). Bạn có thể bao gồm/sử dụng nó trong câu trả lời của bạn nếu bạn muốn.

Khi tôi đang cung cấp tiền thưởng, điều đó có nghĩa là sẽ có ít nhất ba ngày trước khi tôi có thể đánh dấu câu trả lời được chấp nhận (2 ngày để thêm tiền thưởng, 1 ngày cho đến khi tôi có thể chấp nhận).


Note

Nếu bạn sử dụng một ngày dài cố định như 86400 giây để nhảy từ ngày này sang khác, bạn sẽ gặp vấn đề với thời gian tiết kiệm ánh sáng ban ngày. Sử dụng strtotime('-1 day', $timestamp) để thay thế.

Một ví dụ về vấn đề này:

http://codepad.org/uSYiIu5w


giải pháp cuối cùng

Dưới đây là giải pháp cuối cùng tôi đã kết thúc sử dụng, chuyển thể từ ý tưởng của việc sử dụng Keith Minkler của 's last weekday.Phát hiện sự chỉ đạo từ đếm trôi qua, nếu tiêu cực, tìm kiếm ngược, và chuyển tiếp vào dương:

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

Một vài câu trả lời có chức năng nhận ngày lễ theo năm - nhưng họ nhận được năm từ đầu vào - trong những năm đó vào ngày 1 tháng 1, thứ 6 trước đó là ngày lễ Mỹ Nhưng bảng cho ăn của năm 2011 tính ngày 31 tháng 12 năm 2010 là ngày lễ '2011', do đó, nó là một phần về việc tạo ra các bảng đúng trước thời hạn: http://www.opm.gov/operating_status_schedules/fedhol/2011.asp – Joe

+0

Điểm hợp lệ Joe. Lý tưởng nhất là "get_holidays" nên chấp nhận một ngày làm đối số của nó. Bằng cách đó, nó có thể trả lại tất cả các ngày lễ 1 tuần một trong hai bên của ngày đó (ngay cả khi tuần đó xảy ra kéo dài hơn một năm cuối cùng). – Wireblue

Trả lời

8

Bạn có thể sử dụng các biểu như "tuần cuối cùng" hoặc "next Thursday" trong strtotime, như thế này:

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

Thật tuyệt vời! Bây giờ đó là một giải pháp thanh lịch. Tôi đã làm sạch mã của bạn và thêm một vài tính năng. Phiên bản mới tại đây. http: // codepad.org/D3zU1DSA – Wireblue

+0

@wireblue, cảm ơn, bạn nói đúng rằng tôi nên đặt dòng "$ direction = ..." bên ngoài vòng lặp, vì không cần phải tiếp tục xác định nó, cảm ơn vì đã sửa! Tuy nhiên, tôi nghĩ rằng "của bạn trở lại ($ hướng)? ...: ...;" là không cần thiết, vì $ direction sẽ luôn luôn đánh giá TRUE (nó là một chuỗi có độ dài khác không), bạn có thể có nghĩa là $ backwards ở đây. –

+0

Rất tiếc, bạn đã đúng! $ direction nên có $ backwards. – Wireblue

0

Bạn có nghĩa là giống như WORKDAY() chức năng trong Excel

Nếu bạn có một cái nhìn tại các chức năng ngày công lao động trong PHPExcel, bạn sẽ tìm thấy ví dụ về cách mã chức năng như vậy

+0

Ah, tôi thấy không khá WORKDAY() từ các chỉnh sửa của bạn ... bạn cần tập hợp các ngày trở về, không chỉ là ngày cuối cùng –

3

Vượt qua true làm đối số thứ hai để chuyển tiếp theo thời gian thay vì lùi. Tôi cũng đã chỉnh sửa chức năng để cho phép hơn ba ngày nếu bạn muốn trong tương lai.

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

này nên làm như lừa:

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

Tôi đã chức năng mã hóa cứng trong get_holidays(), nhưng tôi chắc chắn bạn sẽ nhận được các ý tưởng và tinh chỉnh nó cho phù hợp. Phần còn lại là tất cả mã làm việc.

+0

Câu trả lời hay, ngắn gọn. Hãy xem nếu ai đó có câu trả lời đơn giản hơn, nếu không bạn sẽ tăng gấp bốn lần danh tiếng của mình :) –

+0

Điều này sẽ thất bại nếu bạn nhập ngày gần đầu năm và có ngày lễ gần cuối năm trước , vì chức năng của bạn sẽ không bao giờ kiểm tra những ngày nghỉ đó. –

+0

@josh. Vâng đúng rồi. Như tôi đã đề cập trên một trong các nhận xét khác của tôi, lý tưởng là bạn muốn chuyển ngày bạn đang làm việc xung quanh làm đối số cho hàm. Bằng cách đó, tất cả các ngày nghỉ 2 tuần, mỗi bên của ngày đó (ví dụ) sẽ được trả lại (ngay cả khi chúng kéo dài vào cuối năm). Tôi đồng ý với bạn mặc dù ... đi qua một năm như là một đối số không phải là một ý tưởng tốt. Tôi ám chỉ điều này trong câu trả lời của mình. ;) – Wireblue

1

Dưới đây là đường đi của tôi lúc đó:

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

Edit:

Thay đổi 86.400--1 day mặc dù tôi không hoàn toàn hiểu được nếu điều này là thực sự là một vấn đề.

Thực hiện một số sửa đổi đối với các chức năng ban đầu nhưng nó khá giống nhau.

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

Đầu ra:

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

Lưu ý rằng bằng cách sử dụng 86400 giây cố định trong một ngày, bạn sẽ gặp sự cố với thời gian tiết kiệm ánh sáng ban ngày. Điều tương tự cũng áp dụng cho các câu trả lời khác, vì vậy tôi đã cập nhật câu hỏi của mình để bao gồm điểm này. –

+0

Xin chào Tatu, bạn có thể giải thích tốt hơn không? Không phải là một ngày luôn luôn 24 * 60 * 60? Tại sao thời gian tiết kiệm ánh sáng ban ngày sẽ thay đổi giả định đó? Cảm ơn bạn đã cảnh báo! :-) – acm

+2

kiểm tra điều này: http://codepad.org/uSYiIu5w. Về cơ bản, nếu ngày đã cho nằm bên trong DST và ngày trước đó ở bên ngoài, ngày của bạn bị tắt một ngày. Điều này tất nhiên chỉ áp dụng cho múi giờ mà DST đang được sử dụng. –

1

tôi thêm câu trả lời khác vì nó tuân theo một cách tiếp cận khác nhau từ những người thân tôi đã đăng trước:

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

ra:

Array 
(
    [0] => 2010-12-02 
    [1] => 2010-12-03 
    [2] => 2010-12-07 
) 
+0

Câu trả lời hay và suy nghĩ mới với ngày chất nền. Và nhỏ gọn nhất (và vẫn có thể đọc được) cho đến nay vẫn nhận được công việc làm. –

+0

Cảm ơn bạn. Trong thực tế, tôi không biết tại sao tôi không nghĩ về nó sớm hơn nhưng tốt, tốt hơn sau này không bao giờ. :-) – acm

3

Đây là việc tôi sử dụng nó bằng cách sử dụng lớp DateTime của PHP. Về những ngày lễ, bạn cần tính đến việc bạn có thể bắt đầu trong một năm và kết thúc bằng một năm khác.

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

Hãy thử cái này (cảnh báo công bằng - Tôi không có quyền truy cập để kiểm tra điều này vì vậy hãy sửa bất kỳ lỗi cú pháp nào).

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

Về cơ bản tạo một "đoạn" ngày và sau đó sử dụng chênh lệch mảng để xóa ngày lễ khỏi nó. Chỉ trả lại ba mục trên cùng (cuối cùng). Rõ ràng phải mất một không gian lưu trữ nhỏ hơn và thời gian để tính toán hơn so với câu trả lời trước đó nhưng mã ngắn hơn nhiều.

Kích thước "chunk" có thể được tinh chỉnh để tối ưu hóa thêm. Lý tưởng nhất nó sẽ là số ngày nghỉ liên tiếp tối đa cộng với 2 cộng 3 nhưng giả định rằng các tình huống kỳ nghỉ thực tế (không thể thực hiện toàn bộ tuần nghỉ lễ, v.v.).

Mã này cũng có thể được "bỏ chọn" để làm cho một số thủ thuật dễ đọc hơn. Nói chung cho thấy một số chức năng PHP tốt hơn một chút - có thể được kết hợp với các ý tưởng khác.

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

Trong các chức năng này chúng tôi sử dụng adjacentWorkingDays() chức năng:

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

Đây là thử nghiệm:

print "<pre>"; 
print_r(nextWorkingDays('2010-06-24', 3)); 
print_r(previousWorkingDays('2010-06-24', 3)); 
print "<pre>"; 

Kết quả:

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

ở đây là trình của tôi;)

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

Dưới đây là quan điểm của tôi. Chức năng này (không giống như hầu hết những người khác được đăng) sẽ không thành công nếu bạn nhập ngày vào đầu năm. Nếu bạn chỉ gọi hàm get_holidays trong một năm, mảng kết quả có thể bao gồm các ngày là ngày lễ của năm trước. Giải pháp của tôi sẽ gọi lại số get_holidays nếu chúng tôi quay trở lại năm trước.

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; 
} 
Các vấn đề liên quan