Java 日付文字列をTimestamp型へ、の前に文字列のフォーマットチェックが...

まぁ、何ていうか、データベースへ登録するときは、String型で来たデータをTimestamp型に変換せねばならないこと、あるあるということで、その文字列のチェックってどうしたら良いの?というか不正なフォーマットだと上手くいかないと。

public static Timestamp valueOf(String s)
JDBCタイムスタンプ・エスケープ形式のStringオブジェクトをTimestamp値に変換します。
パラメータ:
s - yyyy-[m]m-[d]d hh:mm:ss[.f...]形式のタイムスタンプ。小数点以下の秒数は省略されることがある。mmddの先頭のゼロは省略できる。
戻り値:
対応するTimestamp
例外:
IllegalArgumentException - 指定された引数がyyyy-[m]m-[d]d hh:mm:ss[.f...]形式でない場合

Timestamp (Java Platform SE 8)

docs.oracle.com

 

というわけで、 String型をTimestamp型に変換する前のチェケラーを模索していこうという。そんでは、レッツ~トライ。

 

String型のTimestamp型への変換は、至難の業

そもそも、String型のデータが日付のフォーマットになっているかなんて、どうすりゃ分かるんだい?という 情弱な、どうもボクです。

fantastic-works.com

⇧  上記サイト様 によりますと、

  • 正規表現で日付形式をチェック
  • 各フィールドに分けてそれぞれを数値チェックし、問題なければ連結して妥当性チェック
  • SimpleDateFormatを使って日付型に変換できるかチェック
  • Java8が使える環境ならDateTimeFormatter

 のいずれかの実装に行き着くのではないかと。

んで、Javaの標準APIのSimpleDateFormat がなかなかに厄介なものですと。

d.hatena.ne.jp

⇧  上記サイト様によりますと、SimpleDateFormat が、かなり残念な仕様になっている模様...む、無念過ぎる。

 

というわけで、Eclipseを起動し、Javaプロジェクトを適当に作成し、クラスも作成で。

f:id:ts0818:20190208225615p:plain

そんで、ソースはこんな感じで。

import java.text.DateFormat;
import java.text.ParseException;
import java.text.SimpleDateFormat;

public class TimeFormat {

  public static void main(String[] args) {
    //
    try {
      DateFormat df = new SimpleDateFormat("yyyyMMddhhmmss"); // 日付のフォーマットを指定
      df.setLenient(false); // false:厳密な解析
      String input = "20190210123021";
      String check = df.format(df.parse(input));
      
      // 日付のフォーマットになっている場合
      if (input != null && input.equals(check)) {
        System.out.println("正常なフォーマット");
        System.out.println("入力:" + input);
        System.out.println("変換:" + check);
        
      // 不正なフォーマットの場合
      } else {
        System.out.println("無効なフォーマット");
        System.out.println("入力:" + input);
        System.out.println("変換:" + check);

      }
    } catch (ParseException e) {
      // TODO 自動生成された catch ブロック
      e.printStackTrace();
    }

  }

}

そんでは、実行で。

f:id:ts0818:20190208230149p:plain

入力値が正しいフォーマットの場合

f:id:ts0818:20190208230251p:plain

不正なフォーマットの入力値の場合

f:id:ts0818:20190208230439p:plain

で、ちゃんとチェックできると思いきや...

最後の文字以外に、期待しない値が入ると、例外になっちゃうんですよね...

f:id:ts0818:20190208230941p:plain

そもそも、フォーマットが正しくない場合に例外にするしかないって、フォーマットチェックの意味ないやないの...例外にするんでなくて別の処理に遷移したいというのに...

takuya-1st.hatenablog.jp

⇧  上記サイト様も仰るように、絶望的...

というか、日付のフォーマットかどうかの判定結果をboolean値で返せ~というのは期待し過ぎなのかしら...
 

そんなこんなで、例外を発生させないためには、 

docs.oracle.com

⇧   java.text.ParsePosition ってAPIを使えば良いらしい。

というわけで、こんな感じに。

import java.sql.Timestamp;
import java.text.ParsePosition;
import java.text.SimpleDateFormat;
import java.util.Date;

public class TimeFormat {

  public static void main(String[] args) {
    //
    String format = "yyyyMMddhhmmssSSS"; // 入力されるフォーマットに合わせる
    String input = "20190210123045000";  // 入力される日付日時
    // データベースに保存できるよう、String型をTimestamp型へ変換
    // データベースのカラムがTimestamp型を想定
    Timestamp timestamp = canFormatDate(input, format);

    if (timestamp != null) {
      System.out.println("正常なフォーマット");
      System.out.println("入力:" + input);
      System.out.println("変換:" + timestamp);

    } else {
      System.out.println("無効なフォーマット");
      System.out.println("入力:" + input);
    }
  }

  // フォーマットの正しい日付文字列の場合に変換
  public static Timestamp canFormatDate(String date, String format) {

    Date parseDate = null;
    Timestamp timestamp = null;
    SimpleDateFormat sdf = new SimpleDateFormat(format);
    sdf.setLenient(false); // false:厳密な解析

    ParsePosition pos = new ParsePosition(0);  // ParseException を発生させない
    // 正しい日付文字列でパースできた場合のみ処理
    if ((parseDate = sdf.parse(date, pos)) != null && (pos.getIndex() == format.length())) {
      // Timestamp の形式に合わせる
      sdf.applyPattern("yyyy-MM-dd hh:mm:ss");
      // System.out.println(sdf.format(parseDate));
      timestamp = Timestamp.valueOf(sdf.format(parseDate));

    }
    return timestamp;
  }
}

んで、実行してみると、フォーマットがおかしい日付文字列に関しては、

f:id:ts0818:20190210202856p:plain

⇧  指定したフォーマットで最後までパースを行えない入力値であった場合は、変換処理を行わずに処理を終了。

f:id:ts0818:20190210213028p:plain

⇧  指定したフォーマットで最後までパースできる入力値だった場合、変換処理まで行われました。

teratail.com

⇧  上記サイト様でも仰られているように、Timestamp.valueOf("文字列の日付") は、フォーマットが『yyyy-mm-dd hh:mm:ss』に限定されるようです。

stealthinu.hatenadiary.jp

⇧  上記サイト様によりますと、桁数とかの問題もあるようです。

 

まぁ、何ていうか、例外を発生させないでの日付文字列のフォーマットチェックは、事前に入力値のフォーマットが分かってないと使えないっぽいですかね...

Javaの場合の日付文字列のフォーマットチェックのベストプラクティスってどうすれば良いんでしょうかね...アノテーションとかでできるのかしら?

Kotlin とかはアノテーション使わない文化になってるらしいと聞いた気がするので、Kotlin なんかのソースコードを見ればベストプラクティスに辿り着けるのかしら?

Kotlin も勉強せねばですかね...

果てしないモヤモヤが残ったところで、今回はこのへんで。