PHP (17) - Dokončujeme kalendář

V této části seriálu o PHP dokončíme kalendář, který jsme začali psát v minulém dílu.

28.6.2004 15:00 | Petr Zajíc | přečteno 92403×

Připomeňme jen, že v minulém dílu jsme začali sestavovat kalendář, který bude nakonec vypadat nějak takhle:

červenec 2004
Po   5 12 19 26
Út   6 13 20 27
St   7 14 21 28
Čt 1 8 15 22 29
2 9 16 23 30
So 3 10 17 24 31
Ne 4 11 18 25  

Už jsme vyřešili jak zjistit počet dnů v měsíci a rovněž jak stanovit, který den v týdnu připadne na prvního. Dnes nás čeká napsat kód na zjištění počtu sloupců, kód na sestavení jednotlivých buněk a musíme to celé nějak dostat dohromady.

Počet sloupců

Je zřejmé, že jeden sloupec bude muset být vždy rezervován na názvy dnů. Dále je zřejmé, že vlastní kalednář si může vyžádat čtyři (únor v nepřechodném roce začínající pondělím), pět nebo i šest sloupců pro svoje data. Počet sloupců musíme znát ještě dříve, než budeme tabulku sestavovat.

Sám jsem při psaní kódu podlehl chybě popisované v minulém díle, a sice té, že jsem se pokusil sestavit matematické pravidlo, z něhož by počet sloupců vypadl. Jde to však jednodušeji - postačí nám donutit PHP k tomu, aby nám řeklo, zda daný měsíc zabírá rozmezí čtyř, pěti nebo šesti týdnů. Neboli, v kolikátém týdnu roku je prvního v měsíci a v kolikátém týdnu je posledního. Protože už máme funkci PocetDnu, je zjištění obojího záležitost jednoho řádku kódu:

<? $sloupcu = date("W", mktime(0, 0, 0, $mesic, $PocetDnu, $rok)) - date("W", mktime(0, 0, 0, $mesic, 1, $rok))+2; ?>

Dva sloupce se přičítají proto, že jeden je na názvy dnů, kdežto druhý je kvůli matematickému rozdílu mezi základními a řadovými číslovkami (31. mínus 27. týden je 5 ne 4).

Korektura: To jsem tomu dal! Praxe ukázala, že to v některých případech nebude fungovat. Například první leden roku 2000 má číslo týdne 52, zatímco poslední prosinec roku 2007 spadá do týdne číslo 1 následujícího roku. Je to snad nějaká chyba? Ani ne. Kdybych si přečetl manuál, zjistím, že PHP funkce date se při vracení pořadového čísla týdne v roce chová podle normy ISO-8601. Tato norma specifikuje formáty data a mimo jiné stanoví způsob, jakým se číslují týdny na přelomu měsíců prosince a ledna. jednoduše řečeno je to tak, že pokud více než tři dny přelomového měsíce spadají do ledna, je týden považován za první v novém roce, v opačném případě je považován za poslední týden v roce minulém.

Dlouho jsem přemýšlel, jak to jednoduše ošetřit, aniž bych zjišťování týdnů musel nějak dramaticky od základu přepsat. Výsledek je, že zjišťuji a odečítám ne počet týdnů mezi prvním a posledním dnem v měsíci, ale počet týdnů mezi osmým dnem od začátku a osmým dnem od konce. Vynechané dva týdny sice musím v závěru přičíst, ale zase se úplně vyhnu problémům s prosinci a ledny některých let. Opravený a funkční způsob zjištění počtu sloupců tedy je:

<? $sloupcu = date("W", mktime(0, 0, 0, $mesic, $PocetDnu-7, $rok)) - date("W", mktime(0, 0, 0, $mesic, 1+7, $rok))+4; ?>

Aneb: Člověk se stále učí.

Sestavení buňek

Je zřejmé, že dobrý nápad by byl použít dvě vnořené smyčky a sestavovat tabulku zleva doprava a odzhora dolů (ostatně jinak to ani nejde). Naspišme si tedy funkci, které pošleme řádek, sloupec, který den je prvního a kolik má měsíc dnů - a chtějme po ní jako výsledek obsah buňky kalendáře! Mohlo by to vypadat nějak takto:

<?
function Bunka ($radek, $sloupec, $PrvniDen, $PocetDnu)
{
  
$dny=Array(1=>"Po", "Út", "St", "Čt", "Pá", "So", "Ne");
  if (
$sloupec==1) return $dny[$radek];
  
$chcivratit = ($sloupec-2)*7 + $radek - $PrvniDen+1;
  if (
$chcivratit<1 || $chcivratit>$PocetDnu) return "&nbsp;"; else return $chcivratit;
}
?>

