ZonedDateTime — mi ez, időzónákkal és idővel való munka

Szerző: IT Sectr Megjelenés: 2026-07-13 Olvasási idő: 10 perc

ZonedDateTime — immutable osztály a java.time csomagból, amely a dátumot és az időt az időzónáról szóló információval (ZoneId) együtt tárolja. A LocalDateTime-tól eltérően a ZonedDateTime egyértelműen azonosítja a pillanatot az idővonalon. Az Oracle Java 17 (2024) specifikációja szerint az osztály a nyári időszámításra (DST) való áttérést az IANA Time Zone Database időzóna-szabályain keresztül helyesen kezeli.

Főbb pontok

  • ZonedDateTime — immutable osztály, amely egyetlen objektumban egyesíti a dátumot, az időt és az időzónát (ZoneId).
  • A LocalDateTime-tól eltérően a ZonedDateTime egyértelműen meghatározza a pillanatot az idővonalon és alkalmas globális rendszerekhez.
  • Az osztály automatikusan kezeli a nyári időszámításra (DST) való áttérést az IANA Time Zone Database szabályai szerint.
  • Az időzónák közötti konverzióhoz a withZoneSameInstant(ZoneId) metódust használjuk.
  • A ZonedDateTime adatbázisokban történő tárolása OffsetDateTime vagy TIMESTAMP WITH TIME ZONE segítségével ajánlott.

Mi az a ZonedDateTime?

ZonedDateTime — a java.time csomag egyik kulcsfontosságú osztálya, amely a dátumot és az időt az időzónáról szóló teljes információval ábrázolja. Három összetevőt egyesít: LocalDateTime (dátum és idő), ZoneId (zónaazonosító) és ZoneOffset (eltolás az UTC-hez képest).

Eltérően a LocalDateTime-tól, amely csak a falióra-időt (wall-clock time) tárolja zónához való kötöttség nélkül, a ZonedDateTime egyértelműen azonosítja a pillanatot. Két azonos LocalDateTime különböző időzónában különböző időpillanatokat képvisel. Két azonos ZonedDateTime — ugyanazt a pillanatot.

Az osztály teljesen immutable és thread-safe. Minden aritmetikai művelet új objektumot ad vissza. A ZonedDateTime megvalósítja a ChronoZonedDateTime interfészt és bárhol használható, ahol zónális idővel kell dolgozni Java-ban.

Az Oracle Java 17 specifikációja szerint a ZonedDateTime támogatja a munkát az IANA Time Zone Database bármely zónájával, amely több mint 600 időzónát foglal magában.

ZonedDateTime vs LocalDateTime: mi a különbség?

A fő különbség — a ZonedDateTime tartalmazza az időzónát, a LocalDateTime pedig nem. Ez az alapvető különbség határozza meg az egyes osztályok alkalmazási területét.

A LocalDateTime helyi eseményekhez használatos: koncertidő, órarend, születési dátum. Ha az esemény Moszkvában történik 15:00-kor, a LocalDateTime rögzíti a 15:00-t kötöttség nélkül. Ha áthelyezi a szervert New Yorkba, az idő 15:00 marad — de ez már egy másik fizikai pillanat.

A ZonedDateTime globális adatokhoz használatos: szervernaplók, időbélyegek API-ban, nemzetközi találkozók. Ha egy találkozó 15:00 MSK-ra van beállítva, a ZonedDateTime mind az időt, mind a zónát megőrzi. New Yorkban ez helyesen 8:00 EST-ként jelenik meg. A Baeldung (2024) szerint a LocalDateTime és ZonedDateTime közötti választás a leggyakoribb architektúrális döntés a dátumokkal való munka során.

Gyakorlati szabály: ha az adatok egy régióhoz vannak tárolva — használja a LocalDateTime-t. Ha az adatok átlépik az időzónák határait — használja a ZonedDateTime-t. Ha abszolút pillanatot kell átadnia — használja az Instant-ot.

Hogyan működik az időzóna a java.time-ban?

Az időzónát a java.time-ban a ZoneId osztály képviseli. A ZoneId egy zónaazonosító „continent/region” formátumban, például „Europe/Moscow”, „America/New_York”, „Asia/Tokyo”. A ZoneId-t a of(String zoneId) statikus metódussal vagy az alapértelmezett rendszeridőzónán keresztül kapjuk meg.

A ZoneId két típusra oszlik: fixed offset (fix eltolás, például „+03:00”) és region-based (regionális zónák, például „Europe/London”). A regionális zónák tartalmazzák a nyári időszámításra való áttérés szabályait és a történelmi változásokat. A fixed offset — csak fix eltolás.

Ahhoz, hogy megkapjuk a ZoneId aktuális eltolását egy adott pillanatban, a getRules() metódust használjuk, amely ZoneRules-t ad vissza. A ZoneRules tartalmazza az összes átmenetet és eltolást az adott zónához. Ez a kulcsmechanizmus a DST helyes kezeléséhez.

