skok na hlavní menu | menu sekce Aktuality

Úvod » Aktuality » Články » Jaký pracovní den je za zadaný počet pracovních dnů? PHP řešení

Jaký pracovní den je za zadaný počet pracovních dnů? PHP řešení

V životě i v aplikacích často dochází k situacím, že je potřeba znát den, který bude například za 4 pracovní dny. Tedy za 4 dny, do kterých se nepočítá sobota, neděle, ale ani státní svátky a jiné významné dny. Tento článek popisuje naše PHP řešení, ve kterém si můžeme přesně definovat, které dny v roce považujeme za svátky (nepracovní dny). A to včetně dnů s nestálým datem v roce jako jsou například Velikonoce.


Předem bude asi správné poznamenat, že na Internetu je k nalezení řada řešení, ale povětšinou jste v nich v něčem limitováni. Buď mezi nepracovní dny řadí pouze víkendy, nebo mají státní svátky pouze napevno v kódu a podobně. V našem řešení za nepracovní dny považujeme:

  • soboty a neděle,
  • dny, které si sami v databázi označíme jako nepracovní, a to včetně dnů, které každoročně připadají na jiný den.

Nyní již k samotnému řešení. To si rozdělíme na dvě části. Povíme si, co je třeba udělat na straně databáze, a pak si popíšeme, jak s údaji z databáze pracujeme v PHP.

Databázová stránka řešení

Databáze je v našem případě potřeba, protože umožňuje uživatelům nastavit, které dny považují za nepracovní. Tohoto by šlo docílit i pomocí konfiguračních souborů přímo v PHP, ale není to tak pohodlné řešení, co se týče provádění změn.

Konkrétně jsme si v databázi vytvořili tabulku, kterou v tomto článku budeme nazývat holidays. Do té si ukládáme záznamy o dnech v měsíci, které považujeme za svátky. U každého svátku máme uloženo jeho jméno (v obrázku níže name), měsíc v roce (month), den v roce (day) a ještě dva další údaje, které souvisí s tím, že chceme evidovat i dny v roce, které nemají stálé datum. V obrázku níže je vidět, že tyto údaje jsou ve sloupcích float_module a float_method. Ve float_module si ukládáme název třídy (modulu), ve které se nachází metoda, která vrátí datum konkrétního dnu v roce. Název této metody je uložen ve sloupci float_module. Protože lze přesné datumy ke dnům dopočítávat, nemusí být měsíc ani den povinné údaje.

To, jak do databáze dostat konkrétní záznamy, není součástí tohoto řešení. Někomu může stačit vykonat přímo v databázi potřebné INSERT scripty, někdo si bude chtít vyvinou rozhraní, přes které umožní uživatelům si vše naklikat. V první případě by scripty mohly vypadat takto:

INSERT INTO `holidays`(`name`,`mounth`,`day`,`float_module`,`float_method`) VALUES
("Velikonoce",NULL,NULL,"Holidays","getEaster"),
("Štědrý den",12, 24,NULL,NULL),
("1. svátek vánoční",12,25,NULL,NULL),
("2. svátek vánoční",12,26,NULL,NULL);

Vysvětlení: Pro získání datumu Velikonoc používáme třídu Holidays, ve které je metoda getEaster().

PHP stránka řešení

V této části uvedeme několik PHP metod, které používáme při práci se svátky. Všechny máme uloženy v jedné třídě (modulu) s funkcionalitami pro práci se svátky.

Zjištění, na kdy připadají Velikonoce

Ke zjištění, na jaký den v roce přichází Velikonoce, existuje v PHP funkce easter_date(). My si její výstup pouze ukládáme do jiného formátu.

public function getEaster(){
        $velikonoce=(easter_date());
        $mesic=date("m",$velikonoce);
        $den=date("d",$velikonoce);

        $ar=array();
        $ar["day"]=$den;
        $ar["month"]=$mesic;

        return $ar;
}

Získání všech svátků v roce včetně jejich datumů

V následující metodě getHolidays() získáme z databáze záznamy ke svátkům a tam, kde je nastaveno float_method a float_module, dopočteme konkrétní datum. V kódu je použita třída DB, která zastupuje nějakou třídu umožňující přístup k databázi a práci s ní. V této třídě máme metodu query, která provede databázový dotaz. Dále pak máme třídu, jejíž instance vrátí pomocí metody fetchAll() v poli všechny výsledky výše uvedeného databázového dotazu. K tomu existuje řada podobných řešení, proto není třeba uvádět zrovna to naše.

