Domanda

Trovo curioso che il modo più ovvio per creare Date oggetti in Java sia stato deprecato e sembra essere stato " sostituito " con un calendario non così ovvio da usare lenient.

Come si controlla che una data, data come una combinazione di giorno, mese e anno, sia una data valida?

Ad esempio, 2008-02-31 (come in aaaa-mm-gg) sarebbe una data non valida.

È stato utile?

Soluzione

Il modo attuale è usare la classe del calendario. Ha il setLenient metodo che convaliderà la data, il lancio e l'eccezione se è fuori intervallo come nell'esempio.

Hai dimenticato di aggiungere: Se ricevi un'istanza di calendario e imposti l'ora utilizzando la tua data, è così che ottieni la convalida.

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

Altri suggerimenti

La chiave è df.setLenient (false); . Questo è più che sufficiente per casi semplici. Se stai cercando librerie più solide (ne dubito) e / o alternative come joda-time, consulta la risposta dell'utente < !> quot;! tardate <> quot;

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;
        }
}

Come mostrato da @Maglob, l'approccio di base è testare la conversione da stringa ad oggi usando SimpleDateFormat.parse . Ciò catturerà combinazioni giorno / mese non valide come il 31-02-2008.

Tuttavia, in pratica ciò è raramente sufficiente poiché SimpleDateFormat.parse è estremamente liberale. Esistono due comportamenti che potrebbero interessarti:

Caratteri non validi nella stringa della data Sorprendentemente, 2008-02-2x sarà & Quot; passerà & Quot; come data valida con formato locale = " yyyy-MM-dd " per esempio. Anche quando isLenient == false.

Anni: 2, 3 o 4 cifre? Potresti anche voler applicare anni di 4 cifre anziché consentire il comportamento predefinito SimpleDateFormat (che interpreterà & Quot; 12-02-31 & Quot; in modo diverso a seconda che il tuo formato sia & Quot; yyyy- MM-dd & Quot; oppure & Quot; yy-MM-dd & Quot;)

Una soluzione rigorosa con la libreria standard

Quindi un test completo da stringa a data potrebbe assomigliare a questo: una combinazione di regex match e quindi una conversione forzata della data. Il trucco con regex è renderlo adatto alle impostazioni locali.

  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);

Nota che il regex presuppone che la stringa di formato contenga solo caratteri di giorno, mese, anno e separatore. A parte questo, il formato può essere in qualsiasi formato locale: & Quot; d / MM / yy & Quot ;, & Quot; yyyy-MM-dd & Quot ;, e così via. La stringa di formato per la locale corrente può essere ottenuta in questo modo:

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

Tempo di Joda - Migliore alternativa?

Ho sentito parlare di tempo di joda e ho pensato di fare un confronto. Due punti:

  1. Sembra meglio essere severo riguardo ai caratteri non validi nella stringa della data, a differenza di SimpleDateFormat
  2. Non riesco ancora a vedere un modo per imporre anni di 4 cifre (ma immagino che potresti creare il tuo DateTimeFormatter per questo scopo)

È abbastanza semplice da usare:

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;
}

Puoi utilizzare SimpleDateFormat

Ad esempio qualcosa del tipo:

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

tl; dr

Utilizza la modalità rigorosa su java.time.DateTimeFormatter a analizzare un LocalDate . Trap per 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.
)

Dopo l'analisi, potresti verificare un valore ragionevole. Ad esempio, una data di nascita negli ultimi cento anni.

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

Evita le classi di data e ora legacy

Evita di usare le vecchie problematiche classi data-ora fornite con le prime versioni di Java. Ora soppiantato da java.time classi.

DateTimeFormatter & amp; ResolverStyle & amp; ResolverStyle.LENIENT

La classe ResolverStyle.SMART rappresenta un valore solo per la data senza ora del giorno e senza fuso orario.

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 );
}

Il ResolverStyle.STRICT La classe può essere impostata per analizzare le stringhe con una delle tre modalità di clemenza definite nella java.util.Date enum. Inseriamo una riga nel codice sopra per provare ciascuna delle modalità.

f = f.withResolverStyle ( ResolverStyle.LENIENT );

I risultati:

  • Calendar
    ld: 2000-03-02
  • SimpleDateFormat
    ld: 2000-02-29
  • java.sql.*
    ERRORE: java.time.format.DateTimeParseException: impossibile analizzare il testo '31 / 02/2000 ': data non valida' 31 FEBBRAIO '

