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

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"

UTC - Temps Universel Coordonné

  • 1s UTC = 1s TAI
  • synchronisé sur UT
  • En 2014, 35s d'écart entre UTC et TAI

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é

La 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

L'existant

  • Date
  • Calendar

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)

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

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

  • une durée précise

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月
                    

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

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

Questions ?