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 — 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.
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.
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.
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.
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)
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).
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"))
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.
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")
}
}
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.
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
)
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.
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.
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.
data class EventResponse(
@JsonAdapter(ZonedDateTimeAdapter::class)
val eventTime: ZonedDateTime
)
class ZonedDateTimeAdapter : JsonAdapter<ZonedDateTime>() {
override fun fromJson(reader: JsonReader): ZonedDateTime? {
return ZonedDateTime.parse(
reader.nextString()
)
}
}
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
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.
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.
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.
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ő.
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
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.
Olvassa el is