Possiamo vederlo in < => , la data non valida viene spostata in avanti di un numero equivalente di giorni. In Interval (impostazione predefinita), viene presa la decisione logica di mantenere la data entro il mese e andare con l'ultimo giorno possibile del mese, il 29 febbraio in un anno bisestile, poiché non vi è 31 ° giorno in quel mese. La modalità YearWeek genera un'eccezione lamentando che non esiste una data del genere.

Tutti e tre questi sono ragionevoli a seconda del problema aziendale e delle politiche. Sembra che nel tuo caso desideri che la modalità rigorosa rifiuti la data non valida anziché regolarla.


Informazioni su java.time

Il java.time framework è integrato in Java 8 e versioni successive. Queste classi soppiantano le vecchie e problematiche legacy classi data-ora come YearQuarter , <=> , & amp; <=> .

Il progetto Joda-Time , ora in modalità di manutenzione , consiglia la migrazione a java.time classi.

Per saperne di più, consulta Tutorial Oracle . E mareStack Overflow di rch per molti esempi e spiegazioni. La specifica è JSR 310 .

Puoi scambiare oggetti java.time direttamente con il tuo database. Utilizzare un driver JDBC conforme a JDBC 4.2 o successivo. Nessuna necessità di stringhe, nessuna necessità di <=> classi.

Dove ottenere le classi java.time?

Il ThreeTen-Extra estende java.time con classi aggiuntive. Questo progetto è un banco di prova per possibili aggiunte future a java.time. Puoi trovare alcune lezioni utili qui come <=> , <=> , < a href = "http://www.threeten.org/threeten-extra/apidocs/org/threeten/extra/YearQuarter.html" rel = "noreferrer"> <=> e altro .

java.time

Con API Data e ora ( classi java.time ) integrato in Java 8 e versioni successive, è possibile utilizzare 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;
}

Una soluzione rigorosa alternativa che utilizza la libreria standard consiste nell'eseguire quanto segue:

1) Crea un SimpleDateFormat rigoroso usando il tuo pattern

2) Tentativo di analizzare il valore immesso dall'utente utilizzando l'oggetto formato

3) In caso di successo, riformatta la Data risultante da (2) utilizzando lo stesso formato data (da (1))

4) Confronta la data riformattata con il valore originale immesso dall'utente. Se sono uguali, il valore inserito corrisponde rigorosamente al modello.

In questo modo, non è necessario creare espressioni regolari complesse, nel mio caso avevo bisogno di supportare tutta la sintassi del pattern di SimpleDateFormat, piuttosto che limitarmi a determinati tipi come solo giorni, mesi e anni.

Basandosi sulla risposta di @Pangaea per risolvere il problema segnalato da @ ceklock , ho aggiunto un metodo per verificare che dateString non contenga caratteri non validi.

Ecco come faccio:

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+");
}

Ti suggerisco di usare org.apache.commons.validator.GenericValidator classe da apache.

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

Nota: rigoroso - Indica se avere o meno una corrispondenza esatta del datePattern.

Penso che il più semplice sia semplicemente convertire una stringa in un oggetto data e convertirla nuovamente in una stringa. La stringa data specificata va bene se entrambe le stringhe corrispondono ancora.

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;
}

Supponendo che entrambi siano Stringhe (altrimenti sarebbero già Date valide), ecco un modo:

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);
    }
}

Ecco l'output che ottengo:

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

Come puoi vedere, gestisce bene entrambi i casi.

Questo funziona alla grande per me. Approccio suggerito sopra da 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;
    }
}

Due commenti sull'uso di SimpleDateFormat.

  

dovrebbe essere dichiarato come istanza statica   se dichiarato come accesso statico deve essere sincronizzato in quanto non è thread-safe

IME che è meglio che creare un'istanza per ogni analisi di una data.

I metodi di analisi della data sopra riportati sono utili, ho appena aggiunto un nuovo controllo nei metodi esistenti che ricontrollano la data convertita con la data originale utilizzando il formater, quindi funziona per quasi ogni caso, come ho verificato. per esempio. 29/02/2013 non è valida. La funzione specificata analizza la data secondo i formati di data correnti accettabili. Restituisce vero se la data non viene analizzata correttamente.

 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;
    }

Ecco cosa ho fatto per l'ambiente Node senza librerie esterne:

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); 
   }
}

Ed ecco come usarlo:

isDateCorrect('2014-02-23')
true
// 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;
}

Ecco, vorrei controllare il formato della data:

 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");
}
Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top