← Kursa Dön
📄 Text · 12 min

java.time API

Giriş

Tarih ve saat işlemleri her programlama dilinde kabus olmuştur. Java da bu kabustan nasibini almış — java.util.Date ve Calendar sınıfları yıllarca geliştiricileri çıldırtmıştır. Mutable nesneler, ayların 0'dan başlaması, thread-safety eksikliği... Java 8 ile gelen java.time paketi bu karanlık çağı bitirdi.

Bu derste modern Java'nın tarih/saat API'sını öğreneceğiz. LocalDate'ten ZonedDateTime'a, Duration'dan DateTimeFormatter'a kadar her şeyi konuşacağız.

Eski API Neden Kötüydü?

Önce neden yeni bir API gerektiğini anlayalım:

// java.util.Date sorunları
Date simdi = new Date();
System.out.println(simdi); // Thu Feb 19 21:00:00 TRT 2026 — okunamaz format

// Ay 0'dan başlar! Ocak = 0, Şubat = 1 ...
Date tarih = new Date(2026 - 1900, 1, 19); // 1900 çıkart! Ay 0-indexed!

// Mutable — tarih değiştirilebilir
tarih.setYear(2030 - 1900); // Tahmin et ne olur

// Calendar da farklı değil
Calendar cal = Calendar.getInstance();
cal.set(Calendar.MONTH, 1); // Şubat mı? Evet, çünkü 0-indexed

Sorunların listesi:

  • Mutable — Nesne oluşturulduktan sonra değiştirilebilir (thread-unsafe)

  • Ay 0'dan başlar — Ocak = 0, herkesi şaşırtır

  • Yıla 1900 eklenirnew Date(126, 0, 1) = 1 Ocak 2026

  • Zaman dilimi karmaşası — Date zaman dilimi taşır ama göstermez

  • SimpleDateFormat thread-safe değil — Birden fazla thread'de kullanırsan hatalı sonuçlar

⚠️ Kural: Yeni projede java.util.Date ve Calendar kullanma. Her zaman java.time paketini tercih et. Eski kodla çalışıyorsan dönüşüm method'larını kullan.

java.time Genel Bakış

Analoji: Saat Mağazası

java.time paketini bir saat mağazası gibi düşün. Farklı ihtiyaçlar için farklı saat tipleri var:

  • Duvar saati (LocalTime) — sadece saat gösterir, tarih yok

  • Takvim (LocalDate) — sadece tarih gösterir, saat yok

  • Akıllı saat (LocalDateTime) — hem tarih hem saat

  • Dünya saati (ZonedDateTime) — tarih + saat + zaman dilimi

Hangisini kullanacağın, neye ihtiyacın olduğuna bağlı.

SınıfİçerikÖrnek
LocalDateSadece tarih2026-02-19
LocalTimeSadece saat14:30:00
LocalDateTimeTarih + saat2026-02-19T14:30:00
ZonedDateTimeTarih + saat + zaman dilimi2026-02-19T14:30:00+03:00[Europe/Istanbul]
InstantZaman damgası (epoch)1771855800 (saniye cinsinden)

LocalDate

Sadece tarih — yıl, ay, gün. Saat bilgisi yok. Doğum tarihi, son kullanma tarihi gibi durumlar için ideal.

import java.time.LocalDate;

// Bugünün tarihi
LocalDate bugun = LocalDate.now();
System.out.println(bugun); // 2026-02-19

// Belirli tarih oluşturma
LocalDate tarih = LocalDate.of(2026, 2, 19);  // Ay 1'den başlar!
LocalDate tarih2 = LocalDate.of(2026, Month.FEBRUARY, 19); // Enum ile daha okunabilir

// String'den parse
LocalDate parsed = LocalDate.parse("2026-02-19"); // ISO format

Tarih İşlemleri

LocalDate bugun = LocalDate.now();

// Ekleme
LocalDate yarin = bugun.plusDays(1);
LocalDate gelecekHafta = bugun.plusWeeks(1);
LocalDate gelecekAy = bugun.plusMonths(1);
LocalDate gelecekYil = bugun.plusYears(1);

// Çıkarma
LocalDate dun = bugun.minusDays(1);

// Bilgi alma
int yil = bugun.getYear();         // 2026
Month ay = bugun.getMonth();        // FEBRUARY
int gun = bugun.getDayOfMonth();    // 19
DayOfWeek gunAdi = bugun.getDayOfWeek(); // THURSDAY

// Karşılaştırma
boolean onceMi = bugun.isBefore(yarin);  // true
boolean sonraMi = bugun.isAfter(dun);    // true
boolean artikYil = bugun.isLeapYear();    // false

Dikkat: Tüm bu method'lar yeni nesne döndürür. Orijinal nesne değişmez (immutable).

LocalDate tarih = LocalDate.of(2026, 1, 1);
tarih.plusDays(5); // Bu satır tarih'i DEĞİŞTİRMEZ!
LocalDate yeniTarih = tarih.plusDays(5); // Yeni nesne alman gerekir

LocalTime

Sadece saat — saat, dakika, saniye, nanosaniye. Tarih bilgisi yok.

import java.time.LocalTime;

LocalTime simdi = LocalTime.now();
System.out.println(simdi); // 14:30:15.123456789

LocalTime saat = LocalTime.of(14, 30);        // 14:30
LocalTime saat2 = LocalTime.of(14, 30, 45);   // 14:30:45

// İşlemler
LocalTime birSaatSonra = simdi.plusHours(1);
LocalTime onDakikaOnce = simdi.minusMinutes(10);

// Bilgi
int saatDegeri = simdi.getHour();   // 14
int dakika = simdi.getMinute();      // 30

LocalDateTime

Tarih ve saati birlikte tutar. Zaman dilimi bilgisi yok.

import java.time.LocalDateTime;

LocalDateTime simdi = LocalDateTime.now();
System.out.println(simdi); // 2026-02-19T14:30:15.123

// Oluşturma yolları
LocalDateTime dt = LocalDateTime.of(2026, 2, 19, 14, 30);
LocalDateTime dt2 = LocalDateTime.of(
    LocalDate.of(2026, 2, 19),
    LocalTime.of(14, 30)
);

// İşlemler — LocalDate ve LocalTime'ın tüm method'ları var
LocalDateTime yarin = simdi.plusDays(1).withHour(9).withMinute(0);

withHour(), withMinute() gibi method'lar belirli alanı değiştirip yeni nesne döndürür.

// "Bu ayın son günü saat 23:59" gibi ifadeler
LocalDateTime aySonu = LocalDateTime.now()
    .with(TemporalAdjusters.lastDayOfMonth())
    .withHour(23)
    .withMinute(59)
    .withSecond(59);

💡 Ne zaman LocalDateTime, ne zaman ZonedDateTime? Eğer tüm kullanıcılar aynı zaman dilimindeyse veya zaman dilimi önemsizse LocalDateTime yeterli. Farklı zaman dilimlerindeki kullanıcılar varsa (uluslararası uygulama) ZonedDateTime kullan.

ZonedDateTime

Tarih + saat + zaman dilimi. Uluslararası uygulamalarda şart.

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

// Şu anki zaman, sistem zaman diliminde
ZonedDateTime simdi = ZonedDateTime.now();
System.out.println(simdi);
// 2026-02-19T14:30:00+03:00[Europe/Istanbul]

// Belirli zaman diliminde
ZonedDateTime istanbul = ZonedDateTime.now(ZoneId.of("Europe/Istanbul"));
ZonedDateTime londra = ZonedDateTime.now(ZoneId.of("Europe/London"));
ZonedDateTime tokyo = ZonedDateTime.now(ZoneId.of("Asia/Tokyo"));

System.out.println("İstanbul: " + istanbul.toLocalTime());
System.out.println("Londra:   " + londra.toLocalTime());
System.out.println("Tokyo:    " + tokyo.toLocalTime());

Zaman Dilimi Dönüşümü

ZonedDateTime istanbul = ZonedDateTime.now(ZoneId.of("Europe/Istanbul"));

// Aynı anı farklı zaman diliminde göster
ZonedDateTime newYork = istanbul.withZoneSameInstant(ZoneId.of("America/New_York"));

System.out.println("İstanbul: " + istanbul); // 21:30+03:00
System.out.println("New York: " + newYork);  // 13:30-05:00
// Aynı an, farklı gösterim

Instant — Makine Zamanı

Instant epoch'tan (1 Ocak 1970 UTC) geçen süreyi temsil eder. Zaman damgası (timestamp) olarak kullanılır.

Instant simdi = Instant.now();
System.out.println(simdi); // 2026-02-19T18:30:00.123Z (UTC)

// Epoch saniye
long epoch = simdi.getEpochSecond();

// Instant → ZonedDateTime
ZonedDateTime zdt = simdi.atZone(ZoneId.of("Europe/Istanbul"));

// Database'de timestamp saklamak için ideal

⚠️ Dikkat: Veritabanında tarih saklarken UTC olarak sakla, gösterirken kullanıcının zaman dilimine çevir. Bu altın kuraldır.

Duration ve Period

İki zaman noktası arasındaki farkı temsil ederler.

Duration — Saat Bazlı Fark

import java.time.Duration;

LocalTime baslangic = LocalTime.of(9, 0);
LocalTime bitis = LocalTime.of(17, 30);
Duration sure = Duration.between(baslangic, bitis);

System.out.println(sure);              // PT8H30M (8 saat 30 dakika)
System.out.println(sure.toHours());    // 8
System.out.println(sure.toMinutes());  // 510

// Manuel oluşturma
Duration ikiSaat = Duration.ofHours(2);
Duration besDakika = Duration.ofMinutes(5);
Duration toplam = ikiSaat.plus(besDakika); // PT2H5M

Period — Gün Bazlı Fark

import java.time.Period;

LocalDate dogumTarihi = LocalDate.of(2000, 5, 15);
LocalDate bugun = LocalDate.now();
Period yas = Period.between(dogumTarihi, bugun);

System.out.println(yas);            // P25Y9M4D
System.out.println(yas.getYears()); // 25
System.out.println(yas.getMonths());// 9
System.out.println(yas.getDays());  // 4

// Manuel oluşturma
Period birYil = Period.ofYears(1);
Period ucAy = Period.ofMonths(3);

Duration vs Period:

  • Duration — saat, dakika, saniye (kesin süre)

  • Period — yıl, ay, gün (takvim bazlı süre)

// ChronoUnit ile kolay hesaplama
long gunFarki = ChronoUnit.DAYS.between(
    LocalDate.of(2026, 1, 1),
    LocalDate.of(2026, 12, 31)
);
System.out.println(gunFarki); // 364

DateTimeFormatter

Tarihleri istediğin formatta göstermek ve parse etmek için kullanılır.

Hazır Formatlayıcılar

LocalDateTime simdi = LocalDateTime.now();

System.out.println(simdi.format(DateTimeFormatter.ISO_LOCAL_DATE));      // 2026-02-19
System.out.println(simdi.format(DateTimeFormatter.ISO_LOCAL_DATE_TIME)); // 2026-02-19T14:30:00

Özel Format

DateTimeFormatter turkFormat = DateTimeFormatter.ofPattern("dd.MM.yyyy");
DateTimeFormatter detayli = DateTimeFormatter.ofPattern("dd MMMM yyyy, EEEE HH:mm");
DateTimeFormatter saatli = DateTimeFormatter.ofPattern("dd/MM/yyyy HH:mm:ss");

LocalDateTime simdi = LocalDateTime.now();

System.out.println(simdi.format(turkFormat)); // 19.02.2026
System.out.println(simdi.format(detayli));    // 19 Şubat 2026, Perşembe 14:30
System.out.println(simdi.format(saatli));     // 19/02/2026 14:30:00

Locale ile Formatlama

DateTimeFormatter trFormat = DateTimeFormatter
    .ofPattern("dd MMMM yyyy, EEEE")
    .withLocale(new Locale("tr", "TR"));

DateTimeFormatter enFormat = DateTimeFormatter
    .ofPattern("dd MMMM yyyy, EEEE")
    .withLocale(Locale.ENGLISH);

LocalDate tarih = LocalDate.of(2026, 2, 19);
System.out.println(tarih.format(trFormat)); // 19 Şubat 2026, Perşembe
System.out.println(tarih.format(enFormat)); // 19 February 2026, Thursday

