86. La validation des données 88. Des bibliothèques open source Imprimer Sommaire Consulter avec table des matières Développons en Java   v 1.70  
Copyright (C) 1999-2012 Jean-Michel DOUDOUX  

 

87. L'utilisation des dates

 

chapitre 8 7

 

La manipulation des dates n'est pas toujours facile à mettre en oeuvre :

Pourtant le temps s'écoule de façon linéaire : c'est d'ailleurs de cette façon que les calculs de dates sont réalisés avec Java, en utilisant une représentation de la date qui indique le nombre de millisecondes écoulées depuis un point d'origine défini. Dans le cas de Java, ce point d'origine est le 1er janvier 1970. Ceci permet de définir un point dans le temps de façon unique.

L'utilisation de dates en Java est de surcroît plus compliquée à cause de l'API historique qui permet leur gestion car elle n'est pas toujours intuitive.

Ce chapitre contient plusieurs sections :

 

87.1. Les classes standards du JDK pour manipuler des dates

En Java 1.0, la classe java.util.Date était seule responsable de l'encapsulation et de la manipulation d'une date.

A partir de Java 1.1, la responsabilité de la gestion et des traitements sur les dates sont réparties sur plusieurs classes :

Les classes permettant la mise en oeuvre des dates sont dans le package java.util exceptées celles relatives à leur conversion de et vers une chaîne de caractères qui sont dans le package java.text

Le package java.sql contient aussi des classes relatives aux dates et à leur utilisation dans une base de données :

Les classes abstraites Calendar, TimeZone et DateFormat possèdent toutes une implémentation concrète respectivement GregorianCalendar, SimpleTimeZone et SimpleDateFormat.

La conception des classes qui encapsulent et manipulent des dates ne facilitent pas leur mise en oeuvre. C'est d'autant plus dommageable que l'utilisation de dates est courante notamment dans les applications de gestion.

Par exemple, l'API propose au moins quatre manières pour obtenir un point dans le temps depuis le 1 janvier 1970 :

Exemple :
  System.out.println(System.currentTimeMillis());
  System.out.println(new java.util.Date().getTime());
  System.out.println(Calendar.getlnstance().getTimelnMillis() );
  System.out.println(Calendar.getlnstance().getTime().getTime ())

L'API de gestion des dates en Java est particulièrement propice à la confusion et à l'obtention d'erreurs potentielles :

 

87.1.1. La classe java.util.Date

Cette classe encapsule, sous la forme d'une variable de type long, un point dans le temps qui est représenté par le nombre de millisecondes écoulées entre le 1 janvier 1970 à minuit heure GMT et l'instant concerné.

Depuis la version 1.1, toutes les méthodes permettant de manipuler la date sont deprecated.

Par défaut, cette classe encapsule le point courant dans le temps obtenu en utilisant la méthode System.currentTimeMillis() ce qui rend sa précision dépendante du système d'exploitation.

 

87.1.2. La classe java.util.Calendar

La classe Calendar encapsule un point dans le temps (une Date sous la forme d'une variable de type long) et permet une représentation et une manipulation dans un calendrier et un fuseau horaire.

La classe Calendar n'est pas stateless puisqu'elle encapsule un point dans le temps : il est donc nécessaire d'initialiser ce point avant de pouvoir utiliser l'instance de Calendar.

Une nouvelle instance de la classe est toujours initialisée avec le point dans le temps courant. Pour encapsuler un autre point, il faut obligatoirement après l'instanciation utiliser une des méthodes de la classe pour modifier le point encapsulé.

Pour accéder aux différentes propriétés de la date encapsulée dans l'instance de Calendar, il n'existe pas un getter pour chaque propriété mais une seule méthode get() qui attend en paramètre le nom de la propriété souhaitée et qui retourne toujours une valeur de type int.

La classe Calendar définit des constantes de type int pour le nom de ces propriétés.

La classe Calendar définit aussi plusieurs constantes qui contiennent les valeurs possibles pour certaines propriétés. Leur utilisation est fortement recommandée car certaines valeurs sont parfois surprenantes notamment celles qui encapsulent un mois. La valeur d'un mois varie de 0 à 11 correspondants aux constantes Calendar.JANUARY à Calendar.DECEMBER. Calendar définit aussi la constante UNDECIMBER qui représente le treizième mois de l'année requis par certains calendriers.

Attention : toutes ces constantes sont définies pêle-mêle dans la classe et ne sont donc pas groupées par une convention de nommage dans une interface dédiée par rôle. Elles sont toutes de types int, ce qui peut permettre d'utiliser n'importe quelle constante à la place d'une autre.

Exemple :
Calendar calendar = Calendar.getInstance();
if ( calendar.get( Calendar.MONTH )==Calendar.JANUARY ) { 
  system.out.prinln("la date courante est en janvier"); }

La classe Calendar propose trois façons de manipuler la date qu'elle encapsule en agissant sur les éléments qui la compose :

La date encapsulée dans Calendar peut être manipulée de deux façons :

 

87.1.3. La classe java.util.GregorianCalendar

La classe java.util.GregorianCalendar est la seule implémentation concrète de la classe Calendar fournie en standard. Cette implémentation correspond au calendrier Grégorien.

La méthode isLeapYear() permet de savoir si l'année encapsulée par la classe est bissextile.

 

87.1.4. Les classes java.util.TimeZone et java.util.SimpleTimeZone

La classe abstraite TimeZone et sa sous classe SimpleTimeZone encapsulent un fuseau horaire.

Une instance de type TimeZone est utilisée par la classe Calendar pour déterminer la date correspondant au point dans le temps qu'elle encapsule. Un même point dans le temps correspond à des dates/heures différentes pour deux fuseaux horaires différents.

Un fuseau horaire correspond à un certain décalage vis à vis du méridien de référence, le méridien de Greenwich. Le fuseau horaire correspondant à ce méridien est désigné par GMT.

Ce décalage peut en plus être affecté par un second décalage induit par les heures d'été et d'hiver (daylight savings time (DST)) si ceux-ci sont mis en place dans le fuseau horaire.

La classe TimeZone encapsule un nom long et un nom court qui permet d'identifier le fuseau horaire qu'elle encapsule.

La méthode String[] getAvailableIDs() permet d'obtenir les noms des TimeZones définis en standard : par exemple avec Java 6, il y a 597 TimeZones fournis.

La classe est une fabrique qui permet d'obtenir une instance de TimeZone à partir de son identifiant en utilisant la méthode getTimeZone() ou celle correspondant à la Locale courante en utilisant la méthode getDefault().

 

87.1.5. La classe java.text.DateFormat

La clase abstraite DateFormat propose les fonctionnalités de base pour interpréter et formater une date sous la forme d'une chaîne de caractères.

Ce formatage doit traduire certains éléments notamment le jour et le mois de la date selon la Locale. De nombreux formats de dates sont aussi utilisés généralement dépendant eux aussi de la Locale.

Quatre styles de formats sont définis par défaut : SHORT, MEDIUM, LONG, et FULL. Avec une Locale et un style, la classe DateFormat peut fournir un formatage standard de la date.

La classe DateFormat propose plusieurs méthodes statiques getXXXlnstance() qui sont des fabriques qui renvoient des instances de type DateFormat.

La méthode format() permet de formater une date en chaîne de caractères.

La méthode parse() permet d'extraire une date à partir de sa représentation sous la forme d'une chaîne de caractères.

La Locale et le style de la classe DateFormat ne peuvent pas être modifiés après la création de son instance.

 

87.1.6. La classe java.util.SimpleDateFormat

La classe SimpleDateFormat permet de formater et d'analyser une date en tenant compte d'une Locale. Elle hérite de la classe abstraite DateFormat.

Pour réaliser ces traitements, cette classe utilise un modèle (pattern) sous la forme d'une chaîne de caractères.

La classe DataFormat propose plusieurs méthodes pour obtenir le modèle par défaut de la Locale courante :

Ces méthodes utilisent la Locale par défaut mais chacune de ces méthodes possède une surcharge qui permet de préciser une Locale.

Pour chacune de ces méthodes, quatre styles sont utilisables : SHORT, MEDIUM, LONG et FULL. Ils permettent de désigner la richesse des informations contenues dans le modèle pour la date et/ou l'heure.

Exemple :
package com.jmd.test.dej.date;

import java.text.DateFormat;
import java.util.Date;
import java.util.Locale;

public class TestFormaterDate2 {

  /**
   * @param args
   */
  public static void main(String[] args) {
    Date aujourdhui = new Date();

    DateFormat shortDateFormat = DateFormat.getDateTimeInstance(
        DateFormat.SHORT,
        DateFormat.SHORT);

    DateFormat shortDateFormatEN = DateFormat.getDateTimeInstance(
        DateFormat.SHORT,
        DateFormat.SHORT, new Locale("EN","en"));

    DateFormat mediumDateFormat = DateFormat.getDateTimeInstance(
        DateFormat.MEDIUM,
        DateFormat.MEDIUM);

    DateFormat mediumDateFormatEN = DateFormat.getDateTimeInstance(
        DateFormat.MEDIUM,
        DateFormat.MEDIUM, new Locale("EN","en"));

    DateFormat longDateFormat = DateFormat.getDateTimeInstance(
        DateFormat.LONG,
        DateFormat.LONG);

    DateFormat longDateFormatEN = DateFormat.getDateTimeInstance(
        DateFormat.LONG,
        DateFormat.LONG, new Locale("EN","en"));

    DateFormat fullDateFormat = DateFormat.getDateTimeInstance(
        DateFormat.FULL,
        DateFormat.FULL);

    DateFormat fullDateFormatEN = DateFormat.getDateTimeInstance(
        DateFormat.FULL,
        DateFormat.FULL, new Locale("EN","en"));

    System.out.println(shortDateFormat.format(aujourdhui));
    System.out.println(mediumDateFormat.format(aujourdhui));
    System.out.println(longDateFormat.format(aujourdhui));
    System.out.println(fullDateFormat.format(aujourdhui));
    System.out.println("");
    System.out.println(shortDateFormatEN.format(aujourdhui));
    System.out.println(mediumDateFormatEN.format(aujourdhui));
    System.out.println(longDateFormatEN.format(aujourdhui));
    System.out.println(fullDateFormatEN.format(aujourdhui));
  }

}

Résultat :
27/06/06 21:36
27 juin 2006 21:36:30
27 juin 2006 21:36:30 CEST
mardi 27 juin 2006 21 h 36 CEST

6/27/06 9:36 PM
Jun 27, 2006 9:36:30 PM
June 27, 2006 9:36:30 PM CEST
Tuesday, June 27, 2006 9:36:30 PM CEST

Il est aussi possible de définir son propre format en utilisant les éléments du tableau ci-dessous. Chaque lettre du tableau est interprétée de façon particulière. Pour utiliser les caractères sans qu'ils soient interprétés dans le modèle il faut les encadrer par de simples quotes. Pour utiliser une quote il faut en mettre deux consécutives dans le modèle.

Lettre

Description

Exemple

G

Era

AD (Anno Domini), BC (Before Christ)

y

Année

06 ; 2006

M

Mois dans l'année

Septembre; Sept.; 07

w

Semaine dans l'année

34

W

Semaine dans le mois

2

D

Jour dans l'année

192

d

jour dans le mois

23

F

Jour de la semaine dans le mois

17

E

Jour de la semaine

Mercredi; Mer.

a

Marqueur AM/PM (Ante/Post Meridiem)

PM, AM

H

Heure (0-23)

23

k

Heure (1-24)

24

K

Heure en AM/PM (0-11)

6

h

Heure en AM/PM (1-12)

7

m

Minutes

59

s

Secondes

59

S

Millisecondes

12564

z

Zone horaire générale

CEST; Heure d'été d'Europe centrale

Z

Zone horaire (RFC 822)

+0200


Ces caractères peuvent être répétés pour préciser le format à utiliser :

Exemple :
package com.jmd.test.dej.date;

import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.Locale;

public class TestFormaterDate {

  public static void main(String[] args) {
    SimpleDateFormat formater = null;

    Date aujourdhui = new Date();

    formater = new SimpleDateFormat("dd-MM-yy");
    System.out.println(formater.format(aujourdhui));

    formater = new SimpleDateFormat("ddMMyy");
    System.out.println(formater.format(aujourdhui));

    formater = new SimpleDateFormat("yyMMdd");
    System.out.println(formater.format(aujourdhui));

    formater = new SimpleDateFormat("h:mm a");
    System.out.println(formater.format(aujourdhui));

    formater = new SimpleDateFormat("K:mm a, z");
    System.out.println(formater.format(aujourdhui));

    formater = new SimpleDateFormat("hh:mm a, zzzz");
    System.out.println(formater.format(aujourdhui));

    formater = new SimpleDateFormat("EEEE, d MMM yyyy");
    System.out.println(formater.format(aujourdhui));

    formater = new SimpleDateFormat("'le' dd/MM/yyyy 'à' hh:mm:ss");
    System.out.println(formater.format(aujourdhui));

    formater = new SimpleDateFormat("'le' dd MMMM yyyy 'à' hh:mm:ss");
    System.out.println(formater.format(aujourdhui));

    formater = new SimpleDateFormat("dd MMMMM yyyy GGG, hh:mm aaa");
    System.out.println(formater.format(aujourdhui));

    formater = new SimpleDateFormat("yyyyMMddHHmmss");
    System.out.println(formater.format(aujourdhui));

  }

}

Résultat :
27-06-06
270606
060627
9:37 PM
9:37 PM, CEST
09:37 PM, Heure d'été d'Europe centrale
mardi, 27 juin 2006
le 27/06/2006 à 09:37:10
le 27 juin 2006 à 09:37:10
27 juin 2006 ap. J.-C., 09:37 PM
20060627213710

Il existe plusieurs constructeurs de la classe SimpleDateFormat :

Constructeur

Rôle

SimpleDateFormat()

Constructeur par défaut utilisant le modèle par défaut et les symboles de formatage de dates de la Locale par défaut

SimpleDateFormat(String)

Constructeur utilisant le modèle fourni et les symboles de formatage de dates de la Locale par défaut

SimpleDateFormat(String, DateFormatSymbols)

Constructeur utilisant le modèle et les symboles de formatage de dates fournis

SimpleDateFormat(String, Locale)

Constructeur utilisant le modèle fourni et les symboles de formatage de dates de la Locale fournie


La classe DateFormatSymbols encapsule les différents éléments textuels qui peuvent entrer dans la composition d'une date pour une Locale donnée (les jours, les libellés courts des mois, les libellés des mois, ...).

Exemple :
package com.jmd.test.dej.date;

import java.text.DateFormatSymbols;
import java.util.Locale;

public class TestFormaterDate3 {

  public static void main(String[] args) {
    DateFormatSymbols dfsFR = new DateFormatSymbols(Locale.FRENCH);
    DateFormatSymbols dfsEN = new DateFormatSymbols(Locale.ENGLISH);

    String[] joursSemaineFR = dfsFR.getWeekdays();
    String[] joursSemaineEN = dfsEN.getWeekdays();

    StringBuffer texteFR = new StringBuffer("Jours FR ");
    StringBuffer texteEN = new StringBuffer("Jours EN ");

    for (int i = 1; i < joursSemaineFR.length; i++) {
      texteFR.append(" : ");
      texteFR.append(joursSemaineFR[i]);
      texteEN.append(" : ");
      texteEN.append(joursSemaineEN[i]);
    }
    System.out.println(texteFR);
    System.out.println(texteEN);

    texteFR = new StringBuffer("Mois courts FR ");
    texteEN = new StringBuffer("Mois courts EN ");
    String[] moisCourtsFR = dfsFR.getShortMonths();
    String[] moisCourtsEN = dfsEN.getShortMonths();

    for (int i = 0; i < moisCourtsFR.length - 1; i++) {
      texteFR.append(" : ");
      texteFR.append(moisCourtsFR[i]);
      texteEN.append(" : ");
      texteEN.append(moisCourtsEN[i]);
    }

    System.out.println(texteFR);
    System.out.println(texteEN);

    texteFR = new StringBuffer("Mois FR ");
    texteEN = new StringBuffer("Mois EN ");
    String[] moisFR = dfsFR.getMonths();
    String[] moisEN = dfsEN.getMonths();

    for (int i = 0; i < moisFR.length - 1; i++) {
      texteFR.append(" : ");
      texteFR.append(moisFR[i]);
      texteEN.append(" : ");
      texteEN.append(moisEN[i]);
    }

    System.out.println(texteFR);
    System.out.println(texteEN);

  }

}

Résultat :
Jours FR  : dimanche : lundi : mardi : mercredi : jeudi : vendredi : samedi
Jours EN  : Sunday : Monday : Tuesday : Wednesday : Thursday : Friday : Saturday
Mois courts FR  : janv. : févr. : mars : avr. : mai : juin : juil. : août : sept. : oct.
 : nov. : déc.
Mois courts EN  : Jan : Feb : Mar : Apr : May : Jun : Jul : Aug : Sep : Oct : Nov : Dec
Mois FR  : janvier : février : mars : avril : mai : juin : juillet : août : septembre :
octobre : novembre : décembre
Mois EN  : January : February : March : April : May : June : July : August : September :
 October : November : December

Il est possible de définir son propre objet DateFormatSymbols pour personnaliser les éléments textuels nécessaires au traitement des dates. La classe DateFormatSymbols propose à cet effet des setters sur chacun des éléments.

Exemple :
package com.jmd.test.dej.date;

import java.text.DateFormatSymbols;
import java.text.SimpleDateFormat;
import java.util.Date;

public class TestFormaterDate4 {

  public static void main(String[] args) {
    Date aujourdhui = new Date();
    DateFormatSymbols monDFS = new DateFormatSymbols();
    String[] joursCourts = new String[] {
        "",
        "Di",
        "Lu",
        "Ma",
        "Me",
        "Je",
        "Ve",
        "Sa" };
    monDFS.setShortWeekdays(joursCourts);
    SimpleDateFormat dateFormat = new SimpleDateFormat(
        "EEE dd MMM yyyy HH:mm:ss",
        monDFS);
    System.out.println(dateFormat.format(aujourdhui));
  }

}

Résultat :
Ma 27 juin 2006 21:38:22

Attention : il faut consulter la documentation de l'API pour connaître précisément le contenu et l'ordre des éléments fournis sous la forme de tableaux aux setters de la classe. Dans l'exemple, ci-dessus, les jours de la semaine commencent par dimanche.

La méthode applyPattern() permet de modifier le modèle d'un objet SimpleDateFormat.

La classe SimpleDataFormat permet également d'analyser une date sous la forme d'une chaîne de caractères pour la transformer en objet de type Date en utilisant un modèle. Cette opération est réalisée grâce à la méthode parse(). Si elle échoue, elle lève une exception de type ParseException.

Exemple :
package com.jmd.test.dej.date;

import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Date;

public class TestParserDate {

  public static void main(String[] args) {
    Date date = null;
    SimpleDateFormat simpleDateFormat = new SimpleDateFormat("dd/MM/yyyy");

    String date1 = "22/06/2006";
    String date2 = "22062006";

    try {
      date = simpleDateFormat.parse(date1);
      System.out.println(date);
      date = simpleDateFormat.parse(date2);
      System.out.println(date);
    } catch (ParseException e) {
      e.printStackTrace();
    }
  }
}

Résultat :
Thu Jun 22 00:00:00 CEST 2006
java.text.ParseException: Unparseable date: "22062006"
	at java.text.DateFormat.parse(Unknown Source)
	at com.jmd.test.dej.date.TestParserDate.main(TestParserDate.java:19)

 

87.1.7. Les classes java.sql.Date, java.sql.Time, java.sql.TimeStamp

Ces trois classes héritent de la classe java.util.Date pour encapsuler des données correspondant aux types DATE, TIME et TIMESTAMP de la norme SQL 92.

La classe java.sql.Date n'encapsule que la partie date en ignorant la partie horaire du point dans le temps qu'elle encapsule.

La classe java.sql.Time, elle, n'encapsule que la partie horaire en ignorant la partie date du point dans le temps qu'elle encapsule.

La classe java.sql.TimeStamp encapsule un point dans le temps en millisecondes et des informations permettant d'avoir le point dans le temps avec une précision en nanosecondes.

Ces trois méthodes redéfinissent la méthode toString() pour permettre une représentation respectant le standard SQL 92.

Exemple :
final java.sql.Date dateSQL = new java.sql.Date(new Date().getTime()) ;
System.out.println(dateSQL);

Remarque : ces trois classes ne permettent pas de prendre en compte un TimeZone explicite puisque généralement c'est celui de la base de données qui est toujours utilisé par défaut.

 

87.2. Des exemples de manipulations de dates

Cette section présente des portions de code pour répondre à des besoins courants de manipulations de dates.

Formater une date

Exemple :
protected static final SimpleDateFormat dateFormat = 
  new SimpleDateFormat("dd/MM/yyyy");
protected static final SimpleDateFormat dateHeureFormat = 
  new SimpleDateFormat("dd/MM/yyyy hh:mm:ss");

public static String formatterDate(Date date) {
  return dateFormat.format(date);
}
public static String formatterDateHeure(Date date) {
  return dateFormatHeure.format(date);
}

Extraire une date d'une chaîne de caractères

Exemple :
DateFormat df = new SimpleDateFormat("dd-MM-yyyy"); 
Date date=null;
try
{
  date= df.parse("25-12-2010");
} catch (ParseException e){
  e.printstacktrace();
} 

Ajouter/retrancher des jours à une date :

Exemple :
public static Date ajouterJour(Date date, int nbJour) { 
  Calendar cal = Calendar.getInstance(); 
  cal.setTime(date.getTime();
  cal.add(Calendar.DATE, nbJour);
  return cal.getTime();
}

ou

Exemple :
public static Date ajouterJour(Date date, int nbJour) { 
  Calendar cal = Calendar.getlnstance(); 
  cal.setTime(date.getTime();
  cal.add(Calendar.DAY_OF_MONTH, nbJour);
  return cal.getTime();
}

Pour retrancher des jours, il faut fournir un paramètre négatif au nombre de jours.

Ajouter/retrancher des mois à une date

Exemple :
public static Date ajouterMois(Date date, int nbMois) {
  Calendar cal = Calendar.getInstance();
  cal.setTime(date.getTime();
  cal.add(Calendar.MONTH, nbMois);
  return cal.getTime(); 
}

Pour retrancher des mois, il faut fournir un paramètre négatif au nombre de mois.

Ajouter/retrancher des années à une date

Exemple :
public static Date ajouterAnnee(Date date, int nbAnnee) {
  Calendar cal = Calendar.getInstance();
  cal.setTime(date.getTime());
  cal.add(Calendar.YEAR, nbAnnee);
  return cal.getTime();
}

Pour retrancher des années, il faut fournir un paramètre négatif au nombre d'années.

Ajouter/retrancher des heures à une date

Exemple :
public static Date ajouterHeure(Date date, int nbHeure) {
  Calendar cal = Calendar.getInstance();
  cal.setTime(date.getTime());
  cal.add(Calendar.HOUR, nbHeure);
  return cal.getTime();
}

Pour retrancher des heures, il faut fournir un paramètre négatif au nombre d'heures.

Ajouter/retrancher des minutes à une date

Exemple :
public static Date ajouterMinute(Date date, int nbMinute) { 
  Calendar cal = Calendar.getInstance(); 
  cal.setTime(date.getTime());
  cal.add(Calendar.MINUTE, nbMinute);
  return cal.getTime();
}

Pour retrancher des minutes, il faut fournir un paramètre négatif au nombre de minutes.

Ajouter/retrancher des secondes à une date

Exemple :
public static Date ajouterSeconde(Date date, int nbSeconde) {
  Calendar cal = Calendar.getlnstance(); 
  cal.setTime(date.getTime());
  cal.add(Calendar.SECOND, nbSeconde); 
  return cal.getTime();
}

Pour retrancher des secondes, il faut fournir un paramètre négatif au nombre de secondes.

 

87.3. La classe SimpleDateFormat

La classe SimpleDateFormat permet de formater une date pour lui donner une représentation textuelle dans un format donné ou de parser une chaîne de caractères pour extraire une date dans un format donné.

 

87.3.1. L'utilisation de la classe SimpleDateFormat

Le constructeur de la classe SimpleDateFormat attend en paramètre une chaîne de caractères qui précise le format à utiliser durant les traitements de formatage et de parsing.

La méthode format() permet de formater la date fournie en paramètre.

Exemple :
SimpleDateFormat simpleDateFormat = new SimpleDateFormat("dd/MM/yyyy");
String dateStr = simpleDateFormat.format(new Date()); 
System.out.println(dateStr);

Le format comporte de nombreuses options et peut même contenir du texte brut qui doit être échappé avec des quotes simples.

Par défaut, la classe SimpleDateFormat travail avec la Locale courante. Il est possible de préciser une autre Locale en tant que paramètre du constructeur.

Exemple :
SimpleDateFormat simpleDateFormat = new SimpleDateFormat("dd MMMM yyyy zzzz G", Locale.FRENCH);
String dateStr = simpleDateFormat.format(new Date()); 
System.out.println(dateStr);

La méthode parse() permet de déterminer une date extraite d'une chaîne de caractères en utilisant un format donné.

Exemple :
SimpleDateFormat simpleDateFormat = new SimpleDateFormat("dd/MM/yyyy");
Date date = simpleDateFormat.parse("25/12/2010"); 
System.out.println(date);

Par défaut, SimpleDateFormat travail avec la Locale par défaut qui contient le fuseau horaire (time zone)

Si la chaîne de caractères ne contient pas explicitement le fuseau horaire, il peut être nécessaire de le préciser en utilisant la méthode setTimeZone();

Exemple :
SimpleDateFormat simpleDateFormat = new SimpleDateFormat("dd/MM/yyyy");
simpleDateFormat.setTimeZone(TimeZone.getTimeZone("PST"));
Date date = simpleDateFormat.parse("25/12/2010"); 
System.out.println(date);

Il est possible de préciser le siècle si la date à parser ne contient que deux chiffres : par exemple "01/01/02" peut correspondre à une date de l'année 1902 ou 2002. La méthode set2DigitYearStart() permet de préciser la date de début de la plage de 100 ans dans laquelle l'année sera traitée. Par défaut, cette plage de 100 ans correspond à la date du jour - 80 ans jusqu'à la date du jour + 20 ans.

Exemple :
SimpleDateFormat simpleDateFormat =
new SimpleDateFormat("dd MMMM yy", Locale.FRENCH);
Date date = simpleDateFormat.parse("25-12-02");
System.out.println(date);
Date debut20emeSiecle = new GregorianCalendar(1901,1,1).getTime();
simpleDateFormat.set2DigitYearStart(debut20emeSiecle);
date = simpleDateFormat.parse("25-12-02");
System.out.println(date);

Par défaut, le parsing de la date est très permissif : le format de la date n'a pas à respecter strictement le format fourni à SimpleDateFormat. Dans ce cas, SimpleDateFormat va tenter d'extraire une date qui potentiellement ne correspond pas du tout sans générer d'erreur.

Exemple :
SimpleDateFormat simpleDateFormat =
new SimpleDateFormat("dd-MM-yyyy", Locale.FRENCH); 
Date date = simpleDateFormat.parse("31-04-10"); 
System.out.println(date);

Dans l'exemple ci-dessus, le mois d'avril ne possède que 30 jours. La classe SimpleDateFormat en déduit que l'on veut le jour suivant le 30 avril soit le 1er mai. Ce comportement est rarement celui souhaité.

Pour demander un respect strict du format, il faut passer la valeur false à la méthode setLenient(). Si le format de la date à traiter ne correspond pas, une exception est levée.

Exemple :
SimpleDateFormat simpleDateFormat =
new SimpleDateFormat("dd-MM-yyyy", Locale.FRENCH);
simpleDateFormat.setLenient(false);
Date date = simpleDateFormat.parse("31-04-10"); 
System.out.println(date);

La classe SimpleDateFormat n'est pas thread-safe car elle maintient son état, entre autre, avec deux objets de type Calendar et NumberFormat. Si deux threads utilisent la même instance pour manipuler deux dates en même temps, le résultat des traitements est aléatoire : généralement il est erroné par rapport à la date traitée ce qui conduit à une corruption des données qui n'est pas facilement détectable ou, plus rarement, une exception est levée.

L'utilisation d'une même instance de SimpleDateFormat dans un contexte multi-threads implique donc qu'il est nécessaire de prendre des précautions : le résultat peut être aléatoire lors du parsing et du formatage d'une date :

 

87.3.2. Les points faibles de la classe SimpleDateFormat

La classe SimpleDateFormat présente deux faiblesses lors de sa mise en oeuvre :

Exemple :
import java.text.ParseException; 
import java.text.SimpleDateFormat;
import java.util.Date;

public class DateUtil {

  public static final Date parse(String date) throws ParseException{
    SimpleDateFormat simpleDateFormat = new SimpleDateFormat("dd-MM-yyyy");
    return simpleDateFormat.parse(date);
  }

  public static final String format(Date date) throws ParseException{
    SimpleDateFormat simpleDateFormat = new SimpleDateFormat("dd-MM-yyyy");
    return simpleDateFormat.format(date); }
  } 

Cette solution est threadsafe mais son inconvénient est qu'elle peut requérir de nombreuses ressources si le nombre d'invocations est important.

Pour pallier au premier souci, il est possible de créer une instance de classe statique qui permettra de n'avoir qu'un seul objet.

Exemple :
import java.text.ParseException; 
import java.text.SimpleDateFormat; 
import java.util.Date;

public class DateUtil {

  public static final SimpleDateFormat simpleDateFormat = new SimpleDateFormat("dd-MM-yyyy");

  public static final Date parse(String date) throws ParseException{
    return simpleDateFormat.parse(date);
  }

  public static final String format(Date date) throws ParseException{
    return simpleDateFormat.format(date);
  }
}

Cette solution fréquemment utilisée permet de réduire le nombre d'instances créées. Malheureusement, comme indiqué dans la JavaDoc, elle ne fonctionne pas dans un environnement multithreads puisque la classe SimpleDateFormat n'est pas threadsafe. L'utilisation de la classe ci-dessus dans un contexte multi-thread peut donner des résultats aléatoires.

Cependant ces résultats aléatoires ne sont pas faciles à détecter dans une application car il faut que plusieurs threads sollicitent en même temps l'instance de SimpleDateFormat.

Exemple :
import java.text.ParseException;

public class TestSimpleDateFormat {

  public static void main(String[] args) { 
    final String[] dates = new String[] {"15-01-2000", "28-02-2005", "20-04-2005", 
      "31-07-2015" };

    Runnable runnable = new Runnable() { public void run() {
    try {
      for (int j = 0; j < 1000; j++) {
        for (int i = 0; i < 2; i++) {
          String date = DateUtil.format(DateUtil.parse(dates[i]));
          if (!(dates[i].equals(date))) {
            throw new ParseException(dates[i] + " =>"+ date, 0);
          }
        }
      }
    } catch (ParseException e) { 
      e.printStackTrace();
    }

    new Thread(runnable).start();

    Runnable runnable2 = new Runnable() { 
      public void run() {
        try {
          for (int j = 0; j < 1000; j++) {
            for (int i = 0; i < 2; i++) {
              String date = DateUtil.format(DateUtil.parse(dates[i]));
              if (!(dates[i].equals(date))) {
                throw new ParseException(dates[i] + " =>"+ date, 0);
              }
            }
          }
        } catch (ParseException e) { 
          e.printStackTrace();
        }
      }
    }; 
    new Thread(runnable2).start();
  }
}

Dans cet exemple, le nombre d'exceptions et d'anomalies de traitement est important car les threads utilisent en permanence le même objet. Dans la réalité, par exemple dans une application web, les exceptions et les dates erronées sont très rares. L'ennui avec les erreurs de formatage et de parsing c'est qu'elles sont difficiles à détecter.

Il est possible de sécuriser l'utilisation de l'instance de SimpleDateFormat en l'entourant d'un bloc synchronized dont le moniteur est l'instance de la classe SimpleDateFormat ou en définissant les méthodes utilisant l'instance synchronized. Ainsi, un seul thread pourra accéder à l'instance à la fois.

Exemple :
import java.text.ParseException; 
import java.text.SimpleDateFormat; 
import java.util.Date;

public class DateUtil {

  public static final SimpleDateFormat simpleDateFormat = new SimpleDateFormat("dd-MM-yyyy");

  public synchronized static final Date parse(String date) throws ParseException{
    return simpleDateFormat.parse(date);
  }

  public synchronized static final String format(Date date) throws ParseException{
    return simpleDateFormat.format(date);
  }
}

Cette solution simple est thread-safe mais elle peut impliquer de la contention liée au verrou posé lors de l'exécution de la méthode qui bloque l'invocation par d'autres threads.

Une autre solution est d'utiliser la classe ThreadLocal qui est capable de fournir une instance pour le thread en cours, ainsi chaque thread peut avoir sa propre instance.

Exemple :
import java.text.ParseException; 
import java.text.SimpleDateFormat; 
import java.util.Date;

public class DateUtil {

  private static ThreadLocal<SimpleDateFormat> format = new ThreadLocal<SimpleDateFormat>() {
    protected synchronized SimpleDateFormat initialValue() {
      return new SimpleDateFormat("dd-MM-yyyy");
    }
  };

  public static final Date parse(String date) throws ParseException{
    return format.get().parse(date);
  }

  public static final String format(Date date) throws ParseException{
    return format.get().format(date); }
  } 

Remarque : selon l'implémentation fournie de la classe ThreadLocal par le JRE, il peut y avoir des fuites de mémoire lors du redéploiement de l'application dans un conteneur web.

Il peut être intéressant d'utiliser une SoftReference en paramètre du ThreadLocal pour améliorer la gestion de la mémoire par la JVM.

Exemple :
import java.lang.ref.SoftReference; 
import java.text.DateFormat;
import java.text.ParseException; 
import java.text.SimpleDateFormat; 
import java.util.Date;

public class DateUtil {

  private static final ThreadLocal<SoftReference<DateFormat» format = 
    new ThreadLocal<SoftReference<DateFormat» ();

  private static DateFormat getDateFormat() { 
    SoftReference<DateFormat>      softRef = format.get();
    if (softRef != null) {
      final DateFormat result = softRef.get(); 
      if (result != null) {
        return result;
      }
    }
    final DateFormat result = new SimpleDateFormat("dd-MM-yyyy");
    softRef = new SoftReference<DateFormat>(result); 
    format.set(softRef);
    return result;
  }

  public static final Date parse(final String date) throws ParseException {
    return getDateFormat().parse(date);
  }

  public static final String format(final Date date) throws ParseException {
    return getDateFormat().format(date);
  }
}

Cette approche nécessite de recréer l'instance locale de SimpleDateFormat dans le cas où le ramasse-miettes aurait détruit la précédente.

Une autre solution peut être d'utiliser une API tiers tel que :

Lors de la mise en oeuvre de la classe SimpleDateFormat, il faut aussi être vigilent car par défaut, la classe SimpleDateFormat est très permissive : elle tente au mieux de faire correspondre la date selon le format fourni, ce qui peut conduire à un comportement non souhaité et surtout à des résultats indésirables.

Exemple :
import java.text.DateFormat;
import java.text.ParseException; 
import java.text.SimpleDateFormat; 
import java.util.Date;

public class TestDate {

  public static void main(final String[] args) {
    final DateFormat df = new SimpleDateFormat("yyyyMMddHHmmss"); 
    Date d;
    try {
      d = df.parse("2010-01-15 07:23:30");
      System.out.println(d); 
    } catch (final ParseException e) {
      e.printStackTrace(); 
    }
  }
}

Résultat :
Mon Nov 30 23:05:07 CET 2009

Pour que la classe SimpleDateFormat respecte strictement le format fourni et lève une exception de type java.text.ParseException, il faut invoquer la méthode setLenient() en lui passant la valeur false en paramètre.

Exemple :
import java.text.DateFormat;
import java.text.ParseException;
import java.text.SimpleDateFormat; 
import java.util.Date;

public class TestDate {

  public static void main(final String[] args) {
    final DateFormat df = new SimpleDateFormat("yyyyMMddHHmmss"); 
    df.setLenient(false); 
    Date d;
    try {
      d = df.parse("2010-01-15 07:23:30"); 
      System.out.println(d);
    } catch (final ParseException e) { 
      e.printStackTrace();
    } 
  }
}

Résultat :
java.text.ParseException: Unparseable date: "2010-01-15 07:23:30"
at java.text.DateFormat.parse(DateFormat.java:337) at TestDate.main(TestDate.java:39)

 

87.4. Joda Time

Joda Time est une bibliothèque open source permettant de facilement manipuler des dates. Elle propose de remplacer les fonctionnalités des classes du JDK relatives aux traitements de dates afin de faciliter leurs mises en oeuvre et d'améliorer leur performance.

JodaTime est utilisé comme modèle pour la JSR 310.

La classe DateTimeFormatter est thread-safe et immuable. Elle contient un cache en interne qui maintient des instances ce qui évite d'avoir à créer une instance à chaque utilisation.

 

en construction
La suite de cette section sera développée dans une version future de ce document

 

 

87.5. La classe FastDateFormat du projet Apache commons.lang

La classe org.apache.commons.lang.time.FastDateFormat permet le formatage d'une date comme SimpleDateFormat mais elle est plus performante et surtout thread-safe.

La classe FastDateFormat offre des fonctionnalités de formatage d'une date similaires à celles de la clase SimpleDateFormat : elle propose cependant un support du timezone différent dans le formatage.

FastDateFormat ne permet que le formatage d'une date, elle ne permet pas comme la classe SimpleDateFormat le fait, d'extraire une date d'une chaîne de caractères.

Exemple :
import java.text.SimpleDateFormat; import java.util.Date;

import org.apache.commons.lang.time.FastDateFormat;

public class TestSdf {

  public static void main(final String[] args) {
    final String format = "dd-MM-yyyy HH:mm:ss.SSS"; 
    SimpleDateFormat sdf = new SimpleDateFormat(format); 
    FastDateFormat fdf = FastDateFormat.getlnstance(format);
    final Date d = new Date();
    final int nblteration = 1000000;
    long start = 0; 
    long tempsTrt = 0;

    start = System.currentTimeMillis();
    for (int i = 0; i < nblteration; i++) {
      fdf = FastDateFormat.getlnstance(format);
      d.setTime(System.currentTimeMillis()); 
      fdf.format(d);
    }
    tempsTrt = System.currentTimeMillis() - start;
    System.out.println("FastDateFormat : " + tempsTrt + " ms");

    start = System.currentTimeMillis();
    for (int i = 0; i < nblteration; i++) {
      sdf = new SimpleDateFormat(format);
      d.setTime(System.currentTimeMillis()); 
      sdf.format(d);
    }
    tempsTrt = System.currentTimeMillis() - start;
    System.out.println("SimpleDateFormat : " + tempsTrt + " ms");
  }

} 

Résultat :
FastDateFormat : 2041 ms
SimpleDateFormat : 5360 ms

La classe org.apache.commons.lang.time.DateFormatUtils est une classe utilitaire pour le formatage de dates et d'heures en utilisant la classe FastDateFormat.

Elle propose des constantes pour des instances de FastDateFormat avec des motifs de formatage courants en ISO08601 et SMTP :

ISO_DATETIME_FORMAT

formatage d'une date/heure en ISO860 sans timezone : yyyy-MM-dd'T'HH:mm:ss

ISO_DATETIME_TIMEZONE_FORMAT

formatage d'une date/heure en ISO8601 avec timezone : yyyy-MM-dd'T'HH:mm:ssZZ

ISO_DATE_FORMAT

formatage d'une date enISO8601 sans timezone : yyyy-MM-dd

ISO_TIME_FORMAT

formatage d'une heure en pseudo ISO8601 sans time zone (la spécification ne permet pas d'avoir une heure sans timezone) : 'T'HH:mm:ss

ISO_DATE_TIME_ZONE_FORMAT

formatage d'une date enISO8601 sans timezone (la spécification ne permet pas d'avoir une heure sans timezone) : yyyy-MM-ddZZ

ISO_TIME_TIMEZONE_FORMAT

formatage d'une heure en ISO8601 avec time zone : 'T'HH:mm:ssZZ.

ISO_TIME_NOT_FORMAT

formatage d'une heure en pseudo ISO8601 sans time zone (la spécification requiert que l'heure soit préfixée par le caractère T) : HH:mm:ss.

ISO_TIME_NOTTIMEZONE_FORMAT

formatage d'une heure en pseudo ISO8601 avec time zone (la spécification requiert que l'heure soit préfixée par le caractère T) : HH:mm:ssZZ.

SMTP_DATETIME_FORMAT

formatage d'une date heure au format requis par SMTP : EEE, dd MMM yyyy EH:mm:ss Z in US locale.


Il n'est pas recommandé d'utiliser son constructeur par défaut.

Elle propose de nombreuses méthodes notamment plusieurs surcharges des méthodes format() et formatUTC() acceptant la date à formater en plusieurs formats (long, Date, Calendar) et permettant de préciser le format et la Locale à utiliser.

Exemple :
public static void main(final String[] args) { 
  final Date today = new Date();

  /*
   *    formatage en ISO8601 sans timezone : yyyy-MM-dd'T'HH:mm:ss.
   */
  String timestamp = DateFormatUtils.ISO_DATETIME_FORMAT.format(today);    
  System.out.println("timestamp = " + timestamp);

  /*
   *    formatage en ISO8601 avec timezone : yyyy-MM-dd' T'HH:mm:ssZZ.
   */
  timestamp = DateFormatUtils.ISO_DATETIME_TIME_ZONE_FORMAT.format(today)
  System.out.println("timestamp = " + timestamp);

  /*
   *    formatage au format SMTP : EEE, dd MMM yyyy HH:mm:ss Z (Locale US).
   */
  timestamp = DateFormatUtils.SMTP_DATETIME_FORMAT.format(today);
  System.out.println("timestamp = " + timestamp); }

 


  86. La validation des données 88. Des bibliothèques open source Imprimer Sommaire Consulter avec table des matières Développons en Java   v 1.70  
Copyright (C) 1999-2012 Jean-Michel DOUDOUX