První dva řádky jsou myslím jasné; jestliže je v proměnné $sloupec jednička, nemůže to dopadnout jinak než že vrátíme zkratku dne. Ve třetím řádku zjišťujeme pomocí matematiky, jaké číslo v dané buňce pravděpodobně bude. Pravděpodobně proto, že to není jisté. Neexistují totiž záproné dny a nemůžeme zobrazit víc dnů, než kolik jich daný měsíc opravdu má. Což se testuje na řádku čtvrtém. Pokud jsme "v rozsahu", vrátí funkce dané číslo, pokud ne, vrátí HTML kód neoddělitelné mezery. To proto, abychom se vyhnuli problémům při zobrazování prázdných buněk v tabulkách.

Výsledek

Výsledkem našeho snažení bude funkce, která převezme dvě proměnné (měsíc, rok), sestaví kalendář a pošle jej do prohlížeče. Bude to vypadat takto:

<?
function Kalendar ($mesic, $rok)
{
  
$mesice=Array(1=>"leden", "únor", "březen", "duben", "květen", "červen", "červenec", "srpen", "září", "říjen", "listopad", "prosinec");
  
//kontroly
  
if (!is_numeric($mesic)) return "Měsíc musí být číslo!";
  if (!
is_numeric($rok)) return "Rok musí být číslo!";
  if (
$mesic<1 || $mesic>12) return "Měsíc musí být číslo od 1 do 12";
  if (
$rok<1980 || $rok>2050) return "Rok musí být číslo od 1980 do 2050";
  
// zjištění počtu sloupců
  
$PocetDnu = PocetDnu ($mesic, $rok); $PrvniDen = PrvniDen($mesic,$rok);
  
$sloupcu = date("W", mktime(0, 0, 0, $mesic, $PocetDnu-7, $rok)) - date("W", mktime(0, 0, 0, $mesic, 1+7, $rok))+4;
  
// vlastní kód
  
echo "<TABLE border=\"1\" style=\"border-collapse: collapse\" width=\"",$sloupcu*30,"\">";
  echo
"<TR><TD colspan=$sloupcu width=\"",$sloupcu*30,"\" align=\"center\">".$mesice[$mesic]."&nbsp;".$rok."</TD></TR>\n";
  for (
$radek=1;$radek<=7;$radek++)
  {
    echo
"<TR align=\"center\">";
    for (
$sloupec=1; $sloupec<=$sloupcu; $sloupec++) echo "<TD width=\"30\">".Bunka($radek, $sloupec, $PrvniDen, $PocetDnu)."</TD>";
    echo
"</TR>\n";
  }
  echo
"</TABLE>";
}
?>

Ukázat skript | Spustit skript

Při psaní reálného kódu je potřeba příliš nespoléhat na to, že vstupní parametry budou v pořádku, a proto je hned v úvodu funkce ověřujeme. Je zřejmé, že pokus o zobrazení například kalendáře za třináctý měsíc by skončil chybou, takže to víceméně musíme udělat. Co se týče vlastního kódu, začíná tím, že vypíše záhlaví tabulky. Protože už v té době víme, kolik bude sloupců, můžeme si troufnout i na tabulku s pevně danou šířkou (je to mimo jiné hezčí). Pak se píše záhlaví (například "červenec 2004") a nakonec se ve dvou vnořených smyčkách tvoří jednotlivé buňky; jejich hodnoty jsou zjišťovány opakovaným voláním funkce Bunka().

Pokud Vám nejsou jasné některé souvislosti, podívejte se v prohlížeči na zdrojový kód stránky, která kalendář zobrazuje. Věci, které patří spíše do znalostí HTML než k PHP (jako je třeba použití stylu) tady vysvětlovat nebudeme. Jedna věc by Vám ale přesto mohla vrtat hlavou, a to je způsob použití proměnné $sloupcu jako hodnoty atributu colspan ve druhém příkazu echo. Pokud to tak je, pak vězte, že se jedná o tzv. automatickou expanzi proměnné.

Automatická expanze proměnné

Za tímto strašným názvem se skrývá mechanismus, který nám při psaní kódu v PHP ušetří něco málo času při práci s řetězci. Nejlépe to vysvětlíme na příkladu. Následující kód provede dvakrát totéž:

<?
$uzivatel
="Petr";
$server="Linuxsoft";
echo
"Uživatel ".$uzivatel." je vítán na serveru ".$server."!<BR>";
echo
"Uživatel $uzivatel je vítán na serveru $server!<BR>";
?>

Neboli, pokud píšete řetězec v uvozovkách, můžete v něm použít proměnnou a ona se automaticky expanduje. Není to nic převratného, ale může to ušetřit nějaký čas při psaní kódu.

V dalším díle našeho seriálu se podíváme na fukce, které jsme zatím přeskočili - jsou to funkce pro práci s poli.

Online verze článku: http://www.linuxsoft.cz/article.php?id_article=212