Parse (String → Tarih)

DateTimeFormatter format = DateTimeFormatter.ofPattern("dd.MM.yyyy");

LocalDate tarih = LocalDate.parse("19.02.2026", format);
System.out.println(tarih); // 2026-02-19

// Hatalı format → DateTimeParseException
try {
    LocalDate hatali = LocalDate.parse("2026-19-02", format); // Yanlış sıra
} catch (DateTimeParseException e) {
    System.out.println("Parse hatası: " + e.getMessage());
}

💡 İpucu: DateTimeFormatter thread-safe'tir. Bir kere oluştur, static field olarak tut, her yerde kullan. SimpleDateFormat'tan farkı bu — eski API'de her thread için yeni instance gerekiyordu.

Eski API ile Dönüşüm

Eski kodlarla çalışmak zorunda kalabilirsin. Dönüşüm method'ları:

// Date → Instant → LocalDateTime
Date eskiTarih = new Date();
Instant instant = eskiTarih.toInstant();
LocalDateTime yeni = LocalDateTime.ofInstant(instant, ZoneId.systemDefault());

// LocalDateTime → Date
LocalDateTime ldt = LocalDateTime.now();
Date eski = Date.from(ldt.atZone(ZoneId.systemDefault()).toInstant());

// Calendar → ZonedDateTime
Calendar cal = Calendar.getInstance();
ZonedDateTime zdt = cal.toInstant().atZone(cal.getTimeZone().toZoneId());

Pratik Örnekler

Yaş Hesaplama

public int yasHesapla(LocalDate dogumTarihi) {
    return Period.between(dogumTarihi, LocalDate.now()).getYears();
}

int yas = yasHesapla(LocalDate.of(2000, 5, 15)); // 25

İş Günü Hesaplama

public LocalDate isGunuEkle(LocalDate tarih, int gun) {
    int eklenen = 0;
    LocalDate sonuc = tarih;
    while (eklenen < gun) {
        sonuc = sonuc.plusDays(1);
        if (sonuc.getDayOfWeek() != DayOfWeek.SATURDAY
            && sonuc.getDayOfWeek() != DayOfWeek.SUNDAY) {
            eklenen++;
        }
    }
    return sonuc;
}

Tarih Aralığı Kontrolü

public boolean araliktaMi(LocalDate tarih, LocalDate baslangic, LocalDate bitis) {
    return !tarih.isBefore(baslangic) && !tarih.isAfter(bitis);
}

Countdown (Geri Sayım)

public String geriSayim(LocalDate hedefTarih) {
    LocalDate bugun = LocalDate.now();
    long kalanGun = ChronoUnit.DAYS.between(bugun, hedefTarih);

    if (kalanGun < 0) return "Tarih geçmiş!";
    if (kalanGun == 0) return "Bugün!";

    Period kalan = Period.between(bugun, hedefTarih);
    return "%d yıl, %d ay, %d gün kaldı".formatted(
        kalan.getYears(), kalan.getMonths(), kalan.getDays()
    );
}

Yaygın Hatalar

1. Immutability'yi Unutmak

LocalDate tarih = LocalDate.of(2026, 1, 1);
tarih.plusDays(30); // ❌ Bu satır hiçbir şey yapmaz! Sonuç atanmadı

LocalDate yeniTarih = tarih.plusDays(30); // ✅ Yeni nesneyi yakala

Bu hata çok yaygın. String gibi düşün — str.toUpperCase() nasıl orijinal String'i değiştirmezse, tarih.plusDays(30) da orijinal tarihi değiştirmez.

2. Yanlış Sınıf Seçimi

// ❌ Sadece tarih gereken yerde LocalDateTime kullanma
LocalDateTime dogumTarihi = LocalDateTime.of(2000, 5, 15, 0, 0); // Saat niye var?

// ✅ Sadece tarih yeterli
LocalDate dogumTarihi = LocalDate.of(2000, 5, 15);

// ❌ Zaman dilimi gereken yerde LocalDateTime kullanma
LocalDateTime ucusSaati = LocalDateTime.of(2026, 6, 15, 14, 30); // Hangi zaman dilimi?

