web-dev-qa-db-ja.com

Javaの日付を健全性チェックする方法

JavaでDateオブジェクトを作成する最も明白な方法は非推奨になり、寛大なカレンダーを使用するのはそれほど明白ではない「置換」されているように見えます。

日、月、年の組み合わせとして指定された日付が有効な日付であることをどのように確認しますか?

たとえば、2008-02-31(yyyy-mm-ddなど)は無効な日付になります。

71
Bloodboiler

現在の方法は、カレンダークラスを使用することです。この例には、日付を検証し、例のように範囲外の場合に例外をスローする setLenient メソッドがあります。

追加を忘れた場合:カレンダーインスタンスを取得し、日付を使用して時刻を設定する場合、これが検証の取得方法です。

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

キーはdf.setLenient(false);です。これは単純な場合には十分です。より堅牢な(疑わしい)および/またはjoda-timeのような代替ライブラリを探している場合は、 ユーザー "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;
        }
}
70

@Maglobが示すように、基本的なアプローチは、 SimpleDateFormat.parse を使用して文字列から日付への変換をテストすることです。これにより、2008-02-31のような無効な日/月の組み合わせが検出されます。

ただし、実際にはSimpleDateFormat.parseが非常にリベラルであるため、これで十分なことはめったにありません。懸念される可能性のある2つの動作があります。

日付文字列の無効な文字驚くべきことに、2008-02-2xはロケール形式= "yyyy-MM-dd"の有効な日付として "パス"します。 isLenient == falseの場合でも。

年:2、3、または4桁?デフォルトのSimpleDateFormat動作を許可するのではなく、4桁の年を適用することもできます( "12-02-31"を異なる方法で解釈します)フォーマットが「yyyy-MM-dd」か「yy-MM-dd」かに応じて)

標準ライブラリを使用した厳密なソリューション

したがって、完全な文字列から日付へのテストは、正規表現の一致と強制的な日付変換の組み合わせのようになります。正規表現の秘Theは、ロケールに優しいことです。

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

正規表現では、書式文字列には日、月、年、区切り文字のみが含まれていると想定されていることに注意してください。それ以外の形式は、「d/MM/yy」、「yyyy-MM-dd」など、どのロケール形式でも可能です。現在のロケールのフォーマット文字列は、次のように取得できます。

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

Joda Time-より良い代替手段?

joda time について最近聞いて、比較したいと思いました。 2つのポイント:

  1. SimpleDateFormatとは異なり、日付文字列内の無効な文字について厳密であるように見える
  2. まだ4桁の年を強制する方法がわかりません(ただし、この目的で独自の DateTimeFormatter を作成できると思います)

使い方はとても簡単です:

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

SimpleDateFormat を使用できます

たとえば、次のようなものです。

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

tl; dr

Java.time.DateTimeFormatterstrictモード を使用して、 LocalDate を解析します。 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.
)

解析後、妥当な値を確認できます。たとえば、過去100年以内の誕生日。

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

レガシーの日時クラスを避ける

最も初期のバージョンのJavaに同梱されている面倒な古い日時クラスの使用は避けてください。 Java.time クラスに取って代わりました。

LocalDateDateTimeFormatterResolverStyle

LocalDate クラスは、時刻とタイムゾーンのない日付のみの値を表します。

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

Java.time.DateTimeFormatter クラスは、 ResolverStyle 列挙で定義された3つのリーニエンシーモードのいずれかで文字列を解析するように設定できます。上記のコードに行を挿入して、各モードを試します。

f = f.withResolverStyle ( ResolverStyle.LENIENT );

結果:

  • ResolverStyle.LENIENT
    ld:2000-03-02
  • ResolverStyle.SMART
    ld:2000-02-29
  • ResolverStyle.STRICT
    エラー:Java.time.format.DateTimeParseException:テキスト'31/02/2000 'を解析できませんでした:無効な日付' FEBRUARY 31 '

ResolverStyle.LENIENT モードでは、無効な日付が同等の日数だけ前に移動していることがわかります。 ResolverStyle.SMART モード(デフォルト)では、31日目がないため、月内の日付を保持し、うるう年の月の最終日である2月29日に進むという論理的な決定が行われます。月。 ResolverStyle.STRICT モードは、そのような日付がないことを訴える例外をスローします。

これらの3つはすべて、ビジネス上の問題とポリシーに応じて適切です。あなたの場合は、ストリクトモードで無効な日付を調整するのではなく拒否するように聞こえます。


Java.timeについて

