2010-06-22 26 views
5

Ho bisogno di trovare tre giorni lavorativi precedenti da una determinata data, omettendo i fine settimana e le vacanze. Questo non è un compito difficile in sé, ma sembra che il modo in cui lo avrei fatto sarebbe stato eccessivamente complicato, quindi ho pensato di chiedere prima la tua opinione.Trova tre giorni lavorativi precedenti da una determinata data

Per rendere le cose più interessanti, facciamo di questo un concorso. Offro 300 come una taglia a chi esce con la soluzione più pulita più breve che aderisce a questa specifica:

  • scrivere una funzione che restituisce tre giorni lavorativi precedenti da una certa data
  • giorno di lavoro è definito come ogni giorno che non sia sabato o la domenica e non è una vacanza
  • la funzione conosce le vacanze per l'anno della data indicata e può tenerne conto
  • la funzione accetta un parametro, la data, in Y-m-d format
  • La funzione restituisce un array con tre date nel formato Y-m-d, ordinato dal più vecchio al più recente.

Extra:

  • La funzione può trovare anche le prossimi tre giorni lavorativi in ​​aggiunta alle precedenti tre

Un esempio di matrice vacanze:

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

Si noti che nello scenario reale, le festività non sono hardcode d ma provengono dalla funzione get_holidays($year). Puoi includerlo/usarlo nella tua risposta, se lo desideri.

Dato che sto offrendo una taglia, ciò significa che ci saranno almeno tre giorni prima che io possa contrassegnare una risposta come accettata (2 giorni per aggiungere una taglia, 1 giorno fino a quando non posso accettare).


Nota

Se si utilizza una lunghezza giorno fissato come 86400 secondi per passare da un giorno all'altro, si incorrerà in problemi con l'ora legale. Utilizzare invece strtotime('-1 day', $timestamp).

Un esempio di questo problema:

http://codepad.org/uSYiIu5w


soluzione finale

Ecco la soluzione finale ho finito per usare, tratto da un'idea di Keith Minkler di utilizzare 's last weekday.Rileva la direzione dal conteggio passato, se negativo, ricerca all'indietro, e indietro sul positivo:

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

Alcune delle risposte hanno una funzione per ottenere le feste per anno - ma ottengono l'anno dall'ingresso - per quegli anni in cui 1 gennaio è un Sabato, il Venerdì prima è una vacanza nel USA Ma il tavolo della fed per il 2011 conta il 31 dicembre 2010 come una vacanza '2011', quindi si tratta in parte di generare correttamente le tabelle in anticipo: http://www.opm.gov/operating_status_schedules/fedhol/2011.asp – Joe

+0

Punto valido Joe. Idealmente le "get_holidays" dovrebbero accettare una data come argomento. In questo modo è possibile restituire tutte le festività 1 settimana su entrambi i lati di tale data (anche se la settimana si verifica oltre l'anno). – Wireblue

risposta

8

È possibile utilizzare espressioni come "l'ultimo giorno della settimana" o "il prossimo giovedi" in strtotime, come questa:

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

Wow! Questa è una soluzione elegante. Ho ripulito il codice e aggiunto alcune funzionalità. Nuova versione qui. http: // tastiera codici.org/D3zU1DSA – Wireblue

+0

@wireblue, grazie, hai ragione che avrei dovuto mettere la linea "$ direction = ..." al di fuori del ciclo, dal momento che non è necessario continuare a definirla, grazie per la correzione! Tuttavia, penso che il tuo "ritorno (direzione $)? ...: ...;" non è necessario, dato che $ direction valuterà sempre a TRUE (è una stringa di lunghezza diversa da zero), potresti aver significato $ backward qui. –

+0

Oops, sì hai ragione! $ direzione avrebbe dovuto essere di $ indietro. – Wireblue

0

Vuoi dire come la funzione WORKDAY() in Excel

Se si dà un'occhiata alla funzione GIORNI FERIALI in PHPExcel, troverai un esempio di come codificare tale funzione

+0

Ah, non vedo affatto WORKDAY() dalle tue modifiche ... hai bisogno del set di date che ritornano, non solo dell'ultima data –

3

Passa il true come secondo argomento per andare avanti nel tempo invece che all'indietro. Ho anche modificato la funzione per consentire più di tre giorni se si desidera in futuro.

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

Questo dovrebbe fare il trucco:

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

ho funzione di hard-coded nei get_holidays(), ma sono sicuro che si otterrà l'idea e modificarlo per adattarsi. Il resto è tutto codice funzionante.

+0

Una buona risposta concisa. Vediamo se qualcuno ha una risposta ancora più semplice, altrimenti stai per quadruplicare la tua reputazione :) –

