Zjištění věku z data narození

Školení, která pořádám

Jak postupovat, pokud máme v databázi uloženo datum narození a chceme z něj zjistit věk? MySQL nabízí funkci DATEDIFF, která vrátí rozdíl dvou dat ve dnech, převod na roky je ovšem kvůli přestupným rokům značně pracný. Ze stejného důvodu se nedá použít ani rozdíl dvou timestampů vydělených 365*24*60*60. Další funkce, která je v MySQL k dispozici, je PERIOD_DIFF, ta ale nepočítá se dny.

Úkol dokonale řeší funkce TIMESTAMPDIFF, která s parametrem YEAR udělá přesně to, co potřebujeme. K dispozici je od MySQL 5.0.

Ve starších verzích můžeme využít vlastnosti MySQL, že při matematických operacích s daty se data převedou na číslo zřetězením roků, měsíců a dnů. Pokud tedy provedeme CURDATE() - narozeni, získáme jakési podivné číslo, jehož spodní celá část po vydělení 10 000 ovšem dá požadovaný věk. Stejný trik se dá použít i v PHP:

<?php
echo floor((date("Ymd") - date("Ymd", $narozeni)) / 10000);
?>
Jakub Vrána, Řešení problému, 2.2.2007, diskuse: 48 (nové: 0)

Diskuse

Jeremy88:

Já jsem si na to kdysi dosti pracně napsal v dvě fkc a teď vidím, že to jde o dost jednodušeji.
To jsem byl ale ještě "PHP-začínající", tak jsem si na tom alespoň procvičil základy (substr, strstr, strlen, ...).

finc:

Pro hardcore vývojáře doporučuji cyklus a pomocí mktime přičítat, než se dostanu na stejnou hodnotu :D
Pokud zjišťuji věk, tak mi stačí počet let, ne? Na to myslím není třeba speciálních funkcí.

Karaya1:

»Pokud zjišťuji věk, tak mi stačí počet let, ne? Na to myslím není třeba speciálních funkcí.«

To není tak úplně pravda, když se uživatel narodí třeba v červenci a ty to kontroluješ třeba v únoru, tak by Tvá metoda vrátila věk o jedna větší než být má.

ikona Fruiko:

Já to taky tak dělám, akorát ještě kontroluji jestli uživatel neměl narozeniny. Pokud ne, tak tu jedničku zase odečtu.

ikona Jakub Vrána OpenID:

Takhle se měří věk u filmů a u koní. Film uvedený 31.12. je 1.1. už rok starý, stejně tak kobyla narozená 31.12.2004 je 1.1.2007 už tříletá. U lidí to tak jednoduché není.

finc:

To máš pravdu, ale otázkou je, co budu považovat za nejpřesnější jednotku, jestli rok, měsíc, den, hodinu nebo minutu.
Holt někdo by byl rád mladý do poslední vteřiny :)

ikona Jakub Vrána OpenID:

Na planetě Zemi se obvykle ve věku počítá s přesností na dny. V hospodě ti nalejou, i když slavíš osmnáctiny a neptají se, jestli ses narodil ráno nebo večer ;-).

Slash:

Nenalejou, až den po narozeninách, teda aspon by neměli. Tohle je jedna z mála věcí co si pamatuju ze základky :)

Ale jinak super funkce, díky za tip vždycky jsem přemýšlel jak to elegantně vypočítat.

ikona Jakub Vrána OpenID:

Ať tak nebo tak, na čas narození se tě neptají.

pavel:

nevím kam jsi chodil do školy, ale dle zákona jsi plnoletý od 0:00 v den svých 18. narozenin. Takže ti nejspíš nechtěli nalejt protože už jsi byl namol =D

Ehm?:

Jasně, proč nepoužít rovnou DATE_SUB(date,INTERVAL expr unit), když to jde dělat složitě

ikona Jakub Vrána OpenID:

Zkus se na chvíli zamyslet nad tím, co napíšeš do toho "expr unit". Nepotřebuješ od data něco odečíst (to by se dalo použít, pokud bys chtěl zjistit, jestli je uživatel starší než X let), potřebuješ rozdíl dvou dat.

Orion:

SELECT DATE_FORMAT(FROM_DAYS(TO_DAYS(NOW())-TO_DAYS(dn)), '%Y')+0 AS age FROM people

dn = datum narozeni

ikona Jakub Vrána OpenID:

Problém je opět s přestupnými roky: Když pominu, že DATE_FORMAT(FROM_DAYS(365), '%Y') vrací 0, takže ve vzorci by mělo být ještě +1, tak dostaneme špatné výsledky třeba 9.10.2004 pro lidi narozené 10.10.2003 (vyjde, že už jim je rok, i když zatím není).

