CodeGym /Kursy /JAVA 25 SELF /Przegląd API java.time — różnice względem starych API

Przegląd API java.time — różnice względem starych API

JAVA 25 SELF
Poziom 13 , Lekcja 1
Dostępny

1. Kontekst historyczny: jak pracowano z datami przed Javą 8

W zamierzchłych czasach (przed Javą 8) do pracy z datami i czasem używano klas java.util.Date, java.util.Calendar oraz do formatowania — java.text.SimpleDateFormat. To był ten przypadek, gdy programiści na całym świecie wzdychali i, zgrzytając zębami, pisali coś w rodzaju:

import java.util.Date;

Date now = new Date();
System.out.println(now); // Wypisze coś dziwnego w rodzaju "Wed Jun 05 14:15:22 MSK 2025"

Brzmi prosto, ale w praktyce nie było tak różowo. Oto tylko niektóre „uroki” starego API:

  • Date — obiekt mutowalny (mutable). Można go było przypadkowo zmienić, co często prowadziło do błędów.
  • Miesiące w Date i Calendar zaczynały się od zera (styczeń — to 0, grudzień — 11), a dni — od jedynki.
  • SimpleDateFormat nie był bezpieczny wątkowo: jeśli dwa wątki formatowały datę jednocześnie, można było otrzymać nieoczekiwane wyniki.
  • Mnóstwo metod oznaczono jako @Deprecated (przestarzałe), a IDE nieustannie straszyło żółtymi ostrzeżeniami.
  • Praca ze strefami czasowymi była prawdziwą udręką: łatwo było pomylić czas lokalny z UTC, a o zmianie czasu lepiej w ogóle nie wspominać.

Przykład bólu

import java.util.Date;

Date date = new Date(2025, 5, 1); // rok 2025, miesiąc 5 (czerwiec?), dzień 1
System.out.println(date); // Nie to, czego się spodziewasz!

2. Pojawienie się java.time: nowe podejście

Do 2014 roku stało się jasne: stare API nie było tylko niewygodne — było niebezpieczne. Dlatego w Javie pojawił się nowy pakiet — java.time, implementujący specyfikację JSR‑310. To API było inspirowane popularną biblioteką Joda-Time i szybko stało się standardem de facto.

Najważniejsze pakiety i klasy

  • java.time — podstawowy pakiet, w którym znajdują się wszystkie nowe klasy do pracy z datą i czasem.
  • java.time.format — do formatowania i parsowania daty i czasu.
  • java.time.temporal — do bardziej zaawansowanych operacji czasowych.
  • java.time.zone — do pracy ze strefami czasowymi.

Oto główni bohaterowie nowego API:

Klasa Do czego? Przykład użycia
LocalDate
Tylko data (rok, miesiąc, dzień) Urodziny, bez czasu
LocalTime
Tylko czas (godziny, minuty, sekundy) Czas spotkania, bez daty
LocalDateTime
Data i czas, bez strefy czasowej Zdarzenie lokalne
ZonedDateTime
Data i czas ze strefą czasową Spotkanie w Mińsku wg czasu lokalnego
Instant
Bezwzględny punkt czasu (UTC) Znacznik zdarzenia w logu
Duration
Odstęp czasu (godz., min., sek.) Czas trwania połączenia
Period
Okres (lata, miesiące, dni) Staż pracy, wiek

Przykład: tworzenie daty po nowemu

import java.time.LocalDate;

LocalDate today = LocalDate.now();
System.out.println(today); // Na przykład "2025-06-05"

3. Zalety nowego API

Niemutowalność (immutable)

Wszystkie klasy z java.time są niemutowalne. To znaczy: jeśli utworzysz obiekt LocalDate, nie możesz go zmienić. Każda operacja (na przykład dodanie dnia) zwraca nowy obiekt.

LocalDate today = LocalDate.now();
LocalDate tomorrow = today.plusDays(1);

System.out.println(today);    // 2025-06-05
System.out.println(tomorrow); // 2025-06-06

Jawna praca ze strefami czasowymi

W starym API łatwo było zapomnieć, w jakiej strefie czasowej znajduje się data. W java.time wszystko jest jawne: jeśli potrzebna jest strefa czasowa — użyj ZonedDateTime, jeśli nie — użyj LocalDateTime.

import java.time.ZonedDateTime;
import java.time.ZoneId;

ZonedDateTime MinskTime = ZonedDateTime.now(ZoneId.of("Europe/Minsk"));
System.out.println(MinskTime); // 2025-06-05T14:23:45+03:00[Europe/Minsk]

Wygodne metody do obliczeń i porównywania

