LocalDate, LocalTime et LocalDateTime sont les principales classes du paquetage java.time qui fournissent la gestion de la date et de l'heure sans liaison à un fuseau horaire. Selon la documentation Oracle (Java 17, 2024), ces types sont conçus comme immuables et thread-safe, ce qui les rend sûrs pour les applications multithread. Ils sont devenus disponibles sur Android via le desugaring à partir de l'API 26 et, pour les versions plus anciennes — via la bibliothèque ThreeTenABP.
Points clés
LocalDate — une classe qui représente une date au format année-mois-jour sans informations d'heure ni de fuseau horaire. Elle est utilisée pour stocker des données telles que les anniversaires, les dates d'événements ou les dates d'expiration.
LocalDate stocke une année dans la plage de -999999999 à +999999999, un mois de 1 à 12 et un jour du mois en tenant compte des années bissextiles. La classe est complètement immuable — toute opération retourne un nouvel objet.
LocalTime représente l'heure de la journée : heures, minutes, secondes et nanosecondes. La précision maximale est d'une nanoseconde. LocalTime ne contient aucune information de date ou de fuseau horaire, ce qui le rend pratique pour stocker les heures d'ouverture ou la durée des processus.
LocalDateTime combine LocalDate et LocalTime en un seul objet. C'est le type le plus couramment utilisé lorsque vous devez stocker à la fois la date et l'heure, mais qu'aucune liaison de fuseau horaire n'est requise. Par exemple, la date et l'heure d'un concert au format local.
Selon la documentation Oracle Java (2024), les trois classes sont conçues sur la base d'idées de la bibliothèque Joda-Time, mais avec une architecture améliorée et une intégration complète dans la bibliothèque standard.
Le paquetage java.time a été introduit dans Java 8 en remplacement des classes obsolètes Date, Calendar et SimpleDateFormat. Son architecture est construite sur les principes d'objets immuables et d'interface fluide.
Une caractéristique clé — toutes les classes principales sont value-based. Cela signifie que leurs instances sont comparées par valeur, non par référence, et qu'elles ne peuvent pas être héritées. Pour comparer deux objets, utilisez la méthode equals, pas l'opérateur ==.
Le paquetage est divisé en plusieurs catégories. Types sans fuseau horaire — LocalDate, LocalTime, LocalDateTime — sont utilisés pour les dates et heures locales. Types avec fuseau horaire — ZonedDateTime, OffsetDateTime, OffsetTime — ajoutent des informations de décalage ou de zone. Types instantanés — Instant — représentent un point sur la ligne temporelle en UTC.
Cette séparation résout un problème inhérent à l'ancienne API : le développeur ne savait jamais si un objet Date contenait des informations de fuseau horaire ou non. En java.time, chaque type déclare explicitement sa sémantique.
La classe LocalDate fournit de nombreuses méthodes pour créer, lire et modifier les dates. La date actuelle peut être obtenue via la méthode statique now(). Une date spécifique — via la méthode of(int year, int month, int dayOfMonth).
Pour lire les composants de la date, des getters sont utilisés : getYear(), getMonthValue(), getDayOfMonth(), getDayOfWeek(), getDayOfYear(). La méthode getMonth() retourne l'énumération Month, et getDayOfWeek() retourne l'énumération DayOfWeek.
LocalDate prend en charge la vérification des dates. Les méthodes isBefore(), isAfter() et isEqual() permettent de comparer des dates. La méthode isLeapYear() vérifie si l'année est bissextile. La méthode lengthOfMonth() retourne le nombre de jours dans le mois, et lengthOfYear() le nombre de jours dans l'année.
Pour la modification, utilisez les méthodes withYear(), withMonth(), withDayOfMonth(), qui retournent un nouvel objet avec le composant modifié. Les méthodes plusDays(), minusMonths() et similaires effectuent l'arithmétique des dates.
LocalTime représente l'heure de la journée avec une précision à la nanoseconde. Le format standard est ISO-8601 (HH:mm:ss.nnnnnnnnn). La valeur minimale est 00:00, la maximale est 23:59:59.999999999.
Vous pouvez créer un objet LocalTime en utilisant now() pour l'heure actuelle, of(int hour, int minute), of(int hour, int minute, int second) ou of(int hour, int minute, int second, int nanoOfSecond). La méthode parse(CharSequence text) analyse une chaîne au format ISO-8601.
Les getters incluent getHour(), getMinute(), getSecond(), getNano(). La méthode toSecondOfDay() retourne le nombre de secondes depuis le début de la journée, et toNanoOfDay() retourne les nanosecondes. C'est pratique pour calculer la durée au sein d'une même journée.
LocalTime prend en charge les mêmes opérations de comparaison et de modification que LocalDate : plusHours(), minusMinutes(), withHour(), withMinute(). Les méthodes isBefore() et isAfter() fonctionnent en considérant que l'heure est cyclique au sein d'une journée.
LocalDateTime combine les capacités de LocalDate et LocalTime en une seule classe. Il stocke à la fois la date et l'heure, mais sans fuseau horaire. C'est le type local le plus flexible, mais il nécessite de la prudence lors de son utilisation dans des systèmes distribués.
Vous pouvez créer LocalDateTime via les méthodes statiques now(), of(LocalDate date, LocalTime time), of(int year, Month month, int dayOfMonth, int hour, int minute) et leurs surcharges. Vous pouvez également combiner LocalDate et LocalTime via la méthode atTime().
LocalDateTime fournit un accès à tous les champs de date et d'heure via des getters correspondants : toLocalDate() et toLocalTime() retournent des composants individuels. La méthode truncatedTo(TemporalUnit unit) permet d'arrondir l'heure à une précision donnée — par exemple, aux minutes.
Pour convertir vers un fuseau horaire, utilisez la méthode atZone(ZoneId zone), qui retourne ZonedDateTime. C'est la seule façon d'ajouter un fuseau horaire à LocalDateTime.
Les trois classes utilisent un modèle de création unifié via des méthodes statiques d'usine. Les constructeurs des classes sont déclarés private — vous ne pouvez pas créer un objet directement avec new.
Méthodes principales de création :
La méthode of a de nombreuses surcharges. Pour LocalDate, vous avez besoin de l'année, du mois et du jour. Pour LocalTime — des heures et des minutes (éventuellement des secondes et des nanosecondes). Pour LocalDateTime — année, mois, jour, heures, minutes. Le mois peut être passé comme int (1-12) ou comme l'énumération 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)
Les classes java.time sont conçues pour une conversion pratique entre elles. LocalDate peut être converti en LocalDateTime via la méthode atTime(LocalTime) ou atStartOfDay(). LocalTime — via atDate(LocalDate).
LocalDateTime peut être reconverti en LocalDate via toLocalDate() et en LocalTime via toLocalTime(). Pour convertir en ZonedDateTime, utilisez la méthode atZone(ZoneId).
La conversion en java.util.Date (pour la compatibilité avec le code existant) nécessite une étape intermédiaire via Instant et un fuseau horaire. Selon Baeldung (2024), cette opération est effectuée 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"))
Pour le formatage et l'analyse, la classe DateTimeFormatter est utilisée. Elle fournit des formats prédéfinis via des constantes (ISO_LOCAL_DATE, ISO_LOCAL_TIME, ISO_LOCAL_DATE_TIME) et la possibilité de créer des formats personnalisés via des chaînes de motif.
Les motifs de formatage utilisent des symboles : yyyy — année, MM — mois (deux chiffres), dd — jour, HH — heure (0-23), mm — minute, ss — seconde. La méthode format() est appelée sur l'objet date-heure ou via DateTimeFormatter.
DateTimeFormatter prend également en charge la localisation via les méthodes statiques ofLocalizedDate(FormatStyle), ofLocalizedTime(FormatStyle) et ofLocalizedDateTime(FormatStyle). Les styles disponibles sont SHORT, MEDIUM, LONG et FULL.
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")
)
Les trois classes implémentent l'interface Comparable, ce qui permet de les comparer naturellement. La méthode compareTo() retourne un nombre négatif, zéro ou positif selon l'ordre. Les méthodes isBefore(), isAfter() et isEqual() retournent un booléen.
Pour LocalDate, la comparaison est chronologique — une date antérieure est considérée plus petite. Pour LocalTime — par heure de la journée. Pour LocalDateTime — d'abord par date, puis par heure. Toutes les comparaisons tiennent correctement compte des années bissextiles et du nombre de jours dans les mois.
Une différence importante avec l'ancienne API : equals() pour LocalDate, LocalTime et LocalDateTime compare les valeurs, pas les références. Cela signifie que deux objets avec les mêmes champs seront égaux, même s'il s'agit d'instances différentes.
val d1 = LocalDate.of(2026, 7, 21)
val d2 = LocalDate.of(2026, 12, 25)
if (d1.isBefore(d2)) {
Log.d("Date", "d1 est avant d2")
}
val sortedDates = listOf(d2, d1).sorted()
Les trois classes prennent en charge les opérations arithmétiques via les méthodes plus et minus. Pour LocalDate, plusDays(), plusWeeks(), plusMonths(), plusYears() et les méthodes minus correspondantes sont disponibles. LocalTime prend en charge plusHours(), plusMinutes(), plusSeconds(), plusNanos().
LocalDateTime hérite de toutes les opérations arithmétiques des deux types. Une caractéristique notable de LocalDate : lors de l'ajout d'un mois, les résultats gèrent correctement les différentes longueurs des mois. Par exemple, 31 janvier + 1 mois = 28 (29 en année bissextile) février.
Pour des opérations plus complexes, il existe les classes Period (pour les dates) et Duration (pour le temps). Les méthodes plus(TemporalAmount) et minus(TemporalAmount) acceptent ces objets.
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)
Prenons un exemple pratique : une application de suivi des quarts de travail. Nous devons calculer la durée du quart et déterminer s'il tombe en période nocturne. Nous utilisons LocalTime pour les heures de début et de fin, LocalDate pour la date et LocalDateTime pour calculer les quarts qui chevauchent minuit.
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()
}
}
Un deuxième exemple — calcul de l'âge d'un utilisateur. Nous utilisons LocalDate pour la date de naissance et la comparons à la date actuelle, en tenant compte du jour et du mois de naissance.
fun calculateAge(birthDate: LocalDate): Int {
val today = LocalDate.now()
val period = Period.between(birthDate, today)
return period.years
}
Un troisième exemple — travail avec les notifications. LocalDateTime est utilisé pour planifier des rappels. Nous vérifions si l'heure planifiée est arrivée.
data class Reminder(
val id: Long,
val scheduledAt: LocalDateTime
) {
fun isDue(): Boolean =
LocalDateTime.now().isAfter(scheduledAt)
}
La prise en charge intégrée de java.time est apparue sur Android à partir de l'API 26 (Android 8.0 Oreo). Pour les appareils avec des versions Android plus anciennes, vous devez utiliser le desugaring — un mécanisme qui ajoute la prise en charge des nouvelles API Java dans les versions antérieures.
Le desugaring dans Android Gradle Plugin est configuré via compileOptions dans build.gradle. Il suffit de définir isCoreLibraryDesugaringEnabled = true et d'ajouter la bibliothèque desugar_jdk_libs. Après cela, java.time devient disponible pour tous les niveaux d'API à partir de 14.
Pour les projets qui ne peuvent pas utiliser le desugaring (par exemple, les projets existants avec AGP inférieur à 4.0), il existe la bibliothèque ThreeTenABP — un backport de java.time. Elle fournit les mêmes classes (LocalDate, LocalTime, LocalDateTime), mais dans le paquetage org.threeten.bp.
@Suppress("UnstableApiUsage")
android {
compileOptions {
isCoreLibraryDesugaringEnabled = true
}
}
dependencies {
"coreLibraryDesugaring"("com.android.tools:desugar_jdk_libs:2.1.4")
}
La première erreur courante — utiliser LocalDateTime dans des systèmes distribués sans tenir compte des fuseaux horaires. Si le serveur est en Europe/Moscow et le client en Asia/Tokyo, LocalDateTime sera interprété différemment. Solution : utilisez Instant ou ZonedDateTime pour les données globales.
La deuxième erreur — une analyse incorrecte des chaînes. Par défaut, LocalDate.parse() attend le format ISO-8601 (yyyy-MM-dd). Si la chaîne est dans un autre format, vous devez passer explicitement un DateTimeFormatter. Vous devez également gérer DateTimeParseException pour que l'application ne plante pas en cas de saisie invalide.
La troisième erreur — ignorer la sécurité null. LocalDate, LocalTime et LocalDateTime sont des objets qui peuvent être null. En Kotlin, il est recommandé d'utiliser des types nullables avec des vérifications explicites ou l'opérateur Elvis. En Java — vérifiez null avant d'appeler des méthodes.
La quatrième erreur — confondre LocalDateTime et ZonedDateTime. LocalDateTime ne contient aucune information de fuseau horaire. Si vous devez transmettre un moment absolu dans le temps — utilisez des types zonaux. Si l'heure locale suffit — utilisez des types locaux.
Foire aux questions
Date stocke le nombre de millisecondes depuis 1970-01-01 UTC, tandis que LocalDate stocke l'année, le mois et le jour sans liaison à un fuseau horaire. Date est mutable et non thread-safe, LocalDate est immuable et thread-safe. Date est obsolète depuis Java 8.
Oui, LocalDateTime se mappe bien sur le type SQL TIMESTAMP WITHOUT TIME ZONE. JPA et Room le prennent en charge via TypeConverter. Pour TIMESTAMP WITH TIME ZONE, utilisez ZonedDateTime ou OffsetDateTime.
Utilisez ChronoUnit.DAYS.between(startDate, endDate). Cette méthode retourne un long — la différence en jours. Pour un calcul plus détaillé, utilisez Period.between(), qui retourne un Period avec les années, mois et jours.
LocalTime prend en charge la précision à la nanoseconde (9 décimales). Si la précision à la milliseconde suffit, utilisez truncateTo(ChronoUnit.MILLIS) avant de sauvegarder. Cela évite les problèmes d'arrondi lors de la sérialisation.
La méthode now() utilise l'horloge système et le fuseau horaire par défaut de l'appareil. Si les appareils sont dans des fuseaux horaires différents, la date peut différer. Pour un horodatage unifié, utilisez Instant.now(), qui retourne toujours l'heure UTC.
Résumé
Nous développerons une application mobile clé en main
IT Sectr crée des applications iOS et Android pour les startups et les entreprises depuis 2017. Nous vous conseillerons et vous proposerons la meilleure solution.
Lisez aussi