ZonedDateTime — nedir, saat dilimleri ve zamanla çalışma

Yazar: IT Sectr Yayınlanma: 2026-07-13 Okuma süresi: 10 dk

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, tarih, saat ve saat dilimini (ZoneId) tek bir nesnede birleştiren değiştirilemez bir sınıftır.
  • LocalDateTime’ın aksine, ZonedDateTime zaman çizelgesinde bir anı benzersiz şekilde tanımlar ve küresel sistemler için uygundur.
  • Sınıf, IANA Time Zone Database kurallarına göre yaz saati uygulaması (DST) geçişlerini otomatik olarak işler.
  • Saat dilimleri arasında dönüştürme için withZoneSameInstant(ZoneId) yöntemi kullanılır.
  • Veritabanlarında ZonedDateTime depolamak için OffsetDateTime veya TIMESTAMP WITH TIME ZONE önerilir.

ZonedDateTime nedir?

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.

ZonedDateTime vs LocalDateTime: fark nedir?

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’da saat dilimi nasıl çalışır?

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 oluşturma

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.

kotlin
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)

Saat dilimleri arasında dönüştürme

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.

kotlin
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 uygulamasıyla (DST) çalışma

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.

kotlin
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çimlendirme

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.

kotlin
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
)

Android’de ZonedDateTime: pratik örnekler

İ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.

kotlin
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.

kotlin
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.

kotlin
data class EventResponse(
    @JsonAdapter(ZonedDateTimeAdapter::class)
    val eventTime: ZonedDateTime
)

class ZonedDateTimeAdapter : JsonAdapter<ZonedDateTime>() {
    override fun fromJson(reader: JsonReader): ZonedDateTime? {
        return ZonedDateTime.parse(
            reader.nextString()
        )
    }
}

Saat dilimleriyle çalışırken yaygın hatalar

İ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 ve OffsetDateTime arasındaki fark nedir?

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 aracılığıyla UTC’de geçerli saat nasıl alınır?

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.

ZonedDateTime Gson veya Moshi aracılığıyla serileştirilebilir mi?

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.

Zaman bir DST boşluğuna düştüğünde durum nasıl ele alınır?

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.

ZonedDateTime neden SQL veritabanları için önerilmez?

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

  • ZonedDateTime, IANA Time Zone Database aracılığıyla DST’yi doğru şekilde işleyen, saat dilimiyle tarih ve saat için değiştirilemez bir sınıftır.
  • LocalDateTime’dan temel farkı, bir bölgenin varlığıdır ve bu, ZonedDateTime’ı zaman içinde bir anın benzersiz tanımlayıcısı yapar.
  • Bölgeler arasında dönüştürme için, anı koruyan withZoneSameInstant() kullanın, withZoneSameLocal’ı değil.
  • Yaz saati geçişleri sırasında, java.time yerleşik bölge kuralları aracılığıyla boşlukları ve bindirmeleri otomatik olarak çözer.
  • Veritabanı depolama için OffsetDateTime kullanın veya Instant ve ZoneId’yi ayrı ayrı depolayın.
  • Android’de ZonedDateTime’ı cihazın yerel saatine dönüştürmek için ZoneId.systemDefault()’ı withZoneSameInstant ile birlikte kullanın.
  • JSON serileştirme için özel bir bağdaştırıcı gerekir — Kotlinx Serialization veya Jackson JavaTimeModule kullanın.

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.

Projeyi tartış

Ayrıca okuyun