Java.time フレームワークは、Java 8以降に組み込まれています。これらのクラスは、 Java.util.DateCalendar 、および SimpleDateFormat などの厄介な古い レガシー 日時クラスに取って代わります。

メンテナンスモード になった Joda-Time プロジェクトは、 Java.time クラスへの移行を推奨しています。

詳細については、 Oracle Tutorial を参照してください。また、Stack Overflowで多くの例と説明を検索してください。仕様は JSR 31 です。

Java.timeオブジェクトをデータベースと直接交換できます。 JDBC 4.2 以降に準拠する JDBCドライバー を使用します。文字列もJava.sql.*クラスも必要ありません。

Java.timeクラスはどこで入手できますか?

  • Java SE 8Java SE 9 、およびそれ以降
    • ビルトイン。
    • 実装がバンドルされた標準Java AP​​Iの一部。
    • Java 9は、いくつかのマイナーな機能と修正を追加します。
  • Java SE 6 および Java SE 7
    • Java.time機能の多くは、 ThreeTen-Backport のJava 6および7にバックポートされています。
  • Android
    • Androidの後のバージョンは、Java.timeクラスの実装をバンドルします。
    • 以前のAndroid(<26)の場合、 ThreeTenABP プロジェクトはThreeTen-Backport(上記)に適合します。 ThreeTenABPの使用方法… を参照してください。

ThreeTen-Extra プロジェクトは、追加のクラスでJava.timeを拡張します。このプロジェクトは、Java.timeに将来追加される可能性のある証明の場です。 IntervalYearWeekYearQuartermore などの便利なクラスがあります。

16
Basil Bourque

Java.time

Java 8以降に組み込まれている Date and Time APIJava.time クラス)では、 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

Aravind's answer に基づいて ceklockのコメント で指摘された問題を修正し、dateStringに無効な文字が含まれていないことを確認するメソッドを追加しました。

ここに私がする方法があります:

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

標準ライブラリを使用した代替の厳密な解決策は、次のことを実行することです。

1)パターンを使用して厳密なSimpleDateFormatを作成します

2)書式オブジェクトを使用して、ユーザーが入力した値を解析しようとします

3)成功した​​場合は、同じ日付形式((1)から)を使用して(2)から得られた日付を再フォーマットします

4)再フォーマットされた日付を、ユーザーが入力した元の値と比較します。それらが等しい場合、入力された値はパターンに厳密に一致します。

このように、複雑な正規表現を作成する必要はありません-私の場合、日、月、年などの特定のタイプに限定されるのではなく、SimpleDateFormatのすべてのパターン構文をサポートする必要がありました。

6
Ben

Apacheのorg.Apache.commons.validator.GenericValidatorクラスを使用することをお勧めします。

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

注:strict-datePatternと完全に一致するかどうか。

4
Ziya

最も単純なのは、文字列を日付オブジェクトに変換し、それを再び文字列に変換することだと思います。両方の文字列がまだ一致する場合、指定された日付文字列は問題ありません。

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

それらの両方が文字列であると仮定すると(そうでなければ、それらはすでに有効な日付になります)、ここに1つの方法があります:

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

私が得る出力は次のとおりです。

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

ご覧のとおり、両方のケースをうまく処理します。

2
duffymo

これは私にとって非常に効果的です。ベンが上で提案したアプローチ。

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

SimpleDateFormatの使用に関する2つのコメント。

静的アクセスとして宣言される場合、スレッドセーフではないため、静的インスタンスとして宣言する必要があります。

日付の解析ごとにインスタンスをインスタンス化するよりも優れたIME。

1
Tom

日付解析の上記のメソッドはNiceです。既存のメソッドに新しいチェックを追加し、フォーマッターを使用して元の日付で変換された日付をダブルチェックするため、私は確認したようにほとんどすべてのケースで動作します。例えば02/29/2013は無効な日付です。指定された関数は、現在受け入れ可能な日付形式に従って日付を解析します。日付が正常に解析されない場合、trueを返します。

 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

SimpleDateFormatは、setLenient(false);の後でもパターンを厳密にチェックしません。 メソッドが適用されているため、以下のメソッドを使用して、入力された日付が有効な日付であるか、指定されたパターンに従っていないかを検証しました。

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

日付形式を確認します。

 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

外部ライブラリを使用せずにNode環境で行ったことは次のとおりです。

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

そして、それを使用する方法は次のとおりです。

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

厳密な検証が必要な場合は、setLenientをfalseに設定します

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