Joda-Time — biblioteka do pracy z datami i czasem w Javie, stworzona przez Stephena Colebourne'a jako alternatywa dla niewygodnych java.util.Date i java.util.Calendar. Przed wydaniem Javy 8 biblioteka ta była de facto standardem pracy z datami w rozwoju przemysłowym, w tym w aplikacjach Android. Według danych Oracle Java Magazine (2023), ponad 60% projektów migrowało z Joda-Time na java.time w ciągu dwóch lat po wydaniu Android API 26.
Najważniejsze
Joda-Time — to biblioteka typu open source (Apache 2.0), zapewniająca wysokiej jakości zastąpienie klas java.util.Date i java.util.Calendar w języku Java. Projekt został założony przez Stephena Colebourne'a w 2004 roku w odpowiedzi na liczne problemy standardowych API do pracy z datami, w tym mutowalność, złożoność formatowania i ograniczone wsparcie stref czasowych. Biblioteka zyskała szerokie rozpowszechnienie w społeczności Java i była używana przez tysiące projektów, w tym duże systemy klasy enterprise i aplikacje Android, gdzie wbudowane narzędzia do pracy z datami były szczególnie niewygodne. Od momentu wydania Javy 8 w 2014 roku i pojawienia się pakietu java.time (JSR-310) biblioteka została przeniesiona w tryb utrzymania — nowe funkcje nie są dodawane, a istniejąca funkcjonalność jest w pełni pokrywana przez nowe API.
Joda-Time jest zbudowana na zasadzie niemutowalności: każdy obiekt daty lub czasu po utworzeniu nie może zostać zmieniony. Każda operacja — dodanie dnia, zmiana miesiąca, ustawienie strefy czasowej — zwraca nowy obiekt, pozostawiając oryginał bez zmian. Takie podejście całkowicie eliminuje klasę błędów związanych z niezamierzoną modyfikacją obiektów, które były charakterystyczne dla mutowalnych java.util.Date i java.util.Calendar. Immutable-design czyni również bibliotekę bezpieczną wątkowo bez dodatkowej synchronizacji.
Biblioteka udostępnia kilka głównych klas dla różnych scenariuszy: DateTime — pełna data i czas ze strefą czasową, LocalDate — tylko data bez czasu, LocalTime — tylko czas bez daty, LocalDateTime — data i czas bez przypisania do strefy. Ponadto dołączone są klasy do pracy z interwałami (Interval), okresami (Period) i czasami trwania (Duration), które umożliwiają obliczanie różnicy między dwoma momentami czasu w różnych jednostkach miary. Każda klasa implementuje interfejs ReadableInstant lub ReadablePartial dla jednolitego przetwarzania.
Problem standardowych API dat w Javie był znany od samego początku platformy. Klasy java.util.Date i java.util.Calendar miały fundamentalne wady: miesiące numerowane były od 0, Date jednocześnie reprezentował datę i czas, Calendar był mutowalny i wymagał złożonej konfiguracji. Formatowanie przez SimpleDateFormat nie było bezpieczne wątkowo — w aplikacjach wielowątkowych prowadziło to do nieprawidłowego parsowania dat. Stephen Colebourne, pracując nad projektami intensywnie wykorzystującymi daty, postanowił stworzyć alternatywę, która naprawiłaby wszystkie te problemy.
Według wywiadu na InfoQ (2014), Colebourne testował Joda-Time w projektach przemysłowych przez ponad rok przed pierwszym publicznym wydaniem. Pierwsza wersja ukazała się w 2004 roku i od razu przyciągnęła uwagę społeczności. Do 2010 roku biblioteka stała się standardową zależnością w większości projektów Java, w tym w takich frameworkach jak Spring i Hibernate. Sukces Joda-Time bezpośrednio wpłynął na specyfikację JSR-310 (Date and Time API), która została włączona do Javy 8 — przy czym autorem specyfikacji został ten sam Stephen Colebourne.
Joda-Time nie tylko rozwiązała problem dat — zmieniła podejście do projektowania API w Javie. Koncepcja niemutowalnych obiektów wartości, zademonstrowana przez bibliotekę, została przyjęta jako standard w Javie 8 dla wszystkich nowych API. Co więcej, popularność Joda-Time pokazała Oracle, że społeczność nie godzi się na niskiej jakości standardowe biblioteki — po Joda-Time w Javie przeprojektowano API do pracy z plikami (NIO.2), czasem (java.time) i wartościami opcjonalnymi (Optional).
Joda-Time oferuje zestaw możliwości, który przed Javą 8 był niedostępny w standardowej bibliotece. Główne zalety obejmują wsparcie dla ponad 200 stref czasowych z automatycznym przetwarzaniem czasu letniego, bogate API formatowania z wzorcami i lokalizacjami, a także obliczanie okresów między datami w różnych jednostkach — latach, miesiącach, dniach, godzinach. Osobnej uwagi zasługuje system interwałów: Interval (odstęp między dwoma momentami), Period (różnica w jednostkach kalendarzowych) i Duration (dokładny czas trwania w milisekundach).
Klasa DateTimeFormatter w Joda-Time zapewnia bezpieczne wątkowo formatowanie dat — w przeciwieństwie do java.text.SimpleDateFormat. Formatery można tworzyć przez wzorce (np. "yyyy-MM-dd HH:mm:ss") lub przez style (SHORT, MEDIUM, LONG, FULL) dla zlokalizowanego wyjścia. Biblioteka obsługuje również buforowanie formaterów, co przyspiesza działanie w aplikacjach o wysokim obciążeniu. Parsowanie dat z łańcuchów znaków odbywa się z kontrolą ścisłości — można zezwolić lub zabronić niepełnych lub nieprawidłowych dat.
Jedną z mocnych stron Joda-Time jest praca z przedziałami czasowymi. Klasa Interval reprezentuje dokładny odcinek czasu między dwoma momentami (Instant) i obsługuje operacje przecięcia, połączenia i sprawdzania przynależności. Period, w przeciwieństwie do Interval, operuje jednostkami kalendarzowymi — na przykład różnica między 1 marca a 1 kwietnia wynosi dokładnie 1 miesiąc, chociaż pod względem długości może to być 28–31 dni. Duration natomiast reprezentuje dokładny czas trwania w milisekundach bez przypisania do kalendarza i nadaje się do pomiaru czasu wykonania operacji.
import org.joda.time.*;
import org.joda.time.format.*;
DateTime now = new DateTime();
DateTimeFormatter fmt = DateTimeFormat.forPattern("yyyy-MM-dd HH:mm:ss");
String formatted = fmt.print(now);
DateTime nextWeek = now.plusWeeks(1);
Period period = new Period(now, nextWeek);
int days = period.getDays(); // dni = 7
Interval interval = new Interval(now, nextWeek);
boolean contains = interval.contains(now); // true
Pakiet java.time (JSR-310) został opracowany przez tego samego autora — Stephena Colebourne'a — i wchłonął najlepsze pomysły Joda-Time, uwzględniając dziesięć lat doświadczenia eksploatacyjnego. Oba API używają niemutowalnych klas, obsługują strefy czasowe i oferują bogaty zestaw operacji. Jednak java.time jest w pełni zintegrowany ze standardową biblioteką Javy, nie wymaga zewnętrznych zależności i używa bardziej spójnego systemu nazewnictwa: Instant zamiast ReadableInstant, LocalDate zamiast LocalDate (nazwa zachowana), ZonedDateTime zamiast DateTime.
| Cecha | Joda-Time | java.time |
|---|---|---|
| Rok wydania | 2004 | 2014 (Java 8) |
| Status | Utrzymanie (maintenance) | Aktywny rozwój |
| Podstawowa data/czas | DateTime | ZonedDateTime |
| Tylko data | LocalDate | LocalDate |
| Niemutowalność | Tak | Tak |
| Formatowanie | DateTimeFormatter | DateTimeFormatter |
| Różnica dat | Period / Duration | Period / Duration |
java.time naprawia kilka decyzji architektonicznych Joda-Time. Po pierwsze, w java.time wyeliminowano zamieszanie między metodami zwracającymi null a brakującymi wartościami — zamiast tego używane są Optional. Po drugie, dodano wsparcie dla alternatywnych systemów kalendarzowych (japoński, tajski, islamski) poprzez interfejs Chronology. Po trzecie, java.time zawiera klasę Instant do pracy z momentami czasu w UTC bez przypisania do kalendarza — upraszcza to integrację z protokołami sieciowymi i bazami danych. Dla Androida zaleca się używanie java.time począwszy od API 26 (Android 8.0) lub przez desugaring dla starszych wersji.
Migracja z Joda-Time na java.time — to proces, który autorzy biblioteki starali się uczynić maksymalnie bezpiecznym i przewidywalnym. Większość klas ma bezpośrednie odpowiedniki: DateTime → ZonedDateTime, LocalDate → LocalDate (nazwa się pokrywa), LocalTime → LocalTime, DateTimeZone → ZoneId, Period → Period, Duration → Duration. Główne zmiany dotyczą sposobu tworzenia obiektów i nazw metod — na przykład, zamiast `now.toDate()` używa się `Date.from(instant)`, a `DateTime.now()` zastępuje się `ZonedDateTime.now()`.
Migrację zaleca się rozpocząć od izolacji Joda-Time w cienkiej warstwie abstrakcji — umożliwi to wymianę biblioteki etapami, bez wpływania na całą bazę kodu. Pierwszym krokiem zwykle zastępuje się pracę z LocalDate i LocalTime — te klasy mają najmniej różnic między bibliotekami. Drugim etapem migruje się DateTime (na ZonedDateTime) i strefy czasowe. W trzecim etapie zastępuje się Interval, Period i Duration — tutaj należy uważnie sprawdzić zachowanie z uwzględnieniem czasu letniego. Po zakończeniu migracji Joda-Time jest całkowicie usuwana z zależności projektu.
// Joda-Time (przed migracją)
val dateTime = DateTime.now(DateTimeZone.forID("Europe/Moscow"))
val formatted = dateTime.toString("dd.MM.yyyy")
val plusDay = dateTime.plusDays(1)
// java.time (po migracji)
val zonedDateTime = ZonedDateTime.now(ZoneId.of("Europe/Moscow"))
val formatted2 = zonedDateTime.format(
DateTimeFormatter.ofPattern("dd.MM.yyyy")
)
val plusDay2 = zonedDateTime.plusDays(1)
Istnieje kilka narzędzi ułatwiających przejście z Joda-Time na java.time. OpenRewrite (by Moderne) obsługuje automatyczną migrację przez przepis org.openrewrite.java.migrate.JodaTimeToJavaTime — zastępuje on klasy, metody i importy, zachowując logikę programu. Dla projektów Gradle wtyczka de.fayard.refreshVersions również zawiera reguły migracji. Ręcznie migrację najłatwiej przeprowadzić z IDEA: wtyczka Java Time Migration Assistant podświetla wywołania Joda-Time i proponuje odpowiednie zamienniki na java.time, co przyspiesza proces 2–3 razy w porównaniu z ręczną zamianą.
Rozważmy praktyczne przykłady pracy z Joda-Time w aplikacjach Android. Biblioteka jest szczególnie przydatna do zadań formatowania dat według lokalnych standardów, obliczania wieku użytkownika, określania czasu trwania wydarzeń i obliczania interwałów między dwiema datami. Wszystkie przykłady używają niemutowalnych obiektów — każde wywołanie zwraca nową instancję, a oryginalny obiekt pozostaje niezmieniony.
// Oblicz wiek użytkownika
val birthDate = LocalDate.parse("1990-05-15")
val today = LocalDate.now()
val age = Years.yearsBetween(birthDate, today).getYears()
// wiek == 36
// Formatuj z lokalizacją użytkownika
val dateTime = DateTime.now()
val formatter = DateTimeFormat.forStyle("MM").withLocale(Locale.getDefault())
val localized = formatter.print(dateTime)
Interwały w Joda-Time umożliwiają efektywne rozwiązywanie zadań sprawdzania przecięcia przedziałów czasowych, obliczania nakładek i znajdowania najbliższego zdarzenia. Na przykład przy opracowywaniu aplikacji kalendarza można sprawdzić, czy nowe wydarzenie nakłada się na już istniejące. Klasa Interval obsługuje operacje contains, overlaps, gap i abuts — ostatnia pokazuje, czy interwały stykają się granicami. Do pomiaru czasu trwania między dwoma momentami używana jest Duration z dokładnością do milisekund.
// Sprawdź nakładanie się interwałów
Interval meeting1 = new Interval(
new DateTime(2026, 7, 21, 10, 0),
new DateTime(2026, 7, 21, 11, 0)
);
Interval meeting2 = new Interval(
new DateTime(2026, 7, 21, 10, 30),
new DateTime(2026, 7, 21, 11, 30)
);
boolean overlaps = meeting1.overlaps(meeting2); // true
Duration gap = meeting1.gap(meeting2); // null (brak przerwy)
Często zadawane pytania
Joda-Time — biblioteka do pracy z datami i czasem w Javie, stworzona przed wydaniem Javy 8. Naprawiała wady java.util.Date i java.util.Calendar: mutowalność, numerację miesięcy od 0, brak pełnego wsparcia stref czasowych i niebezpieczne wątkowo formatowanie.
Java.time (JSR-310) — następca Joda-Time, opracowany przez tego samego autora. Główne różnice: java.time jest wbudowany w standardową bibliotekę Javy 8+, używa bardziej spójnego systemu nazewnictwa, obsługuje alternatywne systemy kalendarzowe i zawiera klasę Instant do pracy z czasem UTC.
Nie, dla nowych projektów zaleca się używanie java.time. Joda-Time znajduje się w trybie utrzymania od 2014 roku i nie otrzymuje już nowych funkcji. Dla Androida z API 26+ java.time jest dostępny natywnie, a dla starszych wersji używany jest desugaring — mechanizm umożliwiający używanie java.time na urządzeniach z API 19+.
Migracja odbywa się etapami: wyizoluj Joda-Time w abstrakcjach, zastąp najpierw LocalDate i LocalTime, następnie DateTime → ZonedDateTime, a na ostatnim etapie — Period, Duration i Interval. Narzędzie OpenRewrite obsługuje automatyczną migrację przez przepis JodaTimeToJavaTime, co przyspiesza proces 2–3 razy.
DateTime → ZonedDateTime, LocalDate → LocalDate (pokrywa się), LocalTime → LocalTime, LocalDateTime → LocalDateTime, DateTimeZone → ZoneId, Interval → brak bezpośredniego odpowiednika (używana jest kombinacja Instant + Duration), Period → Period, Duration → Duration. Nazwy metod są również zbliżone: plusDays, minusMonths, withZone są dostępne w obu bibliotekach.
Podsumowanie
Opracujemy aplikację mobilną pod klucz
IT Sectr tworzy aplikacje na iOS i Androida dla startupów i firm od 2017 roku. Doradzimy Ci i zaproponujemy najlepsze rozwiązanie.
Przeczytaj również