web-dev-qa-db-fra.com

Comment vérifier la validité d'une date en Java

Je trouve curieux que le moyen le plus évident de créer des objets Date en Java soit obsolète et semble avoir été "substitué" par un calendrier moins évident.

Comment vérifiez-vous qu'une date, donnée sous la forme d'une combinaison de jour, mois et année, est une date valide?

Par exemple, 2008-02-31 (comme dans aaaa-mm-jj) serait une date invalide.

61
Bloodboiler

La méthode actuelle consiste à utiliser la classe calendar. Il a la méthode setLenient qui validera la date, les levées et les exceptions si elles sont hors limites, comme dans votre exemple. 

Oublié d'ajouter: Si vous obtenez une instance de calendrier et définissez l'heure à l'aide de votre date, voici comment vous obtenez la validation.

Calendar cal = Calendar.getInstance();
cal.setLenient(false);
cal.setTime(yourDate);
try {
    cal.getTime();
}
catch (Exception e) {
  System.out.println("Invalid date");
}
27
AdamC

La clé est df.setLenient (false); C'est plus que suffisant pour les cas simples. Si vous recherchez une bibliothèque plus robuste (je doute) et/ou alternative comme joda-time, regardez la réponse de l'utilisateur "tardate"

final static String DATE_FORMAT = "dd-MM-yyyy";

public static boolean isDateValid(String date) 
{
        try {
            DateFormat df = new SimpleDateFormat(DATE_FORMAT);
            df.setLenient(false);
            df.parse(date);
            return true;
        } catch (ParseException e) {
            return false;
        }
}
64
Aravind R. Yarram

Comme l'a montré @Maglob, l'approche de base consiste à tester la conversion de chaîne en date à l'aide de SimpleDateFormat.parse . Cela capturera les combinaisons jour/mois non valides, comme 2008-02-31.

Cependant, dans la pratique, cela suffit rarement, car SimpleDateFormat.parse est extrêmement libéral. Vous pourriez être concerné par deux comportements:

Caractères non valides dans la chaîne de date Étonnamment, 2008-02-2x "passera" comme une date valide avec locale format = "aaaa-MM-jj" par exemple. Même quand isLenient == false.

Années: 2, 3 ou 4 chiffres? Vous pouvez également vouloir appliquer des années à 4 chiffres plutôt que de permettre le comportement SimpleDateFormat par défaut (qui interprétera différemment "12-02-31" selon que votre format est "aaaa-MM-jj" ou "aa -MM-dd ")

Une solution stricte avec la bibliothèque standard

Ainsi, une chaîne complète de test de date pourrait ressembler à ceci: une combinaison de correspondance regex, puis une conversion de date forcée. Le truc avec l'expression rationnelle est de le rendre convivial pour les paramètres régionaux.

  Date parseDate(String maybeDate, String format, boolean lenient) {
    Date date = null;

    // test date string matches format structure using regex
    // - weed out illegal characters and enforce 4-digit year
    // - create the regex based on the local format string
    String reFormat = Pattern.compile("d+|M+").matcher(Matcher.quoteReplacement(format)).replaceAll("\\\\d{1,2}");
    reFormat = Pattern.compile("y+").matcher(reFormat).replaceAll("\\\\d{4}");
    if ( Pattern.compile(reFormat).matcher(maybeDate).matches() ) {

      // date string matches format structure, 
      // - now test it can be converted to a valid date
      SimpleDateFormat sdf = (SimpleDateFormat)DateFormat.getDateInstance();
      sdf.applyPattern(format);
      sdf.setLenient(lenient);
      try { date = sdf.parse(maybeDate); } catch (ParseException e) { }
    } 
    return date;
  } 

  // used like this:
  Date date = parseDate( "21/5/2009", "d/M/yyyy", false);

Notez que l'expression régulière suppose que la chaîne de format ne contient que des caractères de jour, de mois, d'année et de séparation. En dehors de cela, le format peut être dans n'importe quel format de lieu: "j/MM/aa", "aaaa-MM-jj", etc. La chaîne de format pour l'environnement local actuel pourrait être obtenue comme suit:

Locale locale = Locale.getDefault();
SimpleDateFormat sdf = (SimpleDateFormat)DateFormat.getDateInstance(DateFormat.SHORT, locale );
String format = sdf.toPattern();

Joda Time - Meilleure alternative?

J'ai entendu parler de joda time récemment et j'ai pensé comparer. Deux points:

  1. Il semble préférable d’être strict sur les caractères non valides dans la chaîne de date, contrairement à SimpleDateFormat
  2. Vous ne pouvez pas encore trouver le moyen d'appliquer des années à 4 chiffres (mais je suppose que vous pourriez créer votre propre DateTimeFormatter à cet effet)

C'est assez simple à utiliser:

import org.joda.time.format.*;
import org.joda.time.DateTime;

org.joda.time.DateTime parseDate(String maybeDate, String format) {
  org.joda.time.DateTime date = null;
  try {
    DateTimeFormatter fmt = DateTimeFormat.forPattern(format);
    date =  fmt.parseDateTime(maybeDate);
  } catch (Exception e) { }
  return date;
}
45
tardate

Vous pouvez utiliser SimpleDateFormat

Par exemple, quelque chose comme:

boolean isLegalDate(String s) {
    SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");
    sdf.setLenient(false);
    return sdf.parse(s, new ParsePosition(0)) != null;
}
36
Maglob

Java.time

Avec les API Date et heure (/ Java.time classes) intégrées à Java 8 et ultérieur, vous pouvez utiliser la classe LocalDate .

public static boolean isDateValid(int year, int month, int day) {
    boolean dateIsValid = true;
    try {
        LocalDate.of(year, month, day);
    } catch (DateTimeException e) {
        dateIsValid = false;
    }
    return dateIsValid;
}
12
Matthias Braun

tl; dr

Utilisez le mode strict sur Java.time.DateTimeFormatter pour analyser un LocalDate . Piège pour le DateTimeParseException .

LocalDate.parse(                   // Represent a date-only value, without time-of-day and without time zone.
    "31/02/2000" ,                 // Input string.
    DateTimeFormatter              // Define a formatting pattern to match your input string.
    .ofPattern ( "dd/MM/uuuu" )
    .withResolverStyle ( ResolverStyle.STRICT )  // Specify leniency in tolerating questionable inputs.
)

Après l'analyse, vous pouvez rechercher une valeur raisonnable. Par exemple, une date de naissance dans les cent dernières années.

birthDate.isAfter( LocalDate.now().minusYears( 100 ) )

Évitez les anciennes classes date-heure

Évitez d’utiliser les anciennes classes de date et d’heure problématiques livrées avec les premières versions de Java. Maintenant supplanté par les classes Java.time .

LocalDate & DateTimeFormatter & ResolverStyle

La classe LocalDate représente une valeur de date uniquement sans heure et sans fuseau horaire.

String input = "31/02/2000";
DateTimeFormatter f = DateTimeFormatter.ofPattern ( "dd/MM/uuuu" );
try {
    LocalDate ld = LocalDate.parse ( input , f );
    System.out.println ( "ld: " + ld );
} catch ( DateTimeParseException e ) {
    System.out.println ( "ERROR: " + e );
}

La classe Java.time.DateTimeFormatter peut être configurée pour analyser les chaînes avec l'un des trois modes de clémence définis dans l'en-tête ResolverStyle . Nous insérons une ligne dans le code ci-dessus pour essayer chacun des modes.

f = f.withResolverStyle ( ResolverStyle.LENIENT );

Les resultats:

  • ResolverStyle.LENIENT
    ld: 2000-03-02
  • ResolverStyle.SMART
    ld: 2000-02-29
  • ResolverStyle.STRICT
    ERREUR: Java.time.format.DateTimeParseException: le texte '31/02/2000 'n'a pas pu être analysé: date non valide' 31 FÉVRIER '

Nous pouvons voir qu'en mode ResolverStyle.LENIENT , la date non valide est avancée d'un nombre de jours équivalent. En mode ResolverStyle.SMART (par défaut), une décision logique est prise pour conserver la date dans le mois et aller avec le dernier jour possible du mois, le 29 février d'une année bissextile, car il n'y a pas de 31e jour. dans ce mois. Le mode ResolverStyle.STRICT lève une exception en se plaignant de l'absence d'une telle date.

Tous les trois sont raisonnables en fonction de votre problème d’entreprise et de vos règles. On dirait que dans votre cas, vous voulez que le mode strict rejette la date non valide plutôt que de l’ajuster.


À propos de Java.time

Le cadre Java.time est intégré à Java 8 et versions ultérieures. Ces classes supplantent les anciennes classes gênantes héritées _ date-heure telles que Java.util.Date , Calendar _, & SimpleDateFormat .

Le projet Joda-Time , désormais en mode { mode maintenance }, conseille la migration vers les classes Java.time .

Pour en savoir plus, consultez le Oracle Tutorial . Et recherchez Stack Overflow pour de nombreux exemples et explications. La spécification est JSR 310 .

Vous pouvez échanger des objets Java.time directement avec votre base de données. Utilisez un pilote JDBC compatible avec JDBC 4.2 ou une version ultérieure. Pas besoin de chaînes, pas besoin de Java.sql.* classes.

Où obtenir les classes Java.time? 

  • Java SE 8 , Java SE 9 et versions ultérieures
    • Intégré. 
    • Une partie de l'API Java standard avec une implémentation fournie.
    • Java 9 ajoute quelques fonctionnalités et corrections mineures.
  • Java SE 6 et Java SE 7
    • Une grande partie de la fonctionnalité Java.time est rétroportée vers Java 6 et 7 dans ThreeTen-Backport .
  • Android
    • Les versions ultérieures d'Android regroupent des implémentations des classes Java.time.
    • Pour les anciens Android (<26), le projet ThreeTenABP s’adapte ThreeTen-Backport (mentionné ci-dessus). Voir Comment utiliser ThreeTenABP… .

Le projet ThreeTen-Extra étend Java.time avec des classes supplémentaires. Ce projet est un terrain d’essai pour d’éventuels ajouts à Java.time. Vous pouvez y trouver des classes utiles telles que Interval _, YearWeek , YearQuarter _ et plus _.

11
Basil Bourque

Une autre solution stricte utilisant la bibliothèque standard consiste à effectuer les opérations suivantes:

1) Créez un SimpleDateFormat strict en utilisant votre modèle

2) Essayer d'analyser la valeur entrée par l'utilisateur en utilisant l'objet format

3) En cas de succès, reformatez la Date résultant de (2) en utilisant le même format de date (à partir de (1))

4) Comparez la date reformatée à la valeur d'origine saisie par l'utilisateur. Si elles sont égales, la valeur entrée correspond strictement à votre modèle.

De cette façon, vous n'avez pas besoin de créer des expressions régulières complexes - dans mon cas, je devais prendre en charge toute la syntaxe de motif de SimpleDateFormat, plutôt que de me limiter à certains types tels que quelques jours, mois et années.

6
Ben

En me basant sur la réponse de @Pangea pour résoudre le problème signalé par @ceklock , j'ai ajouté une méthode permettant de vérifier que la variable dateString ne contient aucun caractère non valide.

Voici comment je fais:

private boolean isDateCorrect(String dateString) {
    try {
        Date date = mDateFormatter.parse(dateString);
        Calendar calendar = Calendar.getInstance();
        calendar.setTime(date);
        return matchesOurDatePattern(dateString);    //added my method
    }
    catch (ParseException e) {
        return false;
    }
}

/**
 * This will check if the provided string matches our date format
 * @param dateString
 * @return true if the passed string matches format 2014-1-15 (YYYY-MM-dd)
 */
private boolean matchesDatePattern(String dateString) {
    return dateString.matches("^\\d+\\-\\d+\\-\\d+");
}
6
Sufian

Je vous suggère d'utiliser la classe org.Apache.commons.validator.GenericValidator d'Apache.

GenericValidator.isDate(String value, String datePattern, boolean strict);

Remarque: strict - Pour avoir une correspondance exacte du datePattern.

4
Ziya

Je pense que le plus simple consiste simplement à convertir une chaîne en un objet de date et à la reconvertir en une chaîne. La chaîne de date donnée est correcte si les deux chaînes correspondent encore.

public boolean isDateValid(String dateString, String pattern)
{   
    try
    {
        SimpleDateFormat sdf = new SimpleDateFormat(pattern);
        if (sdf.format(sdf.parse(dateString)).equals(dateString))
            return true;
    }
    catch (ParseException pe) {}

    return false;
}
3
mawa

En supposant que les deux sont des chaînes (sinon, elles seraient déjà des dates valides), voici un moyen:

package cruft;

import Java.text.DateFormat;
import Java.text.ParseException;
import Java.text.SimpleDateFormat;
import Java.util.Date;

public class DateValidator
{
    private static final DateFormat DEFAULT_FORMATTER;

    static
    {
        DEFAULT_FORMATTER = new SimpleDateFormat("dd-MM-yyyy");
        DEFAULT_FORMATTER.setLenient(false);
    }

    public static void main(String[] args)
    {
        for (String dateString : args)
        {
            try
            {
                System.out.println("arg: " + dateString + " date: " + convertDateString(dateString));
            }
            catch (ParseException e)
            {
                System.out.println("could not parse " + dateString);
            }
        }
    }

    public static Date convertDateString(String dateString) throws ParseException
    {
        return DEFAULT_FORMATTER.parse(dateString);
    }
}

Voici la sortie que je reçois:

Java cruft.DateValidator 32-11-2010 31-02-2010 04-01-2011
could not parse 32-11-2010
could not parse 31-02-2010
arg: 04-01-2011 date: Tue Jan 04 00:00:00 EST 2011

Process finished with exit code 0

Comme vous pouvez le constater, les deux cas sont bien traités.

2
duffymo

Cela fonctionne très bien pour moi. Approche suggérée ci-dessus par Ben. 

private static boolean isDateValid(String s) {
    SimpleDateFormat sdf = new SimpleDateFormat("dd/MM/yyyy");
    try {
        Date d = asDate(s);
        if (sdf.format(d).equals(s)) {
            return true;
        } else {
            return false;
        }
    } catch (ParseException e) {
        return false;
    }
}
2
Dresden Sparrow

Deux commentaires sur l'utilisation de SimpleDateFormat.

il devrait être déclaré comme une instance statique si déclaré en tant qu'accès statique doit être synchronisé car il n'est pas thread-safe

Il est préférable que IME instancie une instance pour chaque analyse d'une date.

1
Tom

Les méthodes ci-dessus d’analyse de date sont Nice, je viens d’ajouter une nouvelle vérification dans les méthodes existantes qui vérifient la date convertie avec la date originale à l’aide de formater, de sorte que cela fonctionne pour presque chaque cas vérifié. par exemple. 29/02/2013 est invalide date . Fonction donnée analyser la date en fonction des formats de date acceptables actuels. Il renvoie true si la date n'est pas analysée correctement.

 public final boolean validateDateFormat(final String date) {
        String[] formatStrings = {"MM/dd/yyyy"};
        boolean isInvalidFormat = false;
        Date dateObj;
        for (String formatString : formatStrings) {
            try {
                SimpleDateFormat sdf = (SimpleDateFormat) DateFormat.getDateInstance();
                sdf.applyPattern(formatString);
                sdf.setLenient(false);
                dateObj = sdf.parse(date);
                System.out.println(dateObj);
                if (date.equals(sdf.format(dateObj))) {
                    isInvalidFormat = false;
                    break;
                }
            } catch (ParseException e) {
                isInvalidFormat = true;
            }
        }
        return isInvalidFormat;
    }
0
Imran

ressemble à SimpleDateFormat ne vérifie pas le modèle strictement même après setLenient (false); La méthode est appliquée dessus, donc j’ai utilisé la méthode ci-dessous pour valider si la date entrée est une date valide ou non selon le modèle fourni.

import Java.time.format.DateTimeFormatter;
import Java.time.format.DateTimeParseException;
public boolean isValidFormat(String dateString, String pattern) {
    boolean valid = true;
    DateTimeFormatter formatter = DateTimeFormatter.ofPattern(pattern);
    try {
        formatter.parse(dateString);
    } catch (DateTimeParseException e) {
        valid = false;
    }
    return valid;
}
0
vijay
        public static String detectDateFormat(String inputDate, String requiredFormat) {
        String tempDate = inputDate.replace("/", "").replace("-", "").replace(" ", "");
        String dateFormat;

        if (tempDate.matches("([0-12]{2})([0-31]{2})([0-9]{4})")) {
            dateFormat = "MMddyyyy";
        } else if (tempDate.matches("([0-31]{2})([0-12]{2})([0-9]{4})")) {
            dateFormat = "ddMMyyyy";
        } else if (tempDate.matches("([0-9]{4})([0-12]{2})([0-31]{2})")) {
            dateFormat = "yyyyMMdd";
        } else if (tempDate.matches("([0-9]{4})([0-31]{2})([0-12]{2})")) {
            dateFormat = "yyyyddMM";
        } else if (tempDate.matches("([0-31]{2})([a-z]{3})([0-9]{4})")) {
            dateFormat = "ddMMMyyyy";
        } else if (tempDate.matches("([a-z]{3})([0-31]{2})([0-9]{4})")) {
            dateFormat = "MMMddyyyy";
        } else if (tempDate.matches("([0-9]{4})([a-z]{3})([0-31]{2})")) {
            dateFormat = "yyyyMMMdd";
        } else if (tempDate.matches("([0-9]{4})([0-31]{2})([a-z]{3})")) {
            dateFormat = "yyyyddMMM";
        } else {
            return "Pattern Not Added";
//add your required regex
        }
        try {
            String formattedDate = new SimpleDateFormat(requiredFormat, Locale.ENGLISH).format(new SimpleDateFormat(dateFormat).parse(tempDate));

            return formattedDate;
        } catch (Exception e) {
            //
            return "";
        }

    }
0
Suheb Rafique

Voici je vérifierais le format de date:

 public static boolean checkFormat(String dateTimeString) {
    return dateTimeString.matches("^\\d{4}-\\d{2}-\\d{2}") || dateTimeString.matches("^\\d{4}-\\d{2}-\\d{2}\\s\\d{2}:\\d{2}:\\d{2}")
            || dateTimeString.matches("^\\d{4}-\\d{2}-\\d{2}T\\d{2}:\\d{2}:\\d{2}") || dateTimeString
            .matches("^\\d{4}-\\d{2}-\\d{2}T\\d{2}:\\d{2}:\\d{2}Z") ||
            dateTimeString.matches("^\\d{4}-\\d{2}-\\d{2}\\s\\d{2}:\\d{2}:\\d{2}Z");
}
0
Dinesh Bhatia

Voici ce que j'ai fait pour l'environnement Node sans aucune bibliothèque externe:

Date.prototype.yyyymmdd = function() {
   var yyyy = this.getFullYear().toString();
   var mm = (this.getMonth()+1).toString(); // getMonth() is zero-based
   var dd  = this.getDate().toString();
   return zeroPad([yyyy, mm, dd].join('-'));  
};

function zeroPad(date_string) {
   var dt = date_string.split('-');
   return dt[0] + '-' + (dt[1][1]?dt[1]:"0"+dt[1][0]) + '-' + (dt[2][1]?dt[2]:"0"+dt[2][0]);
}

function isDateCorrect(in_string) {
   if (!matchesDatePattern) return false;
   in_string = zeroPad(in_string);
   try {
      var idate = new Date(in_string);
      var out_string = idate.yyyymmdd();
      return in_string == out_string;
   } catch(err) {
      return false;
   }

   function matchesDatePattern(date_string) {
      var dateFormat = /[0-9]+-[0-9]+-[0-9]+/;
      return dateFormat.test(date_string); 
   }
}

Et voici comment l'utiliser:

isDateCorrect('2014-02-23')
true
0
TennisVisuals
// to return valid days of month, according to month and year
int returnDaysofMonth(int month, int year) {
    int daysInMonth;
    boolean leapYear;
    leapYear = checkLeap(year);
    if (month == 4 || month == 6 || month == 9 || month == 11)
        daysInMonth = 30;
    else if (month == 2)
        daysInMonth = (leapYear) ? 29 : 28;
    else
        daysInMonth = 31;
    return daysInMonth;
}

// to check a year is leap or not
private boolean checkLeap(int year) {
    Calendar cal = Calendar.getInstance();
    cal.set(Calendar.YEAR, year);
    return cal.getActualMaximum(Calendar.DAY_OF_YEAR) > 365;
}
0
ashishdhiman2007

setLenient sur false si vous aimez une validation stricte

public boolean isThisDateValid(String dateToValidate, String dateFromat){

    if(dateToValidate == null){
        return false;
    }

    SimpleDateFormat sdf = new SimpleDateFormat(dateFromat);
    sdf.setLenient(false);

    try {

        //if not valid, it will throw ParseException
        Date date = sdf.parse(dateToValidate);
        System.out.println(date);

    } catch (ParseException e) {

        e.printStackTrace();
        return false;
    }

    return true;
}
0
Pravin