// ✅ Zaman dilimini belirt
ZonedDateTime ucusSaati = ZonedDateTime.of(
    2026, 6, 15, 14, 30, 0, 0,
    ZoneId.of("Europe/Istanbul")
);

3. Month Enum vs int Karışıklığı

// İkisi de geçerli, ama int ile dikkatli ol
LocalDate.of(2026, 2, 19);           // int — doğru
LocalDate.of(2026, Month.FEBRUARY, 19); // enum — daha okunabilir

// Eski alışkanlıkla 0-indexed düşünme!
// java.time'da Ocak = 1 (Date'teki gibi 0 değil!)

Clock — Test Edilebilir Zaman

Production kodunda LocalDate.now() çağrısı testte sorun yaratır. Çünkü her çalışmada farklı tarih döner. Clock sınıfı bu sorunu çözer:

public class AbonelikService {
    private final Clock clock;

    // Production'da: Clock.systemDefaultZone()
    // Testte: Clock.fixed(...)
    public AbonelikService(Clock clock) {
        this.clock = clock;
    }

    public boolean abonelikAktifMi(LocalDate bitisTarihi) {
        LocalDate bugun = LocalDate.now(clock);
        return !bugun.isAfter(bitisTarihi);
    }
}
// Test
Clock sabitClock = Clock.fixed(
    LocalDate.of(2026, 6, 15)
        .atStartOfDay(ZoneId.systemDefault())
        .toInstant(),
    ZoneId.systemDefault()
);

AbonelikService service = new AbonelikService(sabitClock);

// Her zaman 15 Haziran 2026 gibi davranır
assertTrue(service.abonelikAktifMi(LocalDate.of(2026, 12, 31)));  // true
assertFalse(service.abonelikAktifMi(LocalDate.of(2026, 1, 1)));   // false

💡 İpucu: LocalDate.now() yerine LocalDate.now(clock) kullanmayı alışkanlık edin. Test yazarken hayat kurtarır.

Yaz Saati (DST) Tuzakları

Yaz saati uygulaması tarih/saat hesaplamalarında beklenmedik sonuçlara yol açabilir:

// Türkiye 2016'da kalıcı yaz saatine geçti ama örnek olarak düşünelim
// Amerika'da hâlâ DST var

ZoneId newYork = ZoneId.of("America/New_York");

// 10 Mart 2026 — DST başlangıcı (saat 02:00 → 03:00 atlar)
ZonedDateTime oncesi = ZonedDateTime.of(
    2026, 3, 8, 1, 30, 0, 0, newYork
);
ZonedDateTime birSaatSonra = oncesi.plusHours(1);

System.out.println(oncesi);       // 01:30-05:00
System.out.println(birSaatSonra); // 03:30-04:00 (02:30 yok!)
// Duration ve Period farkı DST'de ortaya çıkar
ZonedDateTime gun1 = ZonedDateTime.of(2026, 3, 7, 12, 0, 0, 0, newYork);

// Period: takvim günü ekler (24 saat olmayabilir!)
ZonedDateTime gun2Period = gun1.plus(Period.ofDays(1));
// → 8 Mart 12:00 (ama arada sadece 23 saat geçmiş, DST yüzünden)

// Duration: tam 24 saat ekler
ZonedDateTime gun2Duration = gun1.plus(Duration.ofHours(24));
// → 8 Mart 13:00 (tam 24 saat, ama saat farklı!)

Bu yüzden "bir gün sonra" demek istiyorsan plusDays() veya Period kullan. "24 saat sonra" demek istiyorsan Duration kullan. İkisi farklı şeyler.

Veritabanı ile Çalışma

JDBC ile java.time

// INSERT
PreparedStatement ps = conn.prepareStatement(
    "INSERT INTO etkinlikler (isim, tarih, saat) VALUES (?, ?, ?)"
);
ps.setString(1, "Konferans");
ps.setObject(2, LocalDate.of(2026, 6, 15));      // setObject ile
ps.setObject(3, LocalTime.of(10, 0));

// SELECT
ResultSet rs = stmt.executeQuery("SELECT tarih, saat FROM etkinlikler");
while (rs.next()) {
    LocalDate tarih = rs.getObject("tarih", LocalDate.class);
    LocalTime saat = rs.getObject("saat", LocalTime.class);
}

