LocalDate, LocalTime en LocalDateTime — de belangrijkste klassen van het java.time-pakket die werken met datum en tijd mogelijk maken zonder koppeling aan een tijdzone. Volgens Oracle-documentatie (Java 17, 2024) zijn deze typen ontworpen als immutable en thread-safe, wat ze veilig maakt voor multi-threaded applicaties. Ze zijn beschikbaar geworden op Android via desugaring vanaf API 26, en voor oudere versies via de ThreeTenABP-bibliotheek.
Belangrijkste punten
LocalDate — klasse die datum weergeeft in jaar-maand-dag formaat zonder informatie over tijd en tijdzone. Het wordt gebruikt voor het opslaan van gegevens zoals geboortedatum, datum van een evenement of vervaldatum.
LocalDate slaat het jaar op in het bereik van -999999999 tot +999999999, maand van 1 tot 12 en dag van de maand rekening houdend met schrikkeljaren. De klasse is volledig immutable — elke bewerking retourneert een nieuw object.
LocalTime vertegenwoordigt de tijd van de dag: uren, minuten, seconden en nanoseconden. Maximale precisie — tot nanoseconde. LocalTime bevat geen informatie over datum en tijdzone, wat het handig maakt voor het opslaan van openingstijden van winkels of procesduur.
LocalDateTime combineert LocalDate en LocalTime in één object. Het is het meest gebruikte type wanneer zowel datum als tijd moeten worden opgeslagen, maar koppeling aan een tijdzone niet vereist is. Bijvoorbeeld datum en tijd van een concert in lokaal formaat.
Volgens Oracle Java Documentation (2024) zijn alle drie klassen ontworpen op basis van ideeën uit de Joda-Time-bibliotheek, maar met verbeterde architectuur en volledige integratie in de standaardbibliotheek.
Het java.time-pakket verscheen in Java 8 als vervanging voor de verouderde klassen Date, Calendar en SimpleDateFormat. De architectuur is gebaseerd op de principes van immutable objecten en een fluent-interface.
Het belangrijkste kenmerk — alle hoofdklassen zijn value-based. Dit betekent dat hun instanties worden vergeleken op waarde, niet op referentie, en ze kunnen niet worden overgeërfd. Voor het vergelijken van twee objecten wordt de methode equals gebruikt, niet de operator ==.
Het pakket is verdeeld in verschillende categorieën. Typen zonder tijdzone — LocalDate, LocalTime, LocalDateTime — worden gebruikt voor lokale data en tijden. Typen met tijdzone — ZonedDateTime, OffsetDateTime, OffsetTime — voegen informatie over afwijking of zone toe. Momentane typen — Instant — vertegenwoordigen een punt op de tijdlijn in UTC.
Deze verdeling lost het probleem op dat kenmerkend was voor de oude API: de programmeur wist nooit of het Date-object informatie over de tijdzone bevatte of niet. In java.time declareert elk type expliciet zijn semantiek.
De klasse LocalDate biedt vele methoden voor het maken, lezen en wijzigen van een datum. De huidige datum kan worden verkregen via de statische methode now(). Een specifieke datum — via de methode of(int year, int month, int dayOfMonth).
Voor het lezen van datumcomponenten worden getters gebruikt: getYear(), getMonthValue(), getDayOfMonth(), getDayOfWeek(), getDayOfYear(). De methode getMonth() retourneert enum Month, en getDayOfWeek() — enum DayOfWeek.
LocalDate ondersteunt het controleren van data. De methoden isBefore(), isAfter() en isEqual() maken het mogelijk data te vergelijken. De methode isLeapYear() controleert of het jaar een schrikkeljaar is. De methode lengthOfMonth() retourneert het aantal dagen in de maand, en lengthOfYear() — in het jaar.
Voor wijziging worden de methoden withYear(), withMonth(), withDayOfMonth() gebruikt, die een nieuw object retourneren met de gewijzigde component. De methoden plusDays(), minusMonths() en soortgelijke voeren datum rekenkunde uit.
LocalTime vertegenwoordigt de tijd van de dag met precisie tot nanoseconde. Standaard formaat — ISO-8601 (HH:mm:ss.nnnnnnnnn). Minimale waarde — 00:00, maximale — 23:59:59.999999999.
Een LocalTime-object kan worden gemaakt via now() voor de huidige tijd, of(int hour, int minute), of(int hour, int minute, int second) of of(int hour, int minute, int second, int nanoOfSecond). De methode parse(CharSequence text) parseert een string in ISO-8601 formaat.
Getters omvatten getHour(), getMinute(), getSecond(), getNano(). De methode toSecondOfDay() retourneert het aantal seconden sinds het begin van de dag, en toNanoOfDay() — nanoseconden. Dit is handig voor duurberekeningen binnen één dag.
LocalTime ondersteunt dezelfde vergelijkings- en wijzigingsbewerkingen als LocalDate: plusHours(), minusMinutes(), withHour(), withMinute(). De methoden isBefore() en isAfter() werken rekening houdend met de cycliciteit van tijd binnen een dag.
LocalDateTime combineert de mogelijkheden van LocalDate en LocalTime in één klasse. Het slaat zowel datum als tijd op, maar zonder tijdzone. Het is het meest flexibele lokale type, maar vereist voorzichtigheid bij gebruik in gedistribueerde systemen.
LocalDateTime kan worden gemaakt via de statische methoden now(), of(LocalDate date, LocalTime time), of(int year, Month month, int dayOfMonth, int hour, int minute) en hun overbelastingen. Ook kunnen LocalDate en LocalTime worden gecombineerd via de methode atTime().
LocalDateTime biedt toegang tot alle datum- en tijdvelden via de bijbehorende getters: toLocalDate() en toLocalTime() retourneren afzonderlijke componenten. De methode truncatedTo(TemporalUnit unit) maakt het mogelijk de tijd af te ronden op een bepaalde precisie — bijvoorbeeld tot minuten.
Voor conversie naar een tijdzone wordt de methode atZone(ZoneId zone) gebruikt, die ZonedDateTime retourneert. Dit is de enige manier om een tijdzone aan LocalDateTime toe te voegen.
Alle drie klassen gebruiken een uniform creatiepatroon via statische fabrieksmethoden. De constructors van de klassen zijn gedeclareerd als private — een object kan niet direct via new worden gemaakt.
Belangrijkste manieren van maken:
De methode of heeft vele overbelastingen. Voor LocalDate zijn jaar, maand en dag nodig. Voor LocalTime — uren en minuten (optioneel seconden en nanoseconden). Voor LocalDateTime — jaar, maand, dag, uren, minuten. Maand kan worden doorgegeven als int (1-12) of als enum Month.
val today = LocalDate.now()
val specificDate = LocalDate.of(2026, Month.JULY, 21)
val parsedDate = LocalDate.parse("2026-07-21")
val currentTime = LocalTime.now()
val lunchTime = LocalTime.of(13, 30, 0)
val parsedTime = LocalTime.parse("13:30:00")
val now = LocalDateTime.now()
val meeting = LocalDateTime.of(2026, 7, 21, 15, 0)
De java.time-klassen zijn ontworpen voor eenvoudige conversie tussen elkaar. LocalDate kan worden geconverteerd naar LocalDateTime via de methode atTime(LocalTime) of atStartOfDay(). LocalTime — via atDate(LocalDate).
LocalDateTime kan worden teruggeconverteerd naar LocalDate via toLocalDate() en naar LocalTime via toLocalTime(). Voor conversie naar ZonedDateTime wordt de methode atZone(ZoneId) gebruikt.
Conversie naar java.util.Date (voor compatibiliteit met oude code) vereist een tussenstap via Instant en tijdzone. Volgens Baeldung (2024) wordt deze bewerking uitgevoerd via Date.from(instant).
val date = LocalDate.of(2026, 7, 21)
val dateTime = date.atTime(LocalTime.of(10, 30))
val time = LocalTime.of(14, 0)
val dateTimeFromTime = time.atDate(date)
val extractedDate = dateTime.toLocalDate()
val extractedTime = dateTime.toLocalTime()
val zoned = dateTime.atZone(ZoneId.of("Europe/Moscow"))
Voor formatteren en parsen wordt de klasse DateTimeFormatter gebruikt. Deze biedt vooraf gedefinieerde formaten via constanten (ISO_LOCAL_DATE, ISO_LOCAL_TIME, ISO_LOCAL_DATE_TIME) en de mogelijkheid om eigen formaten te maken via patronen.
Formatteringspatronen gebruiken symbolen: yyyy — jaar, MM — maand (twee cijfers), dd — dag, HH — uur (0-23), mm — minuut, ss — seconde. De methode format() wordt aangeroepen op het datum-tijd object of via DateTimeFormatter.
DateTimeFormatter ondersteunt ook lokalisatie via de statische methoden ofLocalizedDate(FormatStyle), ofLocalizedTime(FormatStyle) en ofLocalizedDateTime(FormatStyle). De stijlen SHORT, MEDIUM, LONG en FULL zijn beschikbaar.
val formatter = DateTimeFormatter.ofPattern("dd.MM.yyyy HH:mm")
val formatted = LocalDateTime.now().format(formatter)
val parsed = LocalDate.parse(
"21.07.2026",
DateTimeFormatter.ofPattern("dd.MM.yyyy")
)
Alle drie klassen implementeren de interface Comparable, wat natuurlijke vergelijking mogelijk maakt. De methode compareTo() retourneert een negatief getal, nul of positief afhankelijk van de volgorde. De methoden isBefore(), isAfter() en isEqual() retourneren boolean.
Voor LocalDate wordt vergelijking chronologisch gedaan — een eerdere datum is kleiner. Voor LocalTime — volgens de tijd van de dag. Voor LocalDateTime — eerst op datum, daarna op tijd. Alle vergelijkingen houden correct rekening met schrikkeljaren en het aantal dagen in maanden.
Een belangrijk verschil met de oude API: equals() voor LocalDate, LocalTime en LocalDateTime vergelijkt waarden, niet referenties. Dit betekent dat twee objecten met dezelfde velden gelijk zullen zijn, zelfs als het verschillende instanties zijn.
val d1 = LocalDate.of(2026, 7, 21)
val d2 = LocalDate.of(2026, 12, 25)
if (d1.isBefore(d2)) {
Log.d("Datum", "d1 is vóór d2")
}
val sortedDates = listOf(d2, d1).sorted()
Alle drie klassen ondersteunen rekenkundige bewerkingen via de methoden plus en minus. Voor LocalDate zijn plusDays(), plusWeeks(), plusMonths(), plusYears() en vergelijkbare minus-methoden beschikbaar. LocalTime ondersteunt plusHours(), plusMinutes(), plusSeconds(), plusNanos().
LocalDateTime erft alle rekenkundige bewerkingen van beide typen. Kenmerk van LocalDate: bij het toevoegen van een maand worden de resultaten correct afgestemd op de verschillende lengtes van maanden. Bijvoorbeeld 31 januari + 1 maand = 28 (29 in een schrikkeljaar) februari.
Voor complexere bewerkingen bestaan de klassen Period (voor data) en Duration (voor tijd). De methoden plus(TemporalAmount) en minus(TemporalAmount) accepteren deze objecten.
val today = LocalDate.now()
val nextWeek = today.plusDays(7)
val nextMonth = today.plusMonths(1)
val lastYear = today.minusYears(1)
val now = LocalTime.now()
val inTwoHours = now.plusHours(2)
val halfHourAgo = now.minusMinutes(30)
Laten we een praktisch voorbeeld bekijken: een applicatie voor het registreren van werkploegen. We moeten de duur van een ploeg berekenen en bepalen of deze in de nachtelijke uren valt. We gebruiken LocalTime voor begin- en eindtijd, LocalDate voor de datum en LocalDateTime voor berekeningen van ploegen die middernacht overschrijden.
data class Shift(
val startTime: LocalTime,
val endTime: LocalTime,
val date: LocalDate
) {
fun isOvernight(): Boolean = endTime.isBefore(startTime)
fun durationInMinutes(): Long {
val start = LocalDateTime.of(date, startTime)
val end = LocalDateTime.of(
if (isOvernight()) date.plusDays(1) else date,
endTime
)
return Duration.between(start, end).toMinutes()
}
}
Tweede voorbeeld — berekening van de leeftijd van een gebruiker. We gebruiken LocalDate voor de geboortedatum en vergelijken deze met de huidige datum, rekening houdend met de dag en maand van geboorte.
fun calculateAge(birthDate: LocalDate): Int {
val today = LocalDate.now()
val period = Period.between(birthDate, today)
return period.years
}
Derde voorbeeld — werken met meldingen. LocalDateTime wordt gebruikt voor het plannen van herinneringen. We controleren of de geplande tijd is aangebroken.
data class Reminder(
val id: Long,
val scheduledAt: LocalDateTime
) {
fun isDue(): Boolean =
LocalDateTime.now().isAfter(scheduledAt)
}
Ingebouwde ondersteuning voor java.time verscheen op Android vanaf API 26 (Android 8.0 Oreo). Voor apparaten met oudere versies van Android moet desugaring worden gebruikt — een mechanisme dat ondersteuning voor nieuwe Java API's toevoegt aan eerdere versies.
Desugaring in Android Gradle Plugin wordt geconfigureerd via compileOptions in build.gradle. Het is voldoende om isCoreLibraryDesugaringEnabled = true in te stellen en de desugar_jdk_libs bibliotheek toe te voegen. Hierna wordt java.time beschikbaar voor alle API-niveaus vanaf 14.
Voor projecten die geen desugaring kunnen gebruiken (bijv. legacy-projecten op AGP lager dan 4.0), bestaat de bibliotheek ThreeTenABP — een backport van java.time. Deze biedt dezelfde klassen (LocalDate, LocalTime, LocalDateTime), maar in het pakket org.threeten.bp.
@Suppress("UnstableApiUsage")
android {
compileOptions {
isCoreLibraryDesugaringEnabled = true
}
}
dependencies {
"coreLibraryDesugaring"("com.android.tools:desugar_jdk_libs:2.1.4")
}
De eerste veelvoorkomende fout — het gebruik van LocalDateTime in gedistribueerde systemen zonder rekening te houden met de tijdzone. Als de server zich in Europe/Moscow bevindt en de client in Asia/Tokyo, zal LocalDateTime anders worden geïnterpreteerd. Oplossing: gebruik Instant of ZonedDateTime voor globale gegevens.
De tweede fout — onjuist parsen van strings. Standaard verwacht LocalDate.parse() het formaat ISO-8601 (yyyy-MM-dd). Als de string in een ander formaat is, moet DateTimeFormatter expliciet worden doorgegeven. Ook moet DateTimeParseException worden afgehandeld om te voorkomen dat de applicatie crasht bij ongeldige invoer.
De derde fout — het negeren van null-veiligheid. LocalDate, LocalTime en LocalDateTime zijn objecten die null kunnen zijn. In Kotlin wordt het gebruik van nullable types met expliciete controle of de Elvis-operator aanbevolen. In Java — controleer op null voordat methoden worden aangeroepen.
De vierde fout — verwarring tussen LocalDateTime en ZonedDateTime. LocalDateTime bevat geen informatie over de tijdzone. Als je een absoluut tijdstip moet doorgeven — gebruik dan zonale typen. Als lokale tijd voldoende is — lokale typen.
Veelgestelde vragen
Date slaat het aantal milliseconden op sinds 1970-01-01 UTC, terwijl LocalDate jaar, maand en dag opslaat zonder koppeling aan een tijdzone. Date is mutabel en niet thread-safe, LocalDate — immutable en thread-safe. Date is verouderd sinds Java 8.
Ja, LocalDateTime wordt goed gemapt op het SQL-type TIMESTAMP WITHOUT TIME ZONE. JPA en Room ondersteunen het via TypeConverter. Gebruik voor TIMESTAMP WITH TIME ZONE ZonedDateTime of OffsetDateTime.
Gebruik ChronoUnit.DAYS.between(startDate, endDate). Deze methode retourneert long — het verschil in dagen. Voor meer gedetailleerde berekening gebruikt u Period.between(), die een Period retourneert met jaren, maanden en dagen.
LocalTime ondersteunt precisie tot nanoseconden (9 decimalen). Als precisie tot milliseconden nodig is, gebruik dan truncateTo(ChronoUnit.MILLIS) vóór het opslaan. Dit voorkomt afrondingsproblemen bij serialisatie.
De methode now() gebruikt de systeemklok van het apparaat en de standaard tijdzone. Als apparaten zich in verschillende tijdzones bevinden, kan de datum verschillen. Gebruik voor een uniforme tijdstempel Instant.now(), die altijd de tijd in UTC retourneert.
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