Dále v kódu uvádíme metodu getModule(). V té je vytvořena a vrácena instance potřebné třídy. Její tělo si naimplementujte tak, jak je pro vaši aplikaci nejvýhodnější.

public function getHolidays(){
        $db=new DB();
        $holidays= $db->query("SELECT * FROM holidays ORDER BY month,day")->fetchAll();

        //projdeme dny, kde je nastavena float_method, k nim budeme muset datum jeste dopocist
        $output=array();
        foreach($holidays as $day){
                if(isset($day["float_method"]) && isset($day["float_module"])){
                        $m=$day["float_module"];
                        $method=$day["float_method"];

                        $module=$this->getModule($m);

                        $retDay=$module->$method();

                        $day["day"]=$retDay["day"];
                        $day["month"]=$retDay["month"];
                }
                $output[]=$day;
        }
        return $output;
}

Jaký den bude za zadaný počet pracovních dnů bez uvažování svátků?

Jako odpověď na tuto otázku najdete na Internetu řadu algoritmů. My jsme použili řešení popsané v článku na stackoverflow.com , které jsme drobně upravili.

public function dateFromBusinessDays($days, $dateTime=null) {
        $dateTime = is_null($dateTime) ? time() : $dateTime;
        $_day = 0;
        $_direction = $days == 0 ? 0 : intval($days/abs($days));
        $_day_value = (60 * 60 * 24);

        while($_day != $days) {
                $dateTime += $_direction * $_day_value;
                $_day_w = date("w", $dateTime);
                if ($_day_w > 0 && $_day_w < 6) {
                        $_day    += $_direction * 1;
                }
        }
        return $dateTime;
}

Jaký den bude za zadaný počet pracovních dnů, když uvažujeme i svátky?

Toto se v našem případě řeší rekurzivní metodou getWorkingDay(), která používá metody uvedené výše. Spočteme si, jaký pracovní den bude za X pracovních dnů bez uvažování svátků (uvažují se tedy jen víkendy), a pak zjišťujeme, jestli na získaný časový úsek nepřipadají nějaké svátky. Pokud ano, datum se opět posune. Může se ovšem posunout na datum, které je opět o víkendu – potenciálně se tedy kvůli víkendům a svátkům může posunutí opakovat a skončí až tehdy, když už není posunutí třeba.

/**
 * vrati den, ktery bude od zadaneho datumu vzdaleny zadany pocet dni
 * @param - now - den, ktery je vztazny pro vypocet, format: Y-m-d
 * @param - offset - pocet pracovnich dni, ktere mame k zadanemu datumu pricist
 */
public function getWorkingDay($now,$offset){
        $feed_holidays=$this->getHolidays();//seznam svatku
        $finishDate=date("Y-m-d",$this->dateFromBusinessDays($offset,strtotime($now)));//mame koncovy datum, ale neuvazovali jsme svatky
        $parts=split("-",$finishDate);
        $mounthTo=$parts[1];
        $dayTo=$parts[2];
        $yearTo=$parts[0];
        $parts=split("-",$now);
        $mounthFrom=$parts[1];
        $dayFrom=$parts[2];
        $yearFrom=$parts[0];

        $pricistKvuliSvatkum=0;

        $from=strtotime($now);
        $to=strtotime($finishDate);

        foreach($feed_holidays as $day){//pokud svatek lezi mezi nasimi daty, tak pricteme den
                $d=$yearFrom."-".$day["mounth"]."-".$day["day"];//datum, kdy je svatek
                $timestamp=strtotime($d);//timestamp daneho svatku
                if($timestamp>$from && $timestamp<=$to && !$this->isInWeekend($timestamp)){ //svatek nesmi byt o vikendu, jinak je jiz zapocten
                        $pricistKvuliSvatkum++;
                }
        }

        //pokud mame kvuli svatkum pripocist urcity pocet dnu, tak hrozi, ze po dnu je zase vikend, coz nas vede vlastne k rekurzivnimu chovani

        if($pricistKvuliSvatkum==0) return $finishDate;
        else {
                //k datumu pripocteme pocet svatku
                return $this->getWorkingDay($finishDate,$pricistKvuliSvatkum);
        }
}



Poslat článek Nahoru



TOVARNA.CZ, s.r.o.

E-mail: info@tovarna.cz
Telefon: +420 274 776 344
Mobil: +420 739 654 469

Kancelář Praha

Doubravčická 1474/21
100 00 Praha 10
Telefon: +420 274 776 344