+0

Questo fallirà se inserisci una data vicina all'inizio dell'anno e ci sono festività vicine alla fine dell'anno precedente , poiché la tua funzione non controllerà mai quelle vacanze. –

+0

@josh. Sì, è corretto. Come ho menzionato in uno dei miei altri commenti, idealmente si vuole passare la data in cui si sta lavorando come argomento alla funzione. In questo modo verranno restituite tutte le festività di 2 settimane su entrambi i lati di tale data (ad esempio) (anche se si estendono per la fine dell'anno). Sono d'accordo con te però ... passare un anno come argomento non è una buona idea. Ho accennato a questo nella mia risposta. ;) – Wireblue

1

Ecco il mio andare a esso:

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:

Modificato il 86.400--1 day anche se non capisco pienamente se questo era davvero un problema.

Apporta alcune modifiche alle funzioni originali ma è praticamente la stessa.

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

Uscite:

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

Nota che utilizzando 86400 secondi in un giorno fisso, ti imbatterai in problemi con l'ora legale. Lo stesso vale per altre risposte, quindi ho aggiornato la mia domanda per includere questo punto. –

+0

Ciao Tatu, potresti spiegarlo meglio? Non è un giorno sempre 24 * 60 * 60? Perché l'ora legale cambierebbe questa ipotesi? Grazie per l'avviso comunque! :-) – acm

+2

controlla questo: http://codepad.org/uSYiIu5w. In sostanza, se la data specificata rientra in DST e il giorno precedente è all'esterno, le date sono disattivate di una. Questo ovviamente si applica solo ai fusi orari in cui è in uso l'ora legale. –

1

Sto aggiungendo un'altra risposta dal momento che segue un approccio diverso da quelli che ho postato prima:

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

uscita:

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

Buona risposta e nuovo modo di pensare con il sottostruttura diurna. E il più compatto (e ancora leggibile) fino ad oggi che fa ancora il lavoro. –

+0

Grazie. In effetti non so perché non ci ho pensato prima, ma bene, meglio tardi che mai. :-) – acm

3

Ecco la mia opinione su di esso utilizzando la classe DateTime di PHP. Per quanto riguarda le festività, si tiene conto che è possibile iniziare in un anno e terminare in un altro.

0

Provate questo (equo avviso - non ho accesso per testare questo, quindi si prega di correggere eventuali errori di sintassi).

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

Fondamentalmente creare un "blocco" di date e quindi utilizzare diff di matrice per rimuovere le festività da esso. Restituisce solo i primi (ultimi) tre articoli. Ovviamente ci vuole un minimo di spazio di archiviazione e tempo per calcolare rispetto alle risposte precedenti, ma il codice è molto più breve.

La dimensione del "blocco" può essere ottimizzata per ulteriori ottimizzazioni. Idealmente sarebbe il numero massimo di vacanze consecutive più 2 più 3, ma questo presuppone scenari di vacanza realistici (un'intera settimana di vacanze non è possibile, ecc.).

Il codice può essere "srotolato" anche per rendere alcuni dei trucchi più facili da leggere. Nel complesso mostra alcune delle funzioni di PHP un po 'meglio - potrebbe anche essere combinato con le altre idee.

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

In queste funzioni si usa la funzione 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')); 
} 

Ecco il test:

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

Risultati:

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

ecco la mia presentazione;)

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

Ecco il mio prendere. Questa funzione (diversamente dalla maggior parte degli altri pubblicati) non fallirà se si immette una data all'inizio dell'anno. Se si dovesse chiamare solo la funzione get_holidays su un anno, la matrice risultante potrebbe includere le date che sono feste dell'anno precedente. La mia soluzione chiamerà di nuovo get_holidays se torniamo indietro all'anno precedente.

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