LoginSignup
42

More than 5 years have passed since last update.

[Java8 バグ] DateTimeFormatterで"yyyyMMddHHmmssSSS"がparseできない。

Last updated at Posted at 2016-04-05

簡単な日時のフォーマット変換ユーティリティを書こうとした時に、
Java8のバグに出会ってハマったので記事にします。

※この記事内容は、ほぼ下記リンク先のstackoverflowからの転載です。
 自分はこのバグで結構ハマったので、
 Qiitaに記事があればもっと早く原因がわかったのでは...という思いで書いています。
 http://stackoverflow.com/questions/22588051/is-java-time-failing-to-parse-fraction-of-second

実行環境

この記事内のコードを実行した時の、私のPC環境です。

  • OS:Windows7 64bit
  • Javaのバージョン:Java SE 8u77

DateTimeFormatter

DateTimeFormatterはJava8で導入されたAPIです。
詳しくは下記参照。

やろうとしたこと・起きたこと

下記のように、yyyyMMddHHmmss形式の日時文字列(ミリ秒なし)をLocalDateTimeにparseするのは問題無かった。

        String input = "20111203123456";
        DateTimeFormatter dtf = DateTimeFormatter.ofPattern( "yyyyMMddHHmmss");
        LocalDateTime localDateTime = LocalDateTime.parse(input, dtf);
        System.out.println(localDateTime);//output: 2011-12-03T12:34:56

しかし、同じノリでyyyyMMddHHmmssSSS形式の日時文字列(ミリ秒含む)をparseしようとしたところで問題発生。

        String input = "20111203123456789";
        DateTimeFormatter dtf = DateTimeFormatter.ofPattern( "yyyyMMddHHmmssSSS");
        LocalDateTime localDateTime = LocalDateTime.parse(input, dtf);//Error!
        System.out.println(localDateTime);

実行時に下記のエラーが...!?

Exception in thread "main" java.time.format.DateTimeParseException: Text '20111203123456789' could not be parsed at index 0
    at java.time.format.DateTimeFormatter.parseResolved0(DateTimeFormatter.java:1949)
    at java.time.format.DateTimeFormatter.parse(DateTimeFormatter.java:1851)
    at java.time.LocalDateTime.parse(LocalDateTime.java:492)
    at Main.main(Main.java:11)
    at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
    at java.lang.reflect.Method.invoke(Method.java:498)
    at com.intellij.rt.execution.application.AppMain.main(AppMain.java:144)

えぇ...
なんで...??
ミリ秒のパターンの指定の仕方がおかしいのか!?とか悩み、APIドキュメント見ながら試行錯誤しましたが一向に解決せず...

原因

途方にくれながらググッていたら、前述のstackoverflowに辿りつきました。

JDKのバグが原因でした。
下記、JDK Bug Systemにて既に報告されていました。
https://bugs.openjdk.java.net/browse/JDK-8031085

"yyyyMMddHHmmssSSS"のパターンはparseできないそうです。
なんじゃそりゃ~

他にも、"yyyyMMddHHmmssSS"とか"yyyyMMddHHmmssS"もダメだそうで...
逆に、"yyyyMMddHHmmss.SSS"等、
ミリ秒の直前にパターン文字以外の文字やら記号が入っていれば問題なく動きます。

このバグはいつ直るの?

stackoverflowには、
このバグの修正は少なくともJava9以降になりそうだと書いてありますね...
そういうもんなんでしょうか...

【2017年5月29日追記】
JDK Bug Systemでのstatusが、Resolvedになっていました。
ただし、やはり修正の反映はJava 9になるようです。

対策

バグが直るまでの暫定対応として、前述のstackoverflowに投稿されている内容をまとめています。
ミリ秒部(S)が3桁の場合と、1桁或いは2桁の場合で、対応が異なります。
しかし3桁の場合は良いのですが、1、2桁の場合は何か他に良い対策はないものだろうか。
強引な実装はもちろん、仕事で実装する場合はサードパーティ製のライブラリとか可能なら避けたいですよね...

ミリ秒部が3桁の場合

フォーマッタの定義を工夫する

    String input = "20111203123456789";
    DateTimeFormatter dtf =
            new DateTimeFormatterBuilder()
                    .appendPattern("yyyyMMddHHmmss")
                    .appendValue(ChronoField.MILLI_OF_SECOND, 3)
                    .toFormatter();
    LocalDateTime localDateTime = LocalDateTime.parse(input, dtf);
    System.out.println(localDateTime); //output: 2011-12-03T12:34:56.789

ミリ秒部が1桁または2桁の場合

3桁の時と同じノリで2桁も行けるかと思えば...

    String input = "2011120312345678";
    DateTimeFormatter dtf =
            new DateTimeFormatterBuilder()
                    .appendPattern("yyyyMMddHHmmss")
                    .appendValue(ChronoField.MILLI_OF_SECOND, 2)
                    .toFormatter();
    LocalDateTime localDateTime = LocalDateTime.parse(input, dtf);
    System.out.println(localDateTime); //output: 2011-12-03T12:34:56.078

エラーにはなりませんが、出力結果をよく見るとミリ秒部分が1桁ずれてます...

//expected  2011-12-03T12:34:56.780
//actual    2011-12-03T12:34:56.078
                                ^^^ 

これではダメだ。
ちなみに、SimpleDateFormatを使用しても、ミリ秒が2桁の場合は同じように桁がずれる。

        String input = "2011120312345678";
        SimpleDateFormat sdf = new SimpleDateFormat("yyyyMMddHHmmssSS");
        Date d = sdf.parse(input);
        System.out.println(d.toInstant()); // 2011-12-03T03:34:56.078Z

ミリ秒部分が1桁または2桁の場合は、下記の対策のどちらかで対応する必要がある。

1. 強引に桁をずらす。

ミリ秒が2桁の場合の実装例)


String input = "2011120312345678"; 
DateTimeFormatter dtf = DateTimeFormatter.ofPattern("yyyyMMddHHmmss");
int len = input.length();
LocalDateTime ldt = LocalDateTime.parse(input.substring(0, len - 2),  dtf);
int millis = Integer.parseInt(input.substring(len - 2)) * 10;
ldt = ldt.plus(millis, ChronoUnit.MILLIS);
System.out.println(ldt); //output: 2011-12-03T12:34:56.780

2. サードパーティ製のライブラリを使用する。

String input = "2011120312345678"; 
DateTimeFormatter dtf = DateTimeFormat.forPattern("yyyyMMddHHmmssSS");
System.out.println(dtf.parseLocalDateTime(input)); //output: 2011-12-03T12:34:56.780

String input = "2011120312345678"; 
ChronoFormatter<PlainTimestamp> f = 
  ChronoFormatter.ofTimestampPattern("yyyyMMddHHmmssSS", PatternType.CLDR, Locale.ROOT);
System.out.println(f.parse(input)); //output: 2011-12-03T12:34:56.780

うーん微妙ですよね・・・

【2017年5月29日追記】
↓とてもシンプルに解決されている方がいました。


Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
42