Az összes időzónát a JDK tzdata (IANA Time Zone Database) fájlokon keresztül biztosítja és rendszeresen frissíti. Androidon a tzdata verziója a Google Play Services-en keresztüli rendszerfrissítésektől függ.

ZonedDateTime létrehozása

A ZonedDateTime többféleképpen hozható létre. A legegyszerűbb — a now(), amely az aktuális időt adja vissza a rendszer időzónájában. A now(ZoneId) változat lehetővé teszi az aktuális idő lekérését egy meghatározott zónában.

Az of(LocalDateTime, ZoneId) metódus a helyi időből és zónából hoz létre ZonedDateTime-t. Az of(int year, int month, int dayOfMonth, int hour, int minute, int second, int nanoOfSecond, ZoneId zone) változat — összetevőkből.

A LocalDateTime a atZone(ZoneId) metódussal konvertálható ZonedDateTime-vá. Instant — az Instant.atZone(ZoneId) segítségével. Date — a Date.toInstant().atZone(ZoneId) segítségével.

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)

Konverzió időzónák között

A fő konverziós metódus — a withZoneSameInstant(ZoneId). Átalakítja a ZonedDateTime-t egy másik időzónára, megtartva ugyanazt az időpillanatot. Például, 15:00 MSK → 8:00 EST. A withZoneSameLocal(ZoneId) metódus megváltoztatja a zónát, megtartva a helyi időt — ez más pillanatot eredményez.

Az UTC-hez viszonyított eltolás lekéréséhez a getOffset() metódust használjuk, amely ZoneOffset-ot ad vissza. A ZoneOffset a ZoneId egy alosztálya, amely fix eltolást képvisel „+HH:mm” vagy „-HH:mm” formátumban.

Az Instant-tá történő átalakítás a toInstant() metódussal történik. Az Instant egy abszolút időpillanat, amely független az időzónától. A fordított konverzió — Instant.atZone(ZoneId).

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

Munka a nyári időszámítással (DST)

A nyári időszámításra való áttérés két problémát okoz: réseket (gap) és átfedéseket (overlap). Rés tavasszal keletkezik, amikor az órákat előre állítják — egy bizonyos idő nem létezik. Átfedés ősszel, amikor az időt visszaállítják — ugyanaz az idő kétszer létezik.

A ZonedDateTime ezeket a helyzeteket a resolve stratégián keresztül kezeli. Amikor egy rés idején hozunk létre objektumot, a java.time automatikusan előretolja az időt az eltolás értékével. Átfedés idején történő létrehozzáskor az első változat (a változtatás előtti) kerül kiválasztásra. A viselkedés a withZoneSameInstant segítségével módosítható.

Ellenőrizhető, hogy az idő DST zónában van-e a zone.getRules().isDaylightSavings(instant) segítségével. A getOffset() metódus megmutatja az aktuális eltolást az adott pillanathoz, a getRules().getDaylightSavings(instant) pedig — a DST korrekció mértékét ezredmásodpercben.

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("DST", "DST eltolás: $dstAmount")
    }
}

ZonedDateTime formázása

A ZonedDateTime formázásához a DateTimeFormatter-t használjuk. A szabványos ISO formátum tartalmazza a dátumot, az időt és az eltolást: „2026-07-21T15:30:00+03:00[Europe/Moscow]”. Előre meghatározott formátumok: ISO_ZONED_DATE_TIME, ISO_OFFSET_DATE_TIME, ISO_INSTANT.

Lokalizált formázáshoz használja a DateTimeFormatter.ofLocalizedDateTime(FormatStyle) metódust. A FormatStyle lehet SHORT, MEDIUM, LONG, FULL. A LONG tartalmazza a zóna nevét („MSK”), a FULL — a teljes nevet („Moscow Standard Time”).

Fontos: ZonedDateTime-val történő string elemzésekor a formátumnak tartalmaznia kell a zónára vagy eltolásra vonatkozó információt. Ha a zóna nincs megadva, használja a LocalDateTime.parse()-t, majd az atZone()-t.

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
)

ZonedDateTime Androidban: gyakorlati példák

Első példa — a találkozó idejének megjelenítése a felhasználó számára a saját időzónájában. A szerver ZonedDateTime-t ad vissza UTC-ben, a kliens átalakítja a készülék helyi időzónájára.

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

Második példa — a következő eseményig hátralevő idő kiszámítása az időzóna figyelembevételével. ZonedDateTime-t használunk a szerveridőhöz és Duration.between()-t a különbség kiszámításához.

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"
}

Harmadik példa — munka a Retrofit API-val. A szerver egy stringet ad vissza ISO-8601 formátumban zónával. Egyedi deszerializálót használunk a ZonedDateTime-vá történő átalakításhoz.

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()
        )
    }
}

