web-dev-qa-db-ja.com

DateTimeFormatter月パターン文字「L」が失敗する

_Java.time.format.DateTimeFormatter_ が期待どおりに解析できないことに気づきました。下記参照:

_import Java.time.LocalDate;
import Java.time.format.DateTimeFormatter;

public class Play {
  public static void tryParse(String d,String f) {
    try { 
      LocalDate.parse(d, DateTimeFormatter.ofPattern(f)); 
      System.out.println("Pass");
    } catch (Exception x) {System.out.println("Fail");}
  }
  public static void main(String[] args) {
    tryParse("26-may-2015","dd-L-yyyy");
    tryParse("26-May-2015","dd-L-yyyy");
    tryParse("26-may-2015","dd-LLL-yyyy");
    tryParse("26-May-2015","dd-LLL-yyyy");
    tryParse("26-may-2015","dd-M-yyyy");
    tryParse("26-May-2015","dd-M-yyyy");
    tryParse("26-may-2015","dd-MMM-yyyy");
    tryParse("26-May-2015","dd-MMM-yyyy");
  }
}
_

tryParse("26-May-2015","dd-MMM-yyyy");を使用した最後の試行のみが「成功」します。ドキュメントのとおり、LLLはテキスト形式を解析できるはずです。大文字の「M」と小文字の「m」の微妙な違いにも注意してください。

デフォルトではOracle DBによってフォーマットされた文字列をデフォルトで解析できないため、これは本当に煩わしいです

_SELECT TO_DATE(SYSDATE,'DD-MON-YYYY') AS dt FROM DUAL;
_

同様に、次のプログラムの場合:

_import Java.time.LocalDate;
import Java.time.format.DateTimeFormatter;

public class Play {
  public static void output(String f) {
    LocalDate d = LocalDate.now();
    Locale l = Locale.US;
    // Locale l = Locale.forLanguageTag("ru");
    System.out.println(d.format(DateTimeFormatter.ofPattern(f,l)));
  }
  public static void main(String[] args) {
    output("dd-L-yyyy");
    output("dd-LLL-yyyy");
    output("dd-M-yyyy");
    output("dd-MMM-yyyy");
  }
}
_

私は以下の出力を得ます:

_28-5-2015
28-5-2015
28-5-2015
28-May-2015
_

明らかにL書式指定子はテキストを何も扱わず、私には数値のように見えます...

ただし、ロケールをLocale.forLanguageTag("ru")に変更すると、次の出力が得られます。

_28-5-2015
28-Май-2015
28-5-2015
28-мая-2015
_

本当に面白いですよね。

私が持っている質問は:

  • それぞれが機能することを期待するのは妥当ですか?
  • 少なくともこれらのいくつかをバグとして提出する必要がありますか?
  • Lパターン指定子の使い方を誤解していますか?.

私が「重要」だと思ったドキュメントの一部を引用します:

Text:テキストスタイルは、使用されるパターン文字の数に基づいて決定されます。パターン文字が4文字未満の場合は、短い形式を使用します。正確に4つのパターン文字は完全な形式を使用します。正確に5つのパターン文字が狭いフォームを使用します。 パターン文字「L」、「c」、および「q」は、スタンドアロンスタイルのテキストスタイルを指定します。

Number:文字数が1の場合、値は最小桁数でパディングなしで出力されます。それ以外の場合、桁数は出力フィールドの幅として使用され、必要に応じて値にゼロが埋め込まれます。次のパターン文字は文字数に制約があります。 「c」と「F」の1文字のみ指定できます。 「d」「H」「h」「K」「k」「m」「s」の2文字まで指定できます。 「D」は3文字まで指定できます。

数値/テキスト:パターン文字の数が3以上の場合、上記のテキストルールを使用します 。それ以外の場合は、上記の数値ルールを使用します。

[〜#〜]更新[〜#〜]

Oracleに2つの提出を行いました。

  • LLL(ロングフォームテキスト)問題のバグ修正のリクエスト: JDK-81148 (元のOracleレビューID:JI-9021661)
  • 小文字の月の解析の問題に対する機能強化のリクエスト:レビューID:0(これもバグですか??)
25
YoYo

「スタンドアロン」の月名

「L」は、日付で使用されている方法とは異なり、月自体に別のWordを使用する言語を意味していると思います。例えば:

Locale russian = Locale.forLanguageTag("ru");

asList("MMMM", "LLLL").forEach(ptrn -> 
    System.out.println(ptrn + ": " + ofPattern(ptrn, russian).format(Month.MARCH))
);

出力:

MMMM: марта
LLLL: Март

日付の解析時に「M」の代わりに「L」を使用する理由はありません。

次のことを試して、スタンドアロンの月の名前のフォーマットをサポートしているロケールを確認しました。

Arrays.stream(Locale.getAvailableLocales())
    .collect(partitioningBy(
                loc -> "3".equals(Month.MARCH.getDisplayName(FULL_STANDALONE, loc)),
                mapping(Locale::getDisplayLanguage, toCollection(TreeSet::new))
    )).entrySet().forEach(System.out::println);

以下の言語は、「LLLL」からロケール固有のスタンドアロンの月名を取得します。

カタロニア語、中国語、クロアチア語、チェコ語、フィンランド語、ギリシャ語、ハンガリー語、イタリア語、リトアニア語、ノルウェー語、ポーランド語、ルーマニア語、ロシア語、スロバキア語、トルコ語、ウクライナ語

他のすべての言語は、3月のスタンドアロン名として「3」を取得します。

18
Misha

Javadocsによると:

パターン文字「L」、「c」、および「q」は、スタンドアロンスタイルのテキストスタイルを指定します。

しかし、「スタンドアロン」のフォームがどうあるべきかについては、あまりわかりませんでした。コードを見ると、 'L'を使用するとTextStyle.SHORT_STANDALONEが選択され、そのjavadocに従っていることがわかります。

スタンドアロンで使用するための短いテキスト。通常は省略形。たとえば、曜日の月曜日は "Mon"を出力します。

しかし、それはそれが機能するように見える方法ではありません。 3文字でも、このコードから数値出力が得られます。

DateTimeFormatter pattern = DateTimeFormatter.ofPattern ("dd-LLL-yyyy");
System.out.println (pattern.format (LocalDate.now ()));

編集

さらに調査した結果、これらのコードの「スタンドアロン」バージョンは、おそらくDateTimeFormatterBuilderを使用して、ロケールに依存しない独自のデータをロードしたい場合のものであるように思われます。そのため、デフォルトでは、DateTimeFormatterにはTextStyle.SHORT_STANDALONEのエントリが読み込まれていません。

6
neuronaut

他の回答はパターン文字Lと日付の解析に関する優れた情報を提供しますが、問題を完全に回避する必要があることを付け加えておきます。データベースから日付(および時刻)を文字列として取得しないでください。代わりに、適切な日時オブジェクトを使用してください。

    String sql = "select sysdate as dt from dual;"
    PreparedStatement stmt = yourDatabaseConnection.prepareStatement(sql);
    ResultSet rs = stmt.executeQuery();
    if (rs.next()) {
        LocalDateTime dateTime = rs.getObject("dt", LocalDateTime.class);
        // do something with dateTime
    }

(Oracleデータベースが手元にないため、テストされていません。タイプミスはご容赦ください。)

1
Ole V.V.