LocalDate today = LocalDate.now();
LocalDate nextMonth = today.plusMonths(1);
boolean isAfter = LocalDate.now().plusDays(1).isAfter(today); // true

Formatowanie i parsowanie

Formatowanie i parsowanie — przez DateTimeFormatter (szczegóły — na następnej lekcji):

import java.time.format.DateTimeFormatter;

LocalDate today = LocalDate.now();
DateTimeFormatter formatter = DateTimeFormatter.ofPattern("dd.MM.yyyy");
String formatted = today.format(formatter); // "05.08.2025"

4. Zgodność: jak żyć ze starym kodem

W realnym świecie bardzo często trzeba pracować z bibliotekami lub starszymi systemami, w których używane są Date i Calendar. Na szczęście nowe API jest przyjazne dla dziedzictwa: można konwertować stare typy na nowe i odwrotnie.

Konwersja DateInstant

import java.util.Date;
import java.time.Instant;

// Date → Instant
Date legacyDate = new Date();
Instant instant = legacyDate.toInstant();

// Instant → Date
Date dateBack = Date.from(instant);

Konwersja CalendarZonedDateTime

import java.util.Calendar;
import java.time.ZonedDateTime;
import java.time.ZoneId;
import java.util.Date;

// Calendar → ZonedDateTime
Calendar calendar = Calendar.getInstance();
ZonedDateTime zdt = ZonedDateTime.ofInstant(
        calendar.toInstant(),
        calendar.getTimeZone().toZoneId()
);

// ZonedDateTime → Calendar
Calendar calBack = Calendar.getInstance();
calBack.setTime(Date.from(zdt.toInstant()));

Tabela: mapowanie starych i nowych klas

Stara klasa Nowa klasa Komentarz
java.util.Date
Instant
Czas absolutny
java.util.Calendar
ZonedDateTime
Data i czas ze strefą czasową
java.text.SimpleDateFormat
DateTimeFormatter
Formatowanie/parsowanie dat

5. Praktyka: pierwsze kroki z java.time

Załóżmy, że masz użytkownika z datą urodzenia. Zapiszmy i wypiszmy tę datę:

import java.time.LocalDate;

public class UserProfile {
    private String name;
    private LocalDate birthDate;

    public UserProfile(String name, LocalDate birthDate) {
        this.name = name;
        this.birthDate = birthDate;
    }

    public void printProfile() {
        System.out.println("Imię: " + name);
        System.out.println("Data urodzenia: " + birthDate);
    }
}

public class Main {
    public static void main(String[] args) {
        UserProfile user = new UserProfile("Alisa", LocalDate.of(1998, 12, 25));
        user.printProfile();
    }
}

Wynik:

Imię: Alisa
Data urodzenia: 1998-12-25

6. Porównanie: stare API vs nowe API

Przykład: dodanie tygodnia do daty urodzenia

Stary sposób (Date/Calendar):

import java.util.Calendar;

Calendar cal = Calendar.getInstance();
cal.set(1998, Calendar.DECEMBER, 25);
cal.add(Calendar.WEEK_OF_YEAR, 1);
System.out.println(cal.getTime()); // Żmudne i nieoczywiste

Nowy sposób (java.time):

import java.time.LocalDate;

LocalDate birthDate = LocalDate.of(1998, 12, 25);
LocalDate nextWeek = birthDate.plusWeeks(1);
System.out.println(nextWeek); // 1999-01-01

W nowym API kod jest krótszy, prostszy i bezpieczniejszy.

7. Typowe błędy podczas pracy z java.time

Błąd nr 1: zapomniano, że obiekty są niemutowalne.
Jeśli wywołasz date.plusDays(1); i nie zapiszesz wyniku, pierwotna data pozostanie bez zmian.

Błąd nr 2: pomylenie LocalDate i LocalDateTime.
LocalDate przechowuje tylko datę (rok, miesiąc, dzień), a LocalDateTime — również czas. Nie pomyl, jeśli trzeba obsłużyć godziny i minuty.

Błąd nr 3: używanie starych klas w nowych projektach.
Jeśli to możliwe — zawsze używaj java.time. Stare klasy są potrzebne tylko dla zgodności.

Błąd nr 4: niepoprawna praca ze strefami czasowymi.
Jeśli trzeba przechowywać zdarzenie istotne dla różnych regionów, użyj ZonedDateTime lub przynajmniej Instant. LocalDateTime nie zawiera informacji o strefie czasowej!

Błąd nr 5: próba bezpośredniego porównywania LocalDate i LocalDateTime.
To różne typy danych — nie można ich porównywać bezpośrednio. Najpierw sprowadź do jednego typu.

Komentarze
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION