ZonedDateTime, java.time paketinden, tarih ve saati saat dilimi bilgisiyle (ZoneId) birlikte depolayan değiştirilemez (immutable) bir sınıftır. LocalDateTime’ın aksine, ZonedDateTime zaman çizelgesinde bir anı benzersiz şekilde tanımlar. Oracle Java 17 (2024) spesifikasyonuna göre, sınıf, IANA Time Zone Database’den gelen bölge kuralları aracılığıyla yaz saati uygulaması (DST) geçişlerini doğru şekilde işler.
Önemli Noktalar
ZonedDateTime, java.time paketinin anahtar sınıflarından biridir ve tam saat dilimi bilgisiyle tarih ve saati temsil eder. Üç bileşeni birleştirir: LocalDateTime (tarih ve saat), ZoneId (bölge tanımlayıcı) ve ZoneOffset (UTC’ye göre fark).
LocalDateTime’ın saat dilimi bağlantısı olmadan yalnızca duvar saati (wall-clock time) depolamasının aksine, ZonedDateTime bir anı benzersiz şekilde tanımlar. Farklı saat dilimlerindeki iki özdeş LocalDateTime örneği, zaman içinde farklı anları temsil eder. İki özdeş ZonedDateTime örneği ise aynı anı temsil eder.
Sınıf tamamen değiştirilemez ve iş parçacığı güvenlidir (thread-safe). Tüm aritmetik işlemler yeni bir nesne döndürür. ZonedDateTime, ChronoZonedDateTime arabirimini uygular ve Java’da bölgesel zaman işlemenin gerekli olduğu her yerde kullanılabilir.
Oracle Java 17 spesifikasyonlarına göre, ZonedDateTime, 600’den fazla saat dilimi içeren IANA Time Zone Database’deki herhangi bir bölgeyle çalışmayı destekler.
Temel fark — ZonedDateTime bir saat dilimi içerirken, LocalDateTime içermez. Bu temel fark, her sınıfın kullanım kapsamını belirler.
LocalDateTime yerel etkinlikler için kullanılır: konser saati, ders programı, doğum tarihi. Moskova’da saat 15:00’te bir etkinlik gerçekleşirse, LocalDateTime herhangi bir bağlantı olmadan 15:00’i kaydeder. Sunucuyu New York’a taşırsanız, saat 15:00 olarak kalır — ancak bu farklı bir fiziksel an olacaktır.
ZonedDateTime küresel veriler için kullanılır: sunucu günlükleri, API zaman damgaları, uluslararası toplantılar. Bir toplantı 15:00 MSK olarak planlanmışsa, ZonedDateTime hem saati hem de bölgeyi korur. New York’ta 8:00 EST olarak doğru görüntülenir. Baeldung (2024)’e göre, LocalDateTime ve ZonedDateTime arasında seçim yapmak, tarihlerle çalışırken en yaygın mimari karardır.
Pratik kural: veriler tek bir bölge için depolanıyorsa — LocalDateTime kullanın. Veriler saat dilimi sınırlarını aşıyorsa — ZonedDateTime kullanın. Mutlak bir anı temsil etmeniz gerekiyorsa — Instant kullanın.
java.time’daki saat dilimi ZoneId sınıfı tarafından temsil edilir. ZoneId, “kıta/bölge” biçiminde bir bölge tanımlayıcısıdır, örneğin “Europe/Moscow”, “America/New_York”, “Asia/Tokyo”. ZoneId, statik yöntem of(String zoneId) veya sistem varsayılan saat dilimi aracılığıyla elde edilir.
ZoneId iki türe ayrılır: fixed offset (sabit fark, örn. “+03:00”) ve region-based (bölgesel bölgeler, örn. “Europe/London”). Bölgesel bölgeler, yaz saati geçiş kuralları ve tarihsel değişiklikler içerir. Fixed offset basitçe sabit bir farktır.
Belirli bir anda ZoneId’nin mevcut farkını almak için, ZoneRules döndüren getRules() yöntemi kullanılır. ZoneRules, belirli bir bölge için tüm geçişleri ve farkları içerir. Bu, doğru DST işleme için anahtar mekanizmadır.
Tüm saat dilimleri, JDK ile tzdata dosyaları (IANA Time Zone Database) aracılığıyla gönderilir ve düzenli olarak güncellenir. Android’de tzdata sürümü, Google Play Services aracılığıyla sistem güncellemelerine bağlıdır.
ZonedDateTime birkaç şekilde oluşturulabilir. En basiti, sistem saat diliminde geçerli saati döndüren now() yöntemidir. now(ZoneId) varyantı, belirtilen bir bölgede geçerli saati almanızı sağlar.
of(LocalDateTime, ZoneId) yöntemi, yerel saat ve bölgeden bir ZonedDateTime oluşturur. of(int year, int month, int dayOfMonth, int hour, int minute, int second, int nanoOfSecond, ZoneId zone) varyantı bileşenlerden oluşturur.
LocalDateTime, atZone(ZoneId) yöntemi aracılığıyla ZonedDateTime’a dönüştürülebilir. Instant — Instant.atZone(ZoneId) aracılığıyla. Date — Date.toInstant().atZone(ZoneId) aracılığıyla.
val moscowZone = ZoneId.of("Europe/Moscow")
val nowInMoscow = ZonedDateTime.now(moscowZone)
val fromComponents = ZonedDateTime.of(
2026, 7, 21, 15, 30, 0, 0, moscowZone
)
val fromLocal = LocalDateTime.now().atZone(moscowZone)
val fromInstant = Instant.now().atZone(moscowZone)
Ana dönüştürme yöntemi withZoneSameInstant(ZoneId)’dir. Aynı anı koruyarak ZonedDateTime’ı başka bir saat dilimine dönüştürür. Örneğin, 15:00 MSK → 8:00 EST. withZoneSameLocal(ZoneId) yöntemi, yerel saati koruyarak bölgeyi değiştirir — bu farklı bir an verir.
UTC’ye göre farkı almak için, ZoneOffset döndüren getOffset() yöntemi kullanılır. ZoneOffset, “+HH:mm” veya “-HH:mm” biçiminde sabit bir farkı temsil eden ZoneId’nin bir alt sınıfıdır.
Instant’a dönüştürme, toInstant() yöntemi aracılığıyla yapılır. Instant, saat diliminden bağımsız, zaman içinde mutlak bir andır. Ters dönüştürme Instant.atZone(ZoneId)’dir.
val moscow = ZonedDateTime.of(
2026, 7, 21, 15, 0, 0, 0,
ZoneId.of("Europe/Moscow")
)
val newYork = moscow.withZoneSameInstant(
ZoneId.of("America/New_York")
)
val utcInstant = moscow.toInstant()
val backToMoscow = utcInstant.atZone(ZoneId.of("Europe/Moscow"))
Yaz saati geçişleri iki sorun yaratır: boşluklar (gaps) ve bindirmeler (overlaps). İlkbaharda saatler ileri alındığında bir boşluk oluşur — belirli bir saat mevcut değildir. Sonbaharda saatler geri alındığında bir bindirme oluşur — aynı saat iki kez meydana gelir.
ZonedDateTime bu durumları bir çözümleme (resolve) stratejisi aracılığıyla işler. Bir boşluk sırasında nesne oluştururken, java.time otomatik olarak saati fark miktarı kadar kaydırır. Bindirme sırasında oluştururken, ilk seçenek (geçişten önce) seçilir. Bu davranış, withZoneSameInstant aracılığıyla değiştirilebilir.
Bir saatin DST’de olup olmadığını zone.getRules().isDaylightSavings(instant) aracılığıyla kontrol edebilirsiniz. getOffset() yöntemi, belirli bir an için gerçek farkı gösterir ve getRules().getDaylightSavings(instant) milisaniye cinsinden DST ayar miktarını gösterir.
fun checkDST(zdt: ZonedDateTime) {
val rules = zdt.getZone().getRules()
val instant = zdt.toInstant()
if (rules.isDaylightSavings(instant)) {
val dstAmount = rules.getDaylightSavings(instant)
Log.d("Yaz saati uygulaması", "Yaz saati farkı: $dstAmount")
}
}
ZonedDateTime’ı biçimlendirmek için DateTimeFormatter kullanılır. Standart ISO biçimi tarih, saat ve farkı içerir: “2026-07-21T15:30:00+03:00[Europe/Moscow]”. Önceden tanımlanmış biçimler: ISO_ZONED_DATE_TIME, ISO_OFFSET_DATE_TIME, ISO_INSTANT.
Yerelleştirilmiş çıktı için DateTimeFormatter.ofLocalizedDateTime(FormatStyle) kullanın. FormatStyle SHORT, MEDIUM, LONG, FULL olabilir. LONG, bölge adını (“MSK”) içerir, FULL ise tam adı (“Moscow Standard Time”) içerir.
Önemli: ZonedDateTime ile bir dize ayrıştırırken, biçim bölge veya fark bilgisi içermelidir. Bölge belirtilmemişse, LocalDateTime.parse() ve ardından atZone() kullanın.
val zdt = ZonedDateTime.now(ZoneId.of("Europe/Moscow"))
val iso = zdt.format(DateTimeFormatter.ISO_ZONED_DATE_TIME)
val custom = DateTimeFormatter
.ofPattern("dd.MM.yyyy HH:mm z")
val formatted = zdt.format(custom)
val parsed = ZonedDateTime.parse(
"2026-07-21T15:30:00+03:00",
DateTimeFormatter.ISO_OFFSET_DATE_TIME
)
İlk örnek — kullanıcıya kendi yerel saat diliminde toplantı saatini gösterme. Sunucu ZonedDateTime’ı UTC olarak gönderir, istemci cihazın yerel saat dilimine dönüştürür.
fun displayMeetingTime(
serverUtc: ZonedDateTime
): String {
val deviceZone = ZoneId.systemDefault()
val localTime = serverUtc.withZoneSameInstant(deviceZone)
val formatter = DateTimeFormatter
.ofPattern("dd.MM.yyyy HH:mm z")
return localTime.format(formatter)
}
İkinci örnek — saat dilimini dikkate alarak bir sonraki etkinliğe kadar kalan süreyi hesaplama. Sunucu saati için ZonedDateTime ve farkı hesaplamak için Duration.between() kullanırız.
fun timeUntilEvent(eventTime: ZonedDateTime): String {
val now = ZonedDateTime.now()
val duration = Duration.between(now, eventTime)
val hours = duration.toHours()
val minutes = duration.toMinutes() % 60
return "Remaining $hours h $minutes min"
}
Üçüncü örnek — Retrofit API ile çalışma. Sunucu, bölge içeren bir ISO-8601 dizesi döndürür. ZonedDateTime’a dönüştürmek için özel bir seriçözücü kullanırız.
data class EventResponse(
@JsonAdapter(ZonedDateTimeAdapter::class)
val eventTime: ZonedDateTime
)
class ZonedDateTimeAdapter : JsonAdapter<ZonedDateTime>() {
override fun fromJson(reader: JsonReader): ZonedDateTime? {
return ZonedDateTime.parse(
reader.nextString()
)
}
}
İlk hata — sunucu kodunda ZoneId.systemDefault() kullanmak. Sunucunun saat dilimi istemcinin saat diliminden farklı olabilir ve sunucuda sistem bölgesini kullanmak yanlış hesaplamalara yol açar. Her zaman bölgeyi açıkça belirtin veya UTC’yi referans olarak kullanın.
İkinci hata — süre hesaplarken DST’yi görmezden gelmek. Duration.between() geçişleri doğru şekilde işler, ancak zaman damgalarını manuel olarak çıkarırsanız, yaz saati uygulaması 1 saatlik hataya neden olabilir. Manuel hesaplama yerine ChronoUnit.HOURS.between() kullanın.
Üçüncü hata — withZoneSameInstant ve withZoneSameLocal’ı karıştırmak. İlki, anı koruyarak bölgeyi değiştirir — saat kayar. İkincisi, yerel saati koruyarak bölgeyi değiştirir — an değişir. SonarSource (2024)’e göre, yanlış yöntemi seçmek en yaygın hatalardan biridir.
Dördüncü hata — cihazın saat diliminin her zaman kullanıcının saat dilimiyle aynı olduğunu varsaymak. Kullanıcı seyahat ediyor olabilir ve uygulamanın geçerli bölge yerine “ev” saat diliminde saati göstermesini bekleyebilir. Bu durumda, arayüz aracılığıyla bölge seçimi sağlayın.
Sıkça Sorulan Sorular
ZonedDateTime, bölgesel bir bölge tanımlayıcısı (örn., “Europe/Moscow”) içerir ve DST’yi işler. OffsetDateTime, bölgesel kurallar olmadan yalnızca sabit bir fark (+03:00) depolar. Veritabanı depolama için OffsetDateTime önerilir.
ZonedDateTime.now(ZoneOffset.UTC) veya Instant.now().atZone(ZoneOffset.UTC) kullanın. Her iki seçenek de sıfır farkla geçerli anı döndürür. Basit bir zaman damgası için, bölge bağlantısı olmadan Instant.now() kullanın.
Evet, ancak özel bir bağdaştırıcı gereklidir. Gson, varsayılan olarak ZonedDateTime’ı desteklemez. Moshi, Rfc3339DateJsonAdapter aracılığıyla destekler. Jackson için Kotlinx Serialization veya JavaTimeModule kütüphanesinin kullanılması önerilir.
java.time, saati otomatik olarak fark miktarı kadar ileri kaydırır. Örneğin, saatler 03:00’e ileri alındığında 02:30 mevcut değilse, ZonedDateTime 03:30’da bir nesne oluşturur. ZoneRules.getTransition(instant) aracılığıyla bir boşluk olup olmadığını kontrol edebilirsiniz.
JDBC 4.2, OffsetDateTime’ı destekler ancak ZonedDateTime’ı doğrudan desteklemez. ZonedDateTime, SQL’de karşılığı olmayan bölgesel bir bölge içerir. OffsetDateTime veya Instant depolamanız ve bölgeyi ayrı bir sütunda saklamanız önerilir.
Özet
Anahtar teslim bir mobil uygulama geliştireceğiz
IT Sectr, 2017'den beri girişimler ve işletmeler için iOS ve Android uygulamaları oluşturmaktadır. Size danışmanlık yapacak ve en iyi çözümü önereceğiz.
Ayrıca okuyun