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-indexedSorunları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 eklenir —
new Date(126, 0, 1)= 1 Ocak 2026Zaman 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.DateveCalendarkullanma. Her zamanjava.timepaketini 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 |
|---|---|---|
LocalDate | Sadece tarih | 2026-02-19 |
LocalTime | Sadece saat | 14:30:00 |
LocalDateTime | Tarih + saat | 2026-02-19T14:30:00 |
ZonedDateTime | Tarih + saat + zaman dilimi | 2026-02-19T14:30:00+03:00[Europe/Istanbul] |
Instant | Zaman 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 formatTarih İş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(); // falseDikkat: 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 gerekirLocalTime
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(); // 30LocalDateTime
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
LocalDateTimeyeterli. Farklı zaman dilimlerindeki kullanıcılar varsa (uluslararası uygulama)ZonedDateTimekullan.
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österimInstant — 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); // PT2H5MPeriod — 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); // 364DateTimeFormatter
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:00Locale 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, ThursdayParse (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:
DateTimeFormatterthread-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 yakalaBu 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()yerineLocalDate.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 kullanmaLocalDate sadece tarih, LocalTime sadece saat, LocalDateTime ikisi birden, ZonedDateTime zaman dilimli
Tüm
java.timesınıfları immutable — method'lar yeni nesne döndürür, orijinali değiştirmezDuration 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ştirmeVeritabanında tarihi UTC sakla, gösterirken kullanıcının zaman dilimine çevir
AI Asistan
Sorularını yanıtlamaya hazır