Przejdź do treści

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.

JAVA DataICzas.java 133 linii Pobierz .java
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

  1. Skopiuj kod i zapisz go w pliku o nazwie DataICzas.java — nazwa pliku musi być taka sama jak nazwa klasy publicznej.
  2. 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.
  3. We własnym projekcie wywołuj gotowe metody: DataICzas.naKrotko(data), DataICzas.slownie(data), DataICzas.parsuj(tekst), DataICzas.wiek(urodziny, dzisiaj) czy DataICzas.dniRobocze(od, doDnia) (uwaga: do to słowo kluczowe Javy, więc nie użyjesz go jako nazwy zmiennej).
  4. Zmień format wyświetlania, podmieniając wzorzec w stałej KROTKA lub SLOWNIE — np. "yyyy-MM-dd" dla formatu ISO albo "d MMMM yyyy" dla samej daty słownie.

Warto wiedzieć

  • Nie używaj już Date, Calendar ani SimpleDateFormat. Ta ostatnia klasa nie jest bezpieczna wątkowo — współdzielona między wątkami potrafi zwracać błędne daty. DateTimeFormatter jest niezmienny, więc spokojnie trzymaj go w stałej static final.
  • Domyślny tryb parsowania (SMART) cicho poprawia niemożliwe daty: 31.02.2026 stanie się 28.02.2026. Dlatego wzorzec DO_PARSOWANIA używa ResolverStyle.STRICT — a w tym trybie rok trzeba zapisać jako uuuu, bo yyyy oznacza „rok ery”. Wzorzec dd.MM wymaga dwóch cyfr dnia i miesiąca, więc 1.3.2026 jest odrzucane świadomie.
  • Obiekty java.time są niezmienne — data.plusDays(7) nie zmienia zmiennej data, 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 dniRobocze pomija 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.
#java.time #localdate #formatowanie #parsowanie #locale #dni robocze

Podobne skrypty