JPA/Hibernate ile

@Entity
public class Etkinlik {
    @Id
    private Long id;
    private String isim;
    private LocalDate tarih;        // DATE sütununa map'lenir
    private LocalTime saat;          // TIME sütununa
    private LocalDateTime olusturma; // TIMESTAMP sütununa

    // Hibernate otomatik dönüşüm yapar (Java 8+ destekli)
}

Epoch ve Unix Timestamp

Bazen dış sistemlerle epoch (Unix timestamp) formatında iletişim kurarsın:

// Şu anki epoch saniye
long epoch = Instant.now().getEpochSecond();
System.out.println(epoch); // 1771527000 gibi bir sayı

// Epoch → Instant → LocalDateTime
Instant instant = Instant.ofEpochSecond(1771527000L);
LocalDateTime ldt = LocalDateTime.ofInstant(instant, ZoneId.of("Europe/Istanbul"));

// Epoch milisaniye (JavaScript'in Date.now() gibi)
long epochMilli = Instant.now().toEpochMilli();
Instant fromMilli = Instant.ofEpochMilli(epochMilli);
// API'den gelen epoch'u tarihe çevirme
public LocalDateTime epochToLocal(long epochSaniye, String zonId) {
    return Instant.ofEpochSecond(epochSaniye)
        .atZone(ZoneId.of(zonId))
        .toLocalDateTime();
}

Tarih Aralıkları ve İş Mantığı

// İki tarih arası tüm günleri listeleme (Java 9+)
LocalDate baslangic = LocalDate.of(2026, 1, 1);
LocalDate bitis = LocalDate.of(2026, 1, 10);

List<LocalDate> gunler = baslangic.datesUntil(bitis)
    .toList();
// [2026-01-01, 2026-01-02, ..., 2026-01-09] (bitis dahil değil)

// Bitis dahil olsun:
List<LocalDate> gunlerDahil = baslangic.datesUntil(bitis.plusDays(1))
    .toList();
// Hafta içi günleri filtrele
List<LocalDate> haftaIci = baslangic.datesUntil(bitis)
    .filter(d -> d.getDayOfWeek().getValue() <= 5) // Pzt-Cuma
    .toList();
// Ayın kaç gün çektiğini bul
YearMonth subat = YearMonth.of(2026, 2);
System.out.println(subat.lengthOfMonth()); // 28

YearMonth subat2028 = YearMonth.of(2028, 2);
System.out.println(subat2028.lengthOfMonth()); // 29 (artık yıl)

TemporalAdjusters — Akıllı Tarih Ayarları

TemporalAdjusters sık kullanılan tarih ayarlamaları sunar:

LocalDate bugun = LocalDate.now();

// Ayın ilk/son günü
LocalDate ayBasi = bugun.with(TemporalAdjusters.firstDayOfMonth());
LocalDate aySonu = bugun.with(TemporalAdjusters.lastDayOfMonth());

// Gelecek Pazartesi
LocalDate gelecekPzt = bugun.with(TemporalAdjusters.next(DayOfWeek.MONDAY));

// Bu ayın ilk Cuma'sı
LocalDate ilkCuma = bugun.with(
    TemporalAdjusters.firstInMonth(DayOfWeek.FRIDAY)
);

// Yılın ilk günü
LocalDate yilBasi = bugun.with(TemporalAdjusters.firstDayOfYear());

Özet

  • Eski API (Date, Calendar) mutable, kafa karıştırıcı ve thread-unsafe — yeni projede kullanma

  • LocalDate sadece tarih, LocalTime sadece saat, LocalDateTime ikisi birden, ZonedDateTime zaman dilimli

  • Tüm java.time sınıfları immutable — method'lar yeni nesne döndürür, orijinali değiştirmez

  • Duration saat bazlı süre, Period gün/ay/yıl bazlı süre, ChronoUnit hızlı fark hesabı

  • DateTimeFormatter thread-safe format/parse sınıfı — ofPattern() ile özel format, withLocale() ile yerelleştirme

  • Veritabanında tarihi UTC sakla, gösterirken kullanıcının zaman dilimine çevir