Pavel Zbytovský:

S tímhle mi vyrůstá ještě další problém, jak vypsat uživatele, kteří budou mít nejblíže narozeniny. Napadlo mě seřadit si všechny narozené dnes a později (roky ignorujeme), jenže to tenhle dotaz nám nikdy nevrátí ty narozené dřív než dnes.

Napadlo mě jedině spojit dva dotazy UNIONem, ale nevím, jestli je to nejelegantnější.

ikona D1ce:

Nevím jestli jsem správně pochopil dotaz, ale mělo by stačit(tobd je počet dní zbývajících do narozenin a bd je samotné datum narození):

Oba dotazy jsou dost neefektivní, ale první je asi o 5 procent rychlejší:

SELECT IF((DATE_FORMAT(bd, "%j") - DATE_FORMAT(CURDATE(), "%j")) < 0, DATE_FORMAT(bd, "%j") - DATE_FORMAT(CURDATE(), "%j") + DATE_FORMAT(CONCAT(YEAR(CURDATE()), "-12-31"), "%j"), DATE_FORMAT(bd, "%j") - DATE_FORMAT(CURDATE(), "%j")) as tobd, bd FROM birthdays ORDER BY tobd ASC;

SELECT IF((DATE_FORMAT(bd, "%j") - DATE_FORMAT(CURDATE(), "%j")) < 0, DATE_FORMAT(bd, "%j") - DATE_FORMAT(CURDATE(), "%j") + IF((!(YEAR(CURDATE()) % 4) && (YEAR(CURDATE()) % 100)) || (!(YEAR(CURDATE()) % 400)), 366, 365), DATE_FORMAT(bd, "%j") - DATE_FORMAT(CURDATE(), "%j")) as tobd, bd FROM birthdays ORDER BY tobd ASC;

Testováno na MySQL 5.x

ikona D1ce:

Btw. možná existuje MySQL fce co vrátí počet dní přímo a mohla by být i ryhclejěí a přehlednější.

ikona Jakub Vrána OpenID:

Jedná se o funkci DAYOFYEAR.

ikona Jakub Vrána OpenID:

Problém je zase s přestupnými roky:

DAYOFYEAR('2003-03-01') = 60;
DAYOFYEAR('2004-03-01') = 61;

Kvůli tomu se můžou data seřadit špatně, navíc se u někoho může zdát, že už narozeniny měl, i když je má právě dnes.

Ze stejného důvodu je špatně i přičtení 365 nebo 366 - používá se vždy aktuální rok, ale od 1.3. by se měl použít už následující.

Proto bych použil raději něco takovéhoto:

SELECT DATEDIFF(IFNULL(CONVERT(CONCAT(YEAR(CURDATE()) + IF(RIGHT(bd, 5) >= RIGHT(CURDATE(), 5), 0, 1), RIGHT(bd, 6)), DATE), CONCAT(YEAR(CURDATE()) + IF(RIGHT(bd, 5) >= RIGHT(CURDATE(), 5), 0, 1), '-03-01')), CURDATE()) tobd FROM birthdays;

Popis:

Pokud už narozeniny letos člověk měl, tak se použije přiští rok (IF(RIGHT(bd, 5) >= RIGHT(CURDATE(), 5), 0, 1)).

CONVERT('nepřestupný_rok-02-29', DATE) vrátí NULL, v tom případě se použije '-03-01'.

Pokud ve sloupci mohou být hodnoty NULL, musí se výraz upravit třeba takhle: IF(bd IS NULL, NULL, výraz).

ikona D1ce:

Děkuji moc za ozřejmění a opravení, ale byl bych moc rád, kdyby jste prosím vysvětlil:

Citace:
Ze stejného důvodu je špatně i přičtení 365 nebo 366 - používá se vždy aktuální rok, ale od 1.3. by se měl použít už následující.

Píšete "ze stejného důvodu", ale já žádnou analogii s předchozím bohužel nevidím, přestože ten přechozí text chápu. Ještě jednou děkuji za Vaši přinosnou činnost a případně i za vševysvětlující příspěvek. :)

ikona Jakub Vrána OpenID:

Řekněme, že je 31.12.2003. Chci vědět, za jak dlouho má narozeniny člověk narozený 1.3.2003. Podle vašeho vzorce by to bylo:

+ DATE_FORMAT(bd, "%j") = 60
- DATE_FORMAT(CURDATE(), "%j") = 365
+ DATE_FORMAT(CONCAT(YEAR(CURDATE()), "-12-31"), "%j") = 365

