ZonedDateTime — een immutable klasse uit het java.time-pakket die datum en tijd opslaat samen met informatie over de tijdzone (ZoneId). In tegenstelling tot LocalDateTime identificeert ZonedDateTime het moment op de tijdlijn ondubbelzinnig. Volgens de specificatie van Oracle Java 17 (2024) verwerkt de klasse de overgang naar zomertijd (DST) correct via de zoneregels uit de IANA Time Zone Database.
Belangrijkste
ZonedDateTime — een van de belangrijkste klassen van het java.time-pakket, die datum en tijd weergeeft met volledige informatie over de tijdzone. Het combineert drie componenten: LocalDateTime (datum en tijd), ZoneId (zone-identificatie) en ZoneOffset (offset ten opzichte van UTC).
In tegenstelling tot LocalDateTime, dat alleen de kloktijd (wall-clock time) opslaat zonder koppeling aan een zone, identificeert ZonedDateTime het moment ondubbelzinnig. Twee identieke LocalDateTime in verschillende tijdzones vertegenwoordigen verschillende tijdsmomenten. Twee identieke ZonedDateTime — hetzelfde moment.
De klasse is volledig immutable en thread-safe. Alle rekenkundige bewerkingen retourneren een nieuw object. ZonedDateTime implementeert de interface ChronoZonedDateTime en kan overal worden gebruikt waar zonale tijd in Java nodig is.
Volgens de Oracle Java 17-specificatie ondersteunt ZonedDateTime het werken met elke zone uit de IANA Time Zone Database, die meer dan 600 tijdzones omvat.
Het belangrijkste verschil — ZonedDateTime bevat de tijdzone, LocalDateTime niet. Dit fundamentele verschil bepaalt het toepassingsgebied van elke klasse.
LocalDateTime wordt gebruikt voor lokale gebeurtenissen: concerttijd, lesrooster, geboortedatum. Als de gebeurtenis plaatsvindt in Moskou om 15:00, registreert LocalDateTime 15:00 zonder koppeling. Als u de server naar New York verplaatst, blijft de tijd 15:00 — maar dit is nu een ander fysiek moment.
ZonedDateTime wordt toegepast voor wereldwijde gegevens: serverlogs, tijdstempels in API's, internationale vergaderingen. Als een vergadering is ingesteld op 15:00 MSK, bewaart ZonedDateTime zowel de tijd als de zone. In New York wordt dit correct weergegeven als 8:00 EST. Volgens Baeldung (2024) is de keuze tussen LocalDateTime en ZonedDateTime de meest voorkomende architectuurbeslissing bij het werken met datums.
Praktische regel: als gegevens voor één regio worden opgeslagen — gebruik LocalDateTime. Als gegevens tijdzonegrenzen overschrijden — gebruik ZonedDateTime. Als u een absoluut moment moet doorgeven — gebruik Instant.
De tijdzone in java.time wordt vertegenwoordigd door de klasse ZoneId. ZoneId is een zone-identificatie in het formaat „continent/region”, bijvoorbeeld „Europe/Moscow”, „America/New_York”, „Asia/Tokyo”. ZoneId wordt verkregen via de statische methode of(String zoneId) of via de standaard systeemtijdzone.
ZoneId is onderverdeeld in twee typen: fixed offset (vaste offset, bijvoorbeeld „+03:00”) en region-based (regionale zones, bijvoorbeeld „Europe/London”). Regionale zones bevatten regels voor de overgang naar zomertijd en historische wijzigingen. Fixed offset — alleen een vaste offset.
Om de huidige offset van ZoneId op een specifiek moment te verkrijgen, wordt de methode getRules() gebruikt, die ZoneRules retourneert. ZoneRules bevat alle overgangen en offsets voor die zone. Dit is het belangrijkste mechanisme voor de juiste verwerking van DST.
Alle tijdzones worden met JDK geleverd via tzdata-bestanden (IANA Time Zone Database) en worden regelmatig bijgewerkt. Op Android hangt de tzdata-versie af van systeemupdates via Google Play Services.
Er zijn verschillende manieren om ZonedDateTime aan te maken. De eenvoudigste is now(), die de huidige tijd in de systeemtijdzone retourneert. De variant now(ZoneId) maakt het mogelijk de huidige tijd in een opgegeven zone te verkrijgen.
De methode of(LocalDateTime, ZoneId) maakt ZonedDateTime aan van lokale tijd en zone. De variant of(int year, int month, int dayOfMonth, int hour, int minute, int second, int nanoOfSecond, ZoneId zone) — van componenten.
LocalDateTime kan worden geconverteerd naar ZonedDateTime via de methode atZone(ZoneId). Instant — via Instant.atZone(ZoneId). Date — via Date.toInstant().atZone(ZoneId).
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)
De belangrijkste conversiemethode — withZoneSameInstant(ZoneId). Converteert ZonedDateTime naar een andere tijdzone, met behoud van hetzelfde tijdsmoment. Bijvoorbeeld, 15:00 MSK → 8:00 EST. De methode withZoneSameLocal(ZoneId) wijzigt de zone, met behoud van de lokale tijd — dit geeft een ander moment.
Om de offset ten opzichte van UTC te verkrijgen, wordt de methode getOffset() gebruikt, die ZoneOffset retourneert. ZoneOffset is een subklasse van ZoneId die een vaste offset vertegenwoordigt in het formaat „+HH:mm” of „-HH:mm”.
Conversie naar Instant wordt uitgevoerd via de methode toInstant(). Instant is een absoluut tijdsmoment, onafhankelijk van de tijdzone. Omgekeerde conversie — 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"))
De overgang naar zomertijd veroorzaakt twee problemen: gaten (gap) en overlappingen (overlap). Een gat ontstaat in de lente wanneer de klok vooruit wordt gezet — een bepaalde tijd bestaat niet. Overlapping — in de herfst wanneer de tijd wordt teruggezet — dezelfde tijd bestaat twee keer.
ZonedDateTime verwerkt deze situaties via de strategie resolve. Bij het aanmaken van een object tijdens een gat verschuift java.time de tijd automatisch met de offsetwaarde. Bij het aanmaken tijdens een overlapping wordt de eerste variant (vóór de wijziging) gekozen. Het gedrag kan worden gewijzigd via withZoneSameInstant.
Of de tijd in de DST-zone ligt, kan worden gecontroleerd via zone.getRules().isDaylightSavings(instant). De methode getOffset() toont de huidige offset voor dat moment, en getRules().getDaylightSavings(instant) — de DST-correctie in milliseconden.
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-offset: $dstAmount")
}
}
Voor het formatteren van ZonedDateTime wordt DateTimeFormatter gebruikt. Het standaard ISO-formaat bevat datum, tijd en offset: „2026-07-21T15:30:00+03:00[Europe/Moscow]”. Vooraf gedefinieerde formaten: ISO_ZONED_DATE_TIME, ISO_OFFSET_DATE_TIME, ISO_INSTANT.
Gebruik voor gelokaliseerde opmaak DateTimeFormatter.ofLocalizedDateTime(FormatStyle). FormatStyle kan SHORT, MEDIUM, LONG, FULL zijn. LONG bevat de zonenaam („MSK”), FULL — de volledige naam („Moscow Standard Time”).
Belangrijk: bij het parsen van een string met ZonedDateTime moet het formaat informatie over de zone of offset bevatten. Als de zone niet is opgegeven, gebruik dan LocalDateTime.parse() en vervolgens atZone().
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
)
Het eerste voorbeeld — het weergeven van de vergadertijd voor de gebruiker in zijn eigen tijdzone. De server retourneert ZonedDateTime in UTC, de client converteert naar de lokale tijdzone van het apparaat.
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)
}
Het tweede voorbeeld — het berekenen van de tijd tot de volgende gebeurtenis, rekening houdend met de tijdzone. We gebruiken ZonedDateTime voor de servertijd en Duration.between() voor het berekenen van het verschil.
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"
}
Het derde voorbeeld — werken met de Retrofit API. De server retourneert een string in ISO-8601 met zone. We gebruiken een aangepaste deserializer voor conversie naar ZonedDateTime.
data class EventResponse(
@JsonAdapter(ZonedDateTimeAdapter::class)
val eventTime: ZonedDateTime
)
class ZonedDateTimeAdapter : JsonAdapter<ZonedDateTime>() {
override fun fromJson(reader: JsonReader): ZonedDateTime? {
return ZonedDateTime.parse(
reader.nextString()
)
}
}
De eerste fout — het gebruik van ZoneId.systemDefault() in servercode. De tijdzone van de server kan afwijken van die van de client, en het gebruik van de systeemzone op de server leidt tot onjuiste berekeningen. Geef altijd expliciet de zone op of gebruik UTC als referentie.
De tweede fout — het negeren van DST bij het berekenen van de duur. Duration.between() verwerkt overgangen correct, maar als u handmatig timestamps aftrekt, kan de overgang naar zomertijd een fout van 1 uur geven. Gebruik methoden van ChronoUnit.HOURS.between() in plaats van handmatige wiskunde.
De derde fout — verwarring tussen withZoneSameInstant en withZoneSameLocal. De eerste wijzigt de zone, behoudt het moment — de tijd verschuift. De tweede wijzigt de zone, behoudt de lokale tijd — het moment verandert. De keuze van de verkeerde methode is een van de meest voorkomende fouten volgens SonarSource (2024).
De vierde fout — de aanname dat de tijdzone van het apparaat altijd hetzelfde is als de tijdzone van de gebruiker. De gebruiker kan reizen en verwachten dat de app de tijd in zijn „thuis”-zone weergeeft, niet in de huidige zone. In dit geval moet u de zonekeuze via de interface aanbieden.
Veelgestelde vragen
ZonedDateTime bevat een regionale zone-identificatie (bijvoorbeeld „Europe/Moscow”) en verwerkt DST. OffsetDateTime slaat alleen een vaste offset (+03:00) op zonder regionale regels. Voor opslag in de database wordt OffsetDateTime aanbevolen.
Gebruik ZonedDateTime.now(ZoneOffset.UTC) of Instant.now().atZone(ZoneOffset.UTC). Beide varianten retourneren het huidige moment met een offset van nul. Gebruik voor een eenvoudige tijdstempel Instant.now() zonder koppeling aan een zone.
Ja, maar er is een aangepaste adapter nodig. Gson ondersteunt ZonedDateTime niet standaard. Moshi — ondersteunt via de adapter Rfc3339DateJsonAdapter. Voor Jackson wordt het gebruik van Kotlinx Serialization of de bibliotheek JavaTimeModule aanbevolen.
java.time verschuift de tijd automatisch met de offsetwaarde. Als de tijd 02:30 bijvoorbeeld niet bestaat bij de overgang naar 03:00, maakt ZonedDateTime een object op 03:30. De aanwezigheid van een gat kan worden gecontroleerd via ZoneRules.getTransition(instant).
JDBC 4.2 ondersteunt OffsetDateTime, maar niet direct ZonedDateTime. ZonedDateTime bevat een regionale zone die geen equivalent heeft in SQL. Het wordt aanbevolen OffsetDateTime of Instant op te slaan en de zone in een aparte kolom te bewaren.
Samenvatting
We ontwikkelen een mobiele applicatie turnkey
IT Sectr creëert sinds 2017 iOS- en Android-applicaties voor startups en bedrijven. We adviseren u en stellen de beste oplossing voor.
Lees ook