Ještě pořád se držíte JDK, když je po ruce Joda Time?

Po delší době jsem měl zase čas podívat se na zoubek v mém TODO listu. Tentokrát jsem si vzal na paškál poměrně malou knihovnu s názvem Joda Time. Cílem této knihovny je reimplementace Java API pro práci s datumy a časem. Každý z nás, kdo pracuje s Javou nějaký ten čas, se tu a tam potýká s tímto těžkopádným API. Joda Time přinesl poměrně hodně nových myšlenek a stal se základem pro JSR 310, které by mělo být součástí nové Javy 7. Často na toto téma naráží i pánové z Java Posse. Co je tedy na knihovně tak úžasného? Čtěte dál ...

...

Joda Time se snaží práci s časem přiblížit co nejvíce přirozenému způsobu zacházení s časem v běžném životě. Tak například leden v Joda Time je první měsíc roku a ne nultý jako v Java API - když si vzpomenu kolikrát jen mě tahle hloupost vypekla. Dokáže pracovat s přirozenými entitami jako je čas bez datumu, datum bez času, časová perioda, časový interval a doba trvání. Odbourává netransparentní seznam konstant, který je potřeba pro práci s Java API ve třídě Calendar. Výsledkem je i pozitivní zpřehlednění našeho vlastního API - v parametrech metod nám proplouvají smysluplné objekty (např. Interval openHours místo Date open, Date close), čitelnost kódu se výrazně zvyšuje.

Joda Time poměrně extenzivně využívá builder patternu, který je postaven na jednoduchém principu - volání metody na objektu vrací instanci stejné třídy jako návratovou hodnotu. Výsledným efektem je, že se dá volání metod přirozeně řetězit, což značně zvyšuje čitelnost kódu a přibližuje jeho vzhled přirozené lidské mluvě.

Nejčastěji používané objekty jsou immutable (neměnitelné), což znamená, že je bezpečné s nimi manipulovat paralelně ve více threadech současně, aniž bychom se museli starat o synchronizaci. Knihovna poskytuje i mutable varianty objektů, které se vyplatí používat pouze v případech, kdy dochází k větší manipulaci s časem na úrovni lokální proměnné (analogie k String a StringBuffer třídám).

Posuďte sami na ukázkovém kódu níže:


/*
 * Použití originálního Java API
 */
//vytvoření datumu 12.12.2007
Calendar cal = new GregorianCalendar(2007, 11, 12);
Date date = cal.getTime();
//nastavení hodiny v existujícím datumu
//(ještě že jsme si schovali Calendar objekt)
cal.set(Calendar.HOUR_OF_DAY, 5);
cal.set(Calendar.MINUTE, 0);
cal.set(Calendar.SECOND, 0);
cal.set(Calendar.MILLISECOND, 0);
date = cal.getTime();
//přičtení 1 měsíce k datu (pořád s sebou vlečeme calendar)
cal.add(Calendar.MONTH, 1);
date = cal.getTime();
//kolik minut je to od začátku roku?
Calendar startOfTheYear = new GregorianCalendar(2007, 0, 1, 0, 0, 0);
long minutes = (cal.getTimeInMillis() - startOfTheYear.getTimeInMillis()) / 60000;
//práce s periodou, chceme vypočítat řadu
//časových okamžiků s periodou 5 hodin, 5 vteřin
for(int i = 0; i < 5; i++) {
   cal.add(Calendar.HOUR, 5);
   cal.add(Calendar.MINUTE, 50);
   date = cal.getTime();
}
//zobrazení dne v týdnu na frontend
SimpleDateFormat fmt = new SimpleDateFormat("E", new Locale("cs"));
String dayOfWeek = fmt.format(date);
/*
 * Použití Joda Time API
 */
//vytvoření datumu 12.12.2007
DateTime jodaDate = new DateTime().withDate(2007, 12, 12);
//nastavení hodiny v existujícím datumu
jodaDate = jodaDate.withTime(5, 0, 0, 0);
//přičtení 1 měsíce k datu
jodaDate = jodaDate.plusMonths(1);
//kolik minut je to od začátku roku?
DateTime jodaStartOfTheYear = new DateTime(2007, 1, 1, 0, 0, 0, 0);
long jodaMinutes = new Duration(jodaStartOfTheYear, jodaDate).getMillis() / 60000;
//práce s periodou, chceme vypočítat řadu
//časových okamžiků s periodou 5 hodin, 5 vteřin
Period period = new Period(5, 50, 0, 0);
for(int i = 0; i < 5; i++) {
   jodaDate = jodaDate.plus(period);
}
//zobrazení dne v týdnu na frontend
String jodaDayOfWeek = jodaDate.dayOfWeek().getAsText(new Locale("cs"));