Hibák az időzónákkal való munka során

Első hiba — a ZoneId.systemDefault() használata szerverkódban. A szerver időzónája eltérhet az ügyfélétől, és a rendszerzóna használata a szerveren hibás számításokhoz vezet. Mindig explicit módon adja meg a zónát, vagy használja az UTC-t referenciaként.

Második hiba — a DST figyelmen kívül hagyása az időtartam kiszámításakor. A Duration.between() helyesen kezeli az átmeneteket, de ha kézzel vonja ki a timestamp-eket, a nyári időszámításra való áttérés 1 órás hibát okozhat. Kézi számítás helyett használja a ChronoUnit.HOURS.between() metódusokat.

Harmadik hiba — a withZoneSameInstant és withZoneSameLocal összetévesztése. Az első megváltoztatja a zónát, megtartja a pillanatot — az idő eltolódik. A második megváltoztatja a zónát, megtartja a helyi időt — a pillanat változik. A rossz metódus választása az egyik leggyakoribb hiba a SonarSource (2024) szerint.

Negyedik hiba — annak feltételezése, hogy a készülék időzónája mindig megegyezik a felhasználó időzónájával. A felhasználó utazhat és elvárhatja, hogy az alkalmazás az időt az ő „otthoni” zónájában mutassa, nem az aktuálisban. Ebben az esetben a zónaválasztást a felületen keresztül kell biztosítani.

Gyakran ismételt kérdések

Mi a különbség a ZonedDateTime és az OffsetDateTime között?

A ZonedDateTime tartalmaz egy regionális zónaazonosítót (például „Europe/Moscow”) és kezeli a DST-t. Az OffsetDateTime csak fix eltolást (+03:00) tárol regionális szabályok nélkül. Adatbázisban való tároláshoz az OffsetDateTime ajánlott.

Hogyan kaphatom meg az aktuális időt UTC-ben ZonedDateTime segítségével?

Használja a ZonedDateTime.now(ZoneOffset.UTC) vagy az Instant.now().atZone(ZoneOffset.UTC) metódust. Mindkét változat az aktuális pillanatot adja vissza nulla eltolással. Egyszerű időbélyeghez használja az Instant.now()-t zónához való kötöttség nélkül.

Lehet ZonedDateTime-t szerializálni Gson vagy Moshi segítségével?

Igen, de egyedi adapter szükséges. A Gson alapértelmezetten nem támogatja a ZonedDateTime-t. A Moshi — támogatja az Rfc3339DateJsonAdapter adapteren keresztül. Jacksonhoz a Kotlinx Serialization vagy a JavaTimeModule könyvtár használata ajánlott.

Hogyan kezeljük azt a helyzetet, amikor az idő DST résbe esik?

A java.time automatikusan előretolja az időt az eltolás értékével. Például, ha a 02:30 idő nem létezik a 03:00-ra való áttéréskor, a ZonedDateTime egy 03:30-as objektumot hoz létre. A rés megléte a ZoneRules.getTransition(instant) segítségével ellenőrizhető.

Miért nem ajánlott a ZonedDateTime SQL adatbázisokhoz?

A JDBC 4.2 támogatja az OffsetDateTime-t, de nem közvetlenül a ZonedDateTime-t. A ZonedDateTime tartalmaz egy regionális zónát, amelynek nincs megfelelője SQL-ben. Ajánlott OffsetDateTime vagy Instant tárolása, a zónát pedig külön oszlopban tárolni.

Összefoglalás

  • ZonedDateTime — immutable osztály dátum és idő tárolására időzónával, amely helyesen kezeli a DST-t az IANA Time Zone Database segítségével.
  • A fő különbség a LocalDateTime-tól — a zóna jelenléte, ami a ZonedDateTime-t az időpillanat egyértelmű azonosítójává teszi.
  • Zónák közötti konverzióhoz használja a withZoneSameInstant()-t, ami megtartja a pillanatot, nem a withZoneSameLocal-t.
  • A nyári időszámításra való áttéréskor a java.time automatikusan feloldja a réseket és átfedéseket a beépített zónaszabályok segítségével.
  • Adatbázisban való tároláshoz használja az OffsetDateTime-t, vagy tárolja az Instant és ZoneId külön.
  • Androidban a ZonedDateTime eszköz helyi idejére való átalakításához használja a ZoneId.systemDefault()-t a withZoneSameInstant-tal együtt.
  • JSON szerializációhoz egyedi adapter szükséges — használja a Kotlinx Serialization-t vagy a Jackson JavaTimeModule-t.

Kulcsrakész mobilalkalmazást fejlesztünk

Az IT Sectr 2017 óta készít iOS és Android alkalmazásokat induló vállalkozásoknak és vállalkozásoknak. Tanácsot adunk, és a legjobb megoldást javasoljuk.

Projekt megbeszélése

Olvassa el is