Les dates en Java 8
La JSR 310
La représentation du temps
UT - Temps Universel
- Anciennement GMT
- Basé sur la rotation de la terre
Mesuré par passage des étoiles à un méridien
La terre ralenti, la durée d'une seconde n'est donc pas constante
TAI - Temps Atomique International
- 1s = "durée de 9 192 631 770 périodes de la radiation correspondant à la transition entre les deux niveaux hyperfins de l'état fondamental de l'atome de césium 133"
Moyenne d'un ensemble d'horloge atomique
UTC - Temps Universel Coordonné
- 1s UTC = 1s TAI
- synchronisé sur UT
- En 2014, 35s d'écart entre UTC et TAI
Compromis entre TUC (français) et CUT (anglais) dans la continuité de UT1, UT2
(version du temps universel)
Seconde Intercalaire - Leap second
- UTC et UT ne doivent jamais avoir plus de 0.9s d'écart
23:59:59 -> 23:59:60 -> 00:00:00
Fuseau horaire - Time zone
UTC est le temps utilisé en hiver par le Royaume-Uni
France : UTC + 1 en hiver et UTC + 2 en été
Volonté de normaliser avec des écarts entiers.
Mais Nepal: +05:45 ou +09:30 sur une partie de l'AustralieLa norme ISO 8601
Standardisation de la réprésentation de la date et l'heure
- Calendrier Grégorien
- Système horaire 24h
Exemples
année-mois-jourTheure:minute:secondefuseau horaire
-
1789-07-14T17:00:00+02:00
-
1789-07-14T15:00:00Z
-
2029-04-13T02:00:00,9867Z
-
2029-04-13
Fuseaux horaires
- Représenté soit par un décalage par rapport à l'heure UTC, par ex. +03:00 ou -05:30
- Ou par Z, méridien Zero, qui vaut l'heure UTC et donc +00:00
Complexité - égalité
- 2012-03-05:24:00:00Z
- 2012-03-06:00:00:00Z
- 2012-03-05:23:59:60Z
Complexité - Calendrier Julien
- La norme ne fixe pas de notation avant 1582
- En France, 09/12/1582 est la veille du 20/12/1582
- En Grande-Bretagne, 02/09/1752 est la veille du 14/09/1752
- Une date n'indique un moment précis qu'associée à un calendrier
Les périodes
Commence par P et ensuite un chiffre et une unité, l'heure étant séparé par T
P18Y9M4DT11H9M8S = 18 ans 9 mois 4 jours 11h 9 minutes 8 secondes
Les calendriers
Sert à la représentation usuelle
- Grégorien
- Calendrier Hégirien
- Eres japonaises
- Julien avant 1582
- etc.
Exemple de calendrier
- 2014-10-14 en grégorien
- 1435-12-20 en hégirien
- Heisei 26-10-14 en japonais
Le temps en Java...
...avant Java 8
Ce qui ne vas pas
Le Nommage
-
Date est un instant basé sur un timestamp
-
Calendar est une date et une heure
Ce qui ne vas pas
incohérence d'API
-
Date n'a aucun support de l'i18n ou du l10n
- D'ou l'ajout de Calendar, SimpleDateFormat ou encore TimeZone
- Mais SimpleDateFormat ne permet pas de formater ou parser un Calendar
- Janvier est le mois 0
-
new Date().getYear() renvoit 114 (basé sur 1900)
API deprecated pour getYear etc.
Ce qui ne vas pas
Thread safety
-
Date et Calendar sont mutables
-
SimpleDateFormat est instancié à plusieurs centaines d'endroit dans le JDK
Ce qui ne vas pas
Mauvaise Modélisation
- Une heure, sans TimeZone, sans Date (08:32)
- Une date, sans heure (2014-10-14)
- Une date, sans année (11 novembre)
- Une durée (PT3H5M)
Le temps en Java 8
JSR 310
Joda-Time
- JSR 310 Inspiré de Joda Time
- Son créateur, conscient des limites de Joda, a souhaité tout recommencer
Discussion autour de Joda-Time
- Représentation interne des dates avec des instants
- Toutes les dates peuvent voir leur Calendrier (Chronology) modifiée
- Accepte null pour la plupart de ses méthodes constructrices
représentation par instant => (changement d'heure)
la JSR 310
- Immutable
- Instant, Date, Time Partial, Duration
- Extensible
- Calendrier Grégorien par défaut
-
NullPointerException si on essaye de passer null
Packages
5 packages, 39 classes + 13 enums + 4 exception + 13 interfaces
-
java.time implémentations principales de représentations temporelles (LocalDate, Duration, Instant, etc.)
-
java.time.chrono les calendriers non grégoriens
-
java.time.format formateurs et parseurs
-
java.time.temporal représentations temporelles par champs ou unités
-
java.time.zone représentations des fuseaux horaires
java.time.Instant
- Point précis (à la nanoseconde) dans le temps
- Représentation machine basée sur timestamp
Instant now = Instant.now();
Instant instant = Instant.ofEpochSecond(3600);
instant.toEpochMilli(); // Timestamp
instant.plus(1, ChronoUnit.DAYS);
instant.isAfter(now);
java.time.LocalDateTime
- année-mois-jourTheure:minute:seconde
- PAS DE TIMEZONE
- n'est donc pas un moment précis de la ligne de temps
- représentation humaine
LocalDateTime ldt = LocalDateTime.of(2014,11,10,8,34,56)
ldt.atZone(ZoneOffset.UTC).toInstant() // TimeZone obligatoire
ldt.getMonth() // Month.NOVEMBER
ldt.getDayOfWeek() // DayOfWeek.MONDAY
ldt.withHour(12) // Nouvelle instance
java.time.ZonedDateTime
- Date, heure et zone géographique
- mais n'est pas un moment précis de la ligne de temps...
- à cause du changement d'heure
ZoneId paris = ZoneId.of("Europe/Paris");
ZoneId ny = ZoneId.of("America/New_York");
ZoneId london = ZoneId.of("Europe/London");
ZonedDateTime zdt = ZonedDateTime.of(2014,3,29,2,30,0, 0, paris);
zdt.withZoneSameLocal(london); // 2014-03-29T02:30Z
zdt.withZoneSameInstant(ny); // 2014-03-28T21:30-04:00
java.time.OffsetDateTime
- Date, heure et décalage par rapport à UTC
- est donc un moment précis dans le temps
- convertible en Instant
ZoneOffset offset = ZoneOffset.ofHours(1)
OffsetDateTime ldt = OffsetDateTime.of(2014,3,29,2,30,0, 0, offset)
ldt.toInstant()
java.time.Duration
Duration duration = Duration.ofHours(2).plusMinutes(74);
duration.toString(); // PT3H14M
Instant now = Instant.now();
Instant now2 = Instant.now().plusMillis(60002);
Duration d2 = Duration.between(now, now2); //PT1M0.002S
duration.plus(d2); // PT3H15M0.002S
java.time.Period
- une période...
- ...dont la durée dépend de l'endroit ou elle est appliquée
Period period = Period.ofMonths(2).plusDays(5);
period.toString(); // P2M5D
ZoneOffset offset = ZoneOffset.ofHours(2);
OffsetDateTime odt = OffsetDateTime.of(2013,2,4,8,0,0,0, offset);
odt.plus(period); //2013-04-09T08:00+02:00
Period.between(LocalDate.of(1954,3,2), LocalDate.of(2013,10,5)); //P59Y7M3D
Partials
-
LocalDate : 2014-10-23
-
YearMonth : 2014-10
-
Year : 2014
-
LocalTime : 08:34:23
-
DayOfWeek : DayOfWeek.TUESDAY
-
Month : Month.JANUARY
-
MonthDay : 23 septembre
java.time.Clock
- Factory d'Instant
- Mockable...
- ... et donc testable
- remplace System.currentTimeMillis()
public class MyBean {
private Clock clock; // dependency inject
public void process(LocalDate eventDate) {
if (eventDate.isBefore(LocalDate.now(clock)) {
}
}
}
Parsing
- Depuis les classes elle-même, format ISO
LocalDate.parse("2013-03-24")
- Depuis les classes elle-même, avec formatter
import static java.time.format.DateTimeFormatter.*;
DateTimeFormatter fmt = ofPattern("dd MM yyyy");
LocalDate.parse("24 03 2013", fmt);
- Depuis un formateur
import static java.time.format.DateTimeFormatter.*;
DateTimeFormatter fmt = ofPattern("dd MM yyyy");
LocalDate.from(fmt.parse("23 12 1998"));
fmt.parse("23 12 1998", LocalDate::From)
Formattage
- Depuis les classes elle-même, format ISO
LocalDate.now().toString()
- Depuis les classes elle-même, avec formatter
import static java.time.format.DateTimeFormatter.*;
DateTimeFormatter fmt = ofPattern("dd MM yyyy");
LocalDate.now().format(fmt)
- Depuis un formateur
import static java.time.format.DateTimeFormatter.*;
DateTimeFormatter fmt = ofPattern("dd MM yyyy");
fmt.format(LocalDate.now())
DateTimeFormatterBuilder
Permet de créer un DateTimeFormatter
DateTimeFormatter isoDateFormatter =
new DateTimeFormatterBuilder()
.appendValue(YEAR, 4, 10, SignStyle.EXCEEDS_PAD)
.appendLiteral('-')
.appendValue(MONTH_OF_YEAR, 2)
.appendLiteral('-')
.appendValue(DAY_OF_MONTH, 2)
.toFormatter(ResolverStyle.STRICT, IsoChronology.INSTANCE);
Internationalisation
LocalDate ld = LocalDate.of(2013,2,3);
DateTimeFormatter formatter = DateTimeFormatter.ofPattern("EEEE, MMMM")
ld.format(formatter.withLocale(Locale.FRANCE)); //dimanche, février
ld.format(formatter.withLocale(Locale.JAPAN)); //日曜日, 2月
Nichiyōbi, futsuka
Autres Calendriers
JapaneseChronology.INSTANCE.date(LocalDate.of(2013,2,3))
// Instance de JapaneseDate
// Japanese Heisei 25-02-03
JapaneseChronology.INSTANCE.localDateTime(LocalDateTime.of(2013,2,3,8,30))
// Instance de ChronoLocalDateTime<JapaneseDate>
// Japanese Heisei 25-02-03T08:30
HijrahChronology.INSTANCE.date(1433,3,22)
// Instance de HijrahDate
// Hijrah-umalqura AH 1433-03-22
Égalité
OffsetDateTime dt1 = OffsetDateTime.parse("2012-11-05T06:00+01:00");
OffsetDateTime dt2 = OffsetDateTime.parse("2012-11-05T07:00+02:00");
dt1.equals(dt2) // false
dt1.compareTo(dt2) // -1
dt1.isEqual(dt2) // true
Quand choisir quoi ?
- Une année
- Year et pas Integer
- Mois ou Jour de Semaine
- enum Month ou DayOfWeek
- Date de naissance
- LocalDate
- Date de création/de modification
- Instant
- Horaires d'un train
- ZonedDateTime
- Entrée dans un agenda
- LocalDateTime
Instant et pas LocalDateTime, parce que le moment de la création ne change pas en fonction de la timezone
Entrée dans l'agenda -> si je me balade dans le monde avec mon agenda, la date est toujours local au lieu ou je suis.
Limites
Représentation relativiste du temps
Que choisir ?
- J'ai Java8
- java.time.*
- J'ai Java < 8, mais j'utilise déjà JodaTime
- ne rien changer
- J'ai Java < 8, et je n'utilise rien
- le backport threeten (http://www.threeten.org/)
- Je m'en fous, je fais du JavaScript
- moment.js