Pokrytí všech MUST-HAVE

Learning curve

Pročíst jednoduchý tutorial a přejít na toto API vám nedá víc jak 1 - 2 hodiny. Výsledné zpřehlednění kódu a ušetření "mentální" námahy při práci s datumy vám za to určitě stojí.

Interoperabilita se standardními Date & Time objekty v JDK

Základem je interoperabilita se objekty Date v základním API Javy. Konverze je jednořádková:


// from Joda to JDK
Date jdkDate = new DateTime().toDate();
// from JDK to Joda
DateTime dt = new DateTime(new Date());
// from Joda to JDK
GregorianCalendar jdkGCal = new DateTime().toGregorianCalendar();
// from JDK to Joda
DateTime dt = new DateTime(GregorianCalendar.getInstance());

Převod ze Stringu a na String

Podpora formátování datumů a v opačné směru parsování datumů ze Stringů. Základem je starý dobrý formát ze SimpleDateFormat, nicméně velmi zajímavá je i možnost vytváření vlastních parserů / formatterů následujícím způsobem:


DateTimeFormatter fmt = new DateTimeFormatterBuilder()
            .appendDayOfMonth(2) //první dva znaky znamenají den v měsíci
            .appendLiteral('-') //pak následuje znak pomlčky
            .appendMonthOfYearShortText() //následuje název měsíce ve zkrácené formě
            .appendLiteral('-') //opět následuje pomlčka
            .appendTwoDigitYear(1956)  //a pak číslo roku v dvoumístném formátu
            //toto konkrétně znamená povolený interval od roku 1906 do roku 2005
            //př. 08 - převede na rok 1908, 03 - převede na rok 2003
            .toFormatter();

Což dokáže převádět tam i zpět datumy ve formátu "12-Led-04".

Cenné NICE-TO-HAVE

Výkonnostně předčí standardní Java API

V dokumentaci se autoři Joda Time holedbají rychlostí, která má být ve všech případech větší než při použití standardního Java API. Já jsem si udělal jen jednoduchý testík, o kterém bych nechtěl absolutně tvrdit, že je průkazný, ale dal mi poměrně zajímavé výsledky. Úkázkový kód z prvního přikladu tohoto článku jsem spustil v milionu iterací a zpracování pomocí Joda Time zabralo asi jen okolo 45% času zpracování pomocí Java API. Příjemné.

Podpora pro automatické testy

Tento bonbónek mne velmi mile překvapil. Jedním z problémů při psaní testů pro kód operující se systémovým časem (typicky se jedná o testování metod pracujících s "aktuálními" entitami, které se odvozují od aktuálního systémového času). Při práci s klasickými datumovými objekty Java API nemáte jinou možnost (krom přenastavení systémového času, což bych jako taktiku zrovna nedoporučoval) než testovaným třídám vnutit čas zvenku tak, abyste mohli v testu zaručit "stabilní" testovací čas.

Joda Time umožňuje "virtuálně" přenastavit systémový čas, takže pokud v metodě pracujete s aktuálním časem přes defaultní konstruktory Joda API - tzn. new DateTime() a nikoliv new DateTime(System.currentTimeMillis()), můžete využít při testování virtuálně overridnutý čas:


DateTimeUtils.setCurrentMillisFixed(new DateTime(2007,12,1,12,0,0).getMillis());

Prostě další střípek do JUnit test enabled APIs.

Easy to use with Maven 2

Knihovna je buildovaná Mavenem, takže ve veřejné repository najdete aktuální verzi. Stačí když do svého pomu přidáte:


<dependency>
    <groupId>joda-time</groupId>
    <artifactId>joda-time</artifactId>
    <version>1.5.2</version>
</dependency>

... a můžete začít experimentovat.

Zdroje