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 |
Pá | 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.
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čí.
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 " "; 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ý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]." ".$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é.
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.