Celkem 60, správně je ale 61 (rok 2004 je přestupný), protože v poslední části vzorce se musí použít už příští rok.

ikona D1ce:

Děkuji mnohokrát, už je mi vše naprosto jasné, ale přeci si jen mírně rýpnu. Co když určitá část národa narozená 2.29. slaví, když není přestupný rok, 2.28.? :P To by chtělo nějakou spešl tabulku pro ony jedince, kde by bylo id jedince a booleovská hodnota, kdy že to vlastně slaví. ;)

ikona Jakub Vrána OpenID:

No, moje babička mi taky vždycky volá den předem a to ani nejsem narozen 29.2. :-). 28.2. těm lidem o rok víc zkrátka ještě není, kdy to slaví, je podružné.

ikona Fruiko:

SELECT jmeno, (YEAR(CURDATE())-YEAR(narozeni)) as vek, date_format(narozeni,'%e.%c.') as datum FROM lide WHERE right(narozeni,5) BETWEEN right(CURDATE(),5) AND right(DATE_ADD( CURDATE( ) , INTERVAL 7 DAY ),5) AND umrti = '0000-00-00' AND narozeni != '0000-00-00' ORDER BY right(narozeni,5) ASC

Vypisuje narozeniny na 7 dní dopředu, třeba ve formátu 5.7. Jméno (věk). Odzkoušeno na MySQL 4.x.

ikona D1ce:

Už jsem to sice viděl(díky moc za článek v češtině), ale stále nepřestávám obdivovat jak je to jednoduché a přitom funkční! I když možná by se do článku hodil nějaký nástin proč to tak vlastně funguje, chvilku mi trvalo než mi to docvaklo.

mike:

no taky by sem prijal nejaky nastin... Mam tet stoho akorat tak chaos co pouzit

mike:

Pri tomhle to:
<?php
echo floor((date("Ymd") - date("Ymd", $narozeni)) / 10000);
?>
by se seslo napsat co ma byt v promenne $narozeni... Jestli time() tak to nevim presne na sekundy kdy se clovek narodil, jenom rok, mesic, den

ikona Jakub Vrána OpenID:

Ze dne, měsíce a roku se dá timestamp vyrobit funkcí mktime().

mike:

nemnelo to byt nahodou takhle:
<?php
echo floor((date("Ymd") - $narozeni) / 10000);
?>
?
protoze tet mi to uz funguje.

Tohle hazelo nesmysli:
<?php
echo floor((date("Ymd") - date("Ymd", $narozeni)) / 10000);
?>

ikona Jakub Vrána OpenID:

To záleží na tom, co je uloženo v proměnné $narozeni. Pokud UNIX timestamp, tak je správně druhá varianta, pokud Ymd, tak první.

jarks:

Pěkné. Ocenil bych nějakou funkci, co by převedla na datum narození rodné číslo. Myslel jsem, že udělat takovou věc je velmi jednoduché, ale není.

ikona Jakub Vrána OpenID:


<?php
/** Zjištění data narození z rodného čísla
* @param string $rodne_cislo rodné číslo ve formátu rrmmdd/xxxx
* @return string datum ve formátu rrrr-mm-dd
* @copyright Jakub Vrána, http://php.vrana.cz
*/
function datum_rc($rodne_cislo) {
    if (preg_match('~^([0-9]{2})([0-9]{2})([0-9]{2})/([0-9]{3,4})$~', $rodne_cislo, $match)) {
        return (strlen($match[4]) < 4 || $match[1] >= 54 ? "19" : "20") . "$match[1]-" . sprintf("%02d", $match[2] % 50) . "-$match[3]";
    }
    return false;
}
?>

Fanda Šidák:

Pěkná a užitečná funkce. Díky. :) Je rok 1954 nějak systémově závazný (např. nějakými pravidly pro rodná čísla)?

Fanda Šidák:

Aha, už to mám. Je to dáno zákonem § 13 odst. 1 zákona č. 133/2000 Sb., o evidenci obyvatel a rodných číslech (http://www.mvcr.cz/sbirka/2000/sb039-00.pdf) viz. http://cs.wikipedia.org/wiki/Rodn%C3%A9_%C4%8D%C3%ADslo.

Misha:

A co kdyz potrbuji jen odecist od dnesniho data jeden mesic?

ikona Jakub Vrána OpenID:

K tomu je nejlepší v MySQL použít:

CURDATE() - INTERVAL 1 MONTH

Libor Skala:

Potřeboval bych něco podobného. Situace je následující. Do repertoáru kapely přidam novou skladbu a ta se zvýrazní jako novinka. Chtěl bych pro formátování použít CSS a když by datum přidíní splňovalo stáří měsíce, použilo by se odlišné formátování. pokud by to šlapalo pod PHP4.3.4 Díky

stealth:

dik moc, prave taketo nieco som uz hodnu chvilu hladal ;-)

