Data i czas po polsku (java.time)
- Data i czas
- poziom: początkujący
- aktualizacja: 15 sierpnia 2026
Klasy Date, Calendar i SimpleDateFormat to relikt sprzed Javy 8 — są zmienne, niebezpieczne wątkowo, a miesiące numerują od zera (styczeń to 0), przez co o pomyłkę bardzo łatwo. Zamiast nich używaj pakietu java.time: obiekty są niezmienne, a nazwy metod mówią wprost, co robią. Główna pułapka, o której nikt nie uprzedza: DateTimeFormatter domyślnie działa w trybie „wyrozumiałym” i cicho zamienia 31.02.2026 na koniec lutego zamiast zgłosić błąd — dlatego do parsowania danych od użytkownika ustawiamy tu ResolverStyle.STRICT.
import java.time.DayOfWeek;
import java.time.Instant;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.LocalTime;
import java.time.Period;
import java.time.ZoneId;
import java.time.ZonedDateTime;
import java.time.format.DateTimeFormatter;
import java.time.format.DateTimeParseException;
import java.time.format.ResolverStyle;
import java.time.format.TextStyle;
import java.time.temporal.ChronoUnit;
import java.time.temporal.TemporalAdjusters;
import java.util.Locale;
import java.util.Optional;
/**
* Data i czas po polsku (java.time) — skryptyJAVA.pl
* Zapisz jako: DataICzas.java
*/
public class DataICzas {
/** Polski język formatowania. Locale.forLanguageTag zastępuje przestarzały konstruktor new Locale(...). */
private static final Locale PL = Locale.forLanguageTag("pl-PL");
/** Strefa czasowa Polski — sama uwzględnia zmianę czasu letniego i zimowego. */
private static final ZoneId WARSZAWA = ZoneId.of("Europe/Warsaw");
/** Formattery są niezmienne i bezpieczne wątkowo — można je trzymać w stałych. */
private static final DateTimeFormatter KROTKA = DateTimeFormatter.ofPattern("dd.MM.yyyy", PL);
private static final DateTimeFormatter SLOWNIE = DateTimeFormatter.ofPattern("EEEE, d MMMM yyyy", PL);
private static final DateTimeFormatter Z_GODZINA = DateTimeFormatter.ofPattern("dd.MM.yyyy HH:mm:ss", PL);
private static final DateTimeFormatter GODZINA = DateTimeFormatter.ofPattern("HH:mm:ss", PL);
/** Wzorzec do parsowania. Tryb STRICT odrzuca daty typu 31.02 i wymaga „uuuu” zamiast „yyyy”. */
private static final DateTimeFormatter DO_PARSOWANIA =
DateTimeFormatter.ofPattern("dd.MM.uuuu", PL).withResolverStyle(ResolverStyle.STRICT);
/** Formatuje datę w postaci 15.08.2026. */
public static String naKrotko(LocalDate data) { return data.format(KROTKA); }
/** Formatuje datę słownie po polsku, np. „sobota, 15 sierpnia 2026”. */
public static String slownie(LocalDate data) { return data.format(SLOWNIE); }
/** Zwraca nazwę dnia tygodnia w mianowniku, np. „sobota”. */
public static String nazwaDnia(LocalDate data) {
return data.getDayOfWeek().getDisplayName(TextStyle.FULL_STANDALONE, PL);
}
/** Parsuje tekst „dd.MM.rrrr”, np. „24.12.2026”; pusty Optional, gdy to nie jest poprawna data. */
public static Optional<LocalDate> parsuj(String tekst) {
if (tekst == null || tekst.isBlank()) { return Optional.empty(); }
try {
return Optional.of(LocalDate.parse(tekst.trim(), DO_PARSOWANIA));
} catch (DateTimeParseException wyjatek) {
return Optional.empty();
}
}
/** Liczy pełne lata; odrzuca datę urodzenia późniejszą niż dzień obliczenia. */
public static int wiek(LocalDate urodziny, LocalDate naDzien) {
if (urodziny.isAfter(naDzien)) {
throw new IllegalArgumentException("Data urodzenia jest z przyszłości.");
}
return Period.between(urodziny, naDzien).getYears();
}
/** Liczy wszystkie dni między datami; wynik ujemny, gdy druga data jest wcześniejsza. */
public static long roznicaDni(LocalDate od, LocalDate doDnia) { return ChronoUnit.DAYS.between(od, doDnia); }
/** Liczy dni robocze (pon.–pt.): „od” włącznie, „doDnia” wyłącznie; świąt nie odejmuje. */
public static long dniRobocze(LocalDate od, LocalDate doDnia) {
if (od.isAfter(doDnia)) {
return -dniRobocze(doDnia, od);
}
return od.datesUntil(doDnia)
.filter(dzien -> dzien.getDayOfWeek() != DayOfWeek.SATURDAY)
.filter(dzien -> dzien.getDayOfWeek() != DayOfWeek.SUNDAY)
.count();
}
/** Zwraca pierwszy dzień miesiąca, w którym leży podana data. */
public static LocalDate pierwszyDzienMiesiaca(LocalDate data) {
return data.with(TemporalAdjusters.firstDayOfMonth());
}
/** Zwraca ostatni dzień miesiąca — sam pilnuje 28, 29, 30 i 31 dni. */
public static LocalDate ostatniDzienMiesiaca(LocalDate data) {
return data.with(TemporalAdjusters.lastDayOfMonth());
}
// ── PRZYKŁADY ───────────────────────────────────────────
public static void main(String[] args) {
LocalDate dzisiaj = LocalDate.now(WARSZAWA);
LocalTime teraz = LocalTime.now(WARSZAWA);
LocalDateTime chwila = LocalDateTime.now(WARSZAWA);
System.out.println("Dzisiaj: " + naKrotko(dzisiaj) + " (" + nazwaDnia(dzisiaj) + ")");
System.out.println("Słownie: " + slownie(dzisiaj));
System.out.println("Godzina: " + teraz.format(GODZINA));
System.out.println("Data i czas: " + chwila.format(Z_GODZINA));
// Konkretna data — miesiąc liczony normalnie, styczeń to 1 (a nie 0 jak w starym Date)
LocalDate wigilia = LocalDate.of(2026, 12, 24);
System.out.println("Wigilia: " + slownie(wigilia));
// Parsowanie tekstu — zawsze zakładaj, że użytkownik wpisze bzdurę
System.out.println("Parsowanie: " + parsuj("01.03.2026").map(DataICzas::slownie).orElse("—"));
System.out.println("31.02.2026: " + parsuj("31.02.2026").map(DataICzas::naKrotko).orElse("błędna data"));
// Wiek i różnice
LocalDate urodziny = LocalDate.of(1990, 5, 17);
System.out.println("Wiek: " + wiek(urodziny, dzisiaj) + " lat");
System.out.println("Dni do wigilii: " + roznicaDni(dzisiaj, wigilia));
System.out.println("Dni robocze: " + dniRobocze(dzisiaj, wigilia));
// Przesuwanie dat — obiekty są niezmienne, więc metody zwracają NOWĄ datę
System.out.println("Za 30 dni: " + naKrotko(dzisiaj.plusDays(30)));
System.out.println("3 mies. wstecz: " + naKrotko(dzisiaj.minusMonths(3)));
System.out.println("Początek mies.: " + naKrotko(pierwszyDzienMiesiaca(dzisiaj)));
System.out.println("Koniec mies.: " + naKrotko(ostatniDzienMiesiaca(dzisiaj)));
System.out.println("Koniec lutego: " + naKrotko(ostatniDzienMiesiaca(LocalDate.of(2028, 2, 1))));
// Strefy czasowe i znacznik czasu
Instant znacznik = Instant.now();
ZonedDateTime uNas = znacznik.atZone(WARSZAWA);
ZonedDateTime wNowymJorku = znacznik.atZone(ZoneId.of("America/New_York"));
System.out.println("Warszawa: " + uNas.format(Z_GODZINA) + " " + uNas.getOffset());
System.out.println("Nowy Jork: " + wNowymJorku.format(Z_GODZINA) + " " + wNowymJorku.getOffset());
System.out.println("Instant (UTC): " + znacznik.truncatedTo(ChronoUnit.SECONDS));
}
}
Jak użyć tego skryptu
- Skopiuj kod i zapisz go w pliku o nazwie
DataICzas.java— nazwa pliku musi być taka sama jak nazwa klasy publicznej. - Otwórz terminal w katalogu z plikiem i wpisz
java DataICzas.java— od Javy 11 pojedynczy plik uruchomisz bez osobnej kompilacji. Zobaczysz wszystkie przykłady naraz. - We własnym projekcie wywołuj gotowe metody:
DataICzas.naKrotko(data),DataICzas.slownie(data),DataICzas.parsuj(tekst),DataICzas.wiek(urodziny, dzisiaj)czyDataICzas.dniRobocze(od, doDnia)(uwaga:doto słowo kluczowe Javy, więc nie użyjesz go jako nazwy zmiennej). - Zmień format wyświetlania, podmieniając wzorzec w stałej
KROTKAlubSLOWNIE— np."yyyy-MM-dd"dla formatu ISO albo"d MMMM yyyy"dla samej daty słownie.
Warto wiedzieć
- Nie używaj już
Date,CalendaraniSimpleDateFormat. Ta ostatnia klasa nie jest bezpieczna wątkowo — współdzielona między wątkami potrafi zwracać błędne daty.DateTimeFormatterjest niezmienny, więc spokojnie trzymaj go w stałejstatic final. - Domyślny tryb parsowania (
SMART) cicho poprawia niemożliwe daty:31.02.2026stanie się28.02.2026. Dlatego wzorzecDO_PARSOWANIAużywaResolverStyle.STRICT— a w tym trybie rok trzeba zapisać jakouuuu, boyyyyoznacza „rok ery”. Wzorzecdd.MMwymaga dwóch cyfr dnia i miesiąca, więc1.3.2026jest odrzucane świadomie. - Obiekty
java.timesą niezmienne —data.plusDays(7)nie zmienia zmiennejdata, tylko zwraca nową datę. Jeśli zapomnisz przypisać wyniku, wygląda to tak, jakby metoda nic nie robiła. Każde przesunięcie daty musisz więc czemuś przypisać:data = data.plusDays(7);. - Metoda
dniRoboczepomija wyłącznie soboty i niedziele. Boże Ciało czy 11 listopada policzy jako dni pracujące, więc jeśli wyliczasz termin umowny albo urlop, odejmij dni ustawowo wolne z własnej listy świąt.
Podobne skrypty
Stoper i precyzyjny pomiar czasu
Stoper na System.nanoTime z pauzą i wznowieniem, pomiar operacji razem z jej wynikiem oraz formatowanie Duration w jednostce dobranej do skali — od nanosekund po minuty.
średni Zobacz kod → Data i czasDni robocze, harmonogram i Wielkanoc
Kalendarz polskich dni roboczych: data Wielkanocy z algorytmu Meeusa, święta stałe i ruchome, liczenie dni w przedziale, wyznaczanie terminów oraz gotowe harmonogramy.
zaawansowany Zobacz kod → Data i czasStrefy czasowe i zmiana czasu
Przeliczanie chwili między strefami na ZonedDateTime, wykrywanie regułami ZoneRules godziny, której przy wiosennej zmianie czasu nie ma, i tej padającej jesienią dwa razy, oraz pułapka skrótów CET i EST.
średni Zobacz kod →