Petr:

Ahoj, vymýšlím jak z data narození vypočítávat, jestli bude někdo mít narozeniny během 5 dnů. Je to teda makačka na bednu. Nevíte někdo jak na to? Jen podotýkám, že nemám data v MySQL a tudíž nemám k dispozici zmíněné funkce (data jsou v Paradox přes ODBC). Snažím se to tedy vyřešit pomocí php. Díky moc za tipy.

vojta:

To je jednoduché, vytvoříš si v PHP timestamp pomocí strtotime ('-5 days') a potom kontroluješ u všech záznamů, zda 'time'>=strtotime('-5 days'). Kdyžtak se pro jistotu koukni do manuálu PHP.

Jrx:

Nějak mi to nefunguje, jak by mělo...

při použití:
echo ((date("Ymd") - $narozeni) / 10000)*365;

by měl koeficient přepočítat na dny, mám pravdu? je to dost nepřesné. Zřetelné to je, když $narozeni naplníte jen pár dní starým datem. Existuje nějaká funkce, která mi to v PHP spočítá na den přesně?

ikona Liborse:

Zdravím,

napsal jsem si svou vlastní funkci, měla by být (snad) přesná. Používám ji na svých stránkách. Testoval jsem různá data a ošetřil chyby (a že těch pokusů, i cílených, bylo dost). Můj PHP skript si můžete stáhnout na mých stránkách v sekci "Download" (http://liborse.mechenice.net/download.php) či si ho vyzkoušet zde: http://liborse.mechenice.net/vek/index.php. Je tam i kód, který stačí vložit právě do stránky a pak jen dál použít proměnnou "vek". Je možné, že ten skript není dokonalý či obsahuje chyby, takže pokud je najdete, budu rád, když mě kontaktujete (mail mám na stránkách). Doufám, že to někomu pomůže.

ikona Jagen:

nemas osetreni vkladani pismen a nul, mozna by se toho naslo vic

vojta:

Ahoj, a co když se ten člověk narodil třeba v roce 1945? To už je mimo rozsah čísel v PHP, ne?

ikona Pája:

Ahoj,
rád bych do své stránky chtěl vložit informaci o věku pejska.

Představoval bych si to takhle:

Místo "Dnes je mi 74 dní" by se zobrazilo "Dnes mi je 2 měsíce a 13 dní.

Prosím Vás o napsání příkladu.

Všem dopředu MOC děkuji :)

Pája

Jack:

DObrý den mám dotaz, nějak to nechápu...
Mám v databázi tabulku type dat tzn: 1990-15-02 takto datum
a potřeboval bych získat věk s jedním desetinným místem tzn např 19.2 let a vůbec nevím jak pořád mi to píše 39 let nebo chybu numeric nebo co :-( prosím prosím.. děkuji

Eldik:

Ahoj, čistě teoreticky, zajímalo by mě, jestli někdy failne tohle, případně jaké to má nevýhody (opomenu-li vyšší spotřebu proměnných, na druhou stranu získám hezky upravitelný kód pro jakékoli lidsky vkládané datum):
<?php
$zaznam
= "1998-02-15"; // ukazka vstupu
$vek = 0;
if (!
is_null($zaznam) and $zaznam != "") {
  $poleNarozeni = explode("-",$zaznam);
  $vek = date("Y") - $poleNarozeni[0];
  if ($poleNarozeni[1] > date("m")) { // porovname mesice
    $vek -= 1;
  } elseif (($poleNarozeni[1] = date("m")) and ($poleNarozeni[2] >= date("d"))) { // porovname dny v mesici
    $vek -= 1;
  }
}
echo
$vek;
?>

jony:

ELDIK - toto je zatial co pozeram super, uz som vyskusal asi 5 kodov a ziadny nebol na 100%, divne je ze ani oficialna funkcia v php - DetermineAgeFromDOB nefunguje
dik

Diskuse je zrušena z důvodu spamu.

avatar © 2005-2024 Jakub Vrána. Publikované texty můžete přetiskovat pouze se svolením autora. Ukázky kódu smíte používat s uvedením autora a URL tohoto webu bez dalších omezení Creative Commons. Můžeme si tykat. Skripty předpokládají nastavení: magic_quotes_gpc=Off, magic_quotes_runtime=Off, error_reporting=E_ALL & ~E_NOTICE a očekávají předchozí zavolání mysql_set_charset. Skripty by měly být funkční v PHP >= 4.3 a PHP >= 5.0.