2016-11-11 11 views
4

私は最近2年間でSimpleDateFormatを正常に使用しています。私はそれを使用して、時間の束を構築しました。SimpleDateFormatからDateTimeFormatterに移動するときの問題

SimpleDateFormat(SDF)のスレッドセーフではない問題に遭遇したので、私は最後にこのユーティリティクラスをリファクタリングしてDateTimeFormatter(DTF)を内部的に使用しました。両方のクラスの時間パターンはほぼ同じなので、この移行は当時は良い考えでした。

EpochMillis(ミリ秒:1970-01-01T00:00:00Zからのミリ秒)の取得で問題が発生しました。 10:30HH:mmとして解釈し、1970-01-01T10:30:00Zと解釈すると、DTFは同じことをしません。 DTFはを使用してLocalTimeを解析できますが、EpochMillisを取得するために必要なZonedDateTimeは解析できません。

私はjava.timeのオブジェクトが異なる考え方に従っていることを理解しています。 Date,Time、およびZonedのオブジェクトは別々に保管されます。しかし、私のユーティリティクラスがこれまでと同じようにすべての文字列を解釈するためには、欠落しているすべてのオブジェクトのデフォルトの解析を動的に定義できる必要があります。私は使用しようとしました

DateTimeFormatterBuilder builder = new DateTimeFormatterBuilder(); 
builder.parseDefaulting(ChronoField.YEAR, 1970); 
builder.parseDefaulting(ChronoField.MONTH_OF_YEAR, 1); 
builder.parseDefaulting(ChronoField.DAY_OF_MONTH, 1); 
builder.parseDefaulting(ChronoField.HOUR_OF_DAY, 0); 
builder.parseDefaulting(ChronoField.MINUTE_OF_HOUR, 0); 
builder.parseDefaulting(ChronoField.SECOND_OF_MINUTE, 0); 
builder.append(DateTimeFormatter.ofPattern(pattern)); 

しかし、これはすべてのパターンでは機能しません。 patternで定義されていないパラメータのデフォルトのみを許可しているようです。 ChronoFieldpatternで定義されているかどうかをテストする方法はありますか?

代わりに、私はどちらかのすべてのケースをカバーしていない

TemporalAccessor temporal = formatter.parseBest(time, 
     ZonedDateTime::from, 
     LocalDateTime::from, 
     LocalDate::from, 
     LocalTime::from, 
     YearMonth::from, 
     Year::from, 
     Month::from); 
if (temporal instanceof ZonedDateTime) 
    return (ZonedDateTime)temporal; 
if (temporal instanceof LocalDateTime) 
    return ((LocalDateTime)temporal).atZone(formatter.getZone()); 
if (temporal instanceof LocalDate) 
    return ((LocalDate)temporal).atStartOfDay().atZone(formatter.getZone()); 
if (temporal instanceof LocalTime) 
    return ((LocalTime)temporal).atDate(LocalDate.of(1970, 1, 1)).atZone(formatter.getZone()); 
if (temporal instanceof YearMonth) 
    return ((YearMonth)temporal).atDay(1).atStartOfDay().atZone(formatter.getZone()); 
if (temporal instanceof Year) 
    return ((Year)temporal).atMonth(1).atDay(1).atStartOfDay().atZone(formatter.getZone()); 
if (temporal instanceof Month) 
    return Year.of(1970).atMonth((Month)temporal).atDay(1).atStartOfDay().atZone(formatter.getZone()); 

を試してみました。

動的な日付/時刻/日付/時刻/ゾーンの日付/時刻解析を有効にするための最良の方法を教えてください。

+0

問題がスレッドセーフであった場合、なぜソリューションは 'synchronized'ブロックの形式へのアクセスを保護していませんでしたか? – BadZen

+0

はい、元の問題は本当にスレッドセーフであっただけです。しかし、私は時間を浪費していました。実際には動作させるのが好きです。一般的な解析のデフォルト設定は 'java.time'では不可能だとは思いません。 – dotwin

+0

現時点では、この時点で沈んでいるようですが、正直なところ...現地時間にゾーンを適用してから、エポックからミリ秒を測定することができます: 'ZonedDateTime zdt = local.atZone(ZoneId.of(" America/New_York "));'しかし、複雑さを追加するようなことはないようです。 – BadZen

答えて

3

のJava-8-ソリューション:

変更不履行命令は、すべてのパターン命令の後に起こるようにビルダー中にあなた解析命令の順序。

private static final DateTimeFormatter FLEXIBLE_FORMATTER; 

static { 
    DateTimeFormatterBuilder builder = new DateTimeFormatterBuilder(); 
    builder.appendPattern("MM/dd"); 
    builder.parseDefaulting(ChronoField.YEAR_OF_ERA, 1970); 
    builder.parseDefaulting(ChronoField.MONTH_OF_YEAR, 1); 
    builder.parseDefaulting(ChronoField.DAY_OF_MONTH, 1); 
    builder.parseDefaulting(ChronoField.HOUR_OF_DAY, 0); 
    builder.parseDefaulting(ChronoField.MINUTE_OF_HOUR, 0); 
    builder.parseDefaulting(ChronoField.SECOND_OF_MINUTE, 0); 
    FLEXIBLE_FORMATTER = builder.toFormatter(); 
} 

理由:この静的コード(ウェル、あなたのアプローチは全くパフォーマンス異なるパターンではなく、インスタンス・ベースの組合せを使用する)を使用して、例えば

方法parseDefaulting(...)面白いやり方で、つまり埋め込みパーサのように動作します。つまり、このフィールドがまだ解析されていない場合、このメソッドは定義されたフィールドのデフォルト値を挿入します。また、後のパターン命令は、同じフィールド(ここではパターン "MM/dd"のMONTH_OF_YEARと "07/13"を入力)を解析しようとしますが、値が異なる可能性があります。そうであれば、コンポーザーは同じフィールドの相反する値を検出し、コンフリクト(解析された値7、デフォルト値1)を解決することができないため、中断します。

official APIは、以下の注意事項が含まれています:解析中に

、パースの現在の状態を検査します。 指定されたフィールドに関連付けられた値がない場合、その時点で が正常に解析されていないため、指定された値は解析結果に です。注入は即時であるため、 フィールド値ペアは、 フォーマッタの後続の要素から見えます。そのため、このメソッドは通常、 ビルダーの最後に呼び出されます。

我々はとしてそれを読む必要があります。同じフィールドのいずれかの構文解析命令の前に

Dont'tコールparseDefaulting(...)

サイド注1:

  • それが不足している分のみ不足している年(?MONTHDAY)などとの組み合わせの全てをカバーしていないので、が

    parseBest(...)に基づいてあなたの別のアプローチがさらに悪くなりますデフォルト値の解はより柔軟です。

  • パフォーマンス面で議論する価値はありません。

サイド注2:

この詳細は、多くのユーザーのためのトラップのようなものですので、私はむしろ全体の実装順序と小文字を区別しない作られているだろう。そして、私の自身のタイムライブラリTime4Jで行われたように、デフォルト値のマップベースの実装を選択することで、このトラップを回避することは可能です。デフォルト値の注入はすべてのフィールドが解析されました。 Time4Jは、「動的な日付/時刻/日付/時刻のゾーン解析を可能にする最良の戦略は何ですか?」という専用の答えを提供しています。 MultiFormatParserを入力してください。

UPDATE:使用ChronoField.YEAR_OF_ERA代わりのパターンが文字 "Y"(=年の時代、予期的グレゴリオ暦年間と同じではないが)含まれているためChronoField.YEAR:Javaの-8で

。それ以外の場合は、解析エンジンは解析された時代に加えて誤ったデフォルトの年を挿入し、矛盾を見つけます。本当の落とし穴。ちょうど昨日私はわずかに異なる2つのバリエーションに存在する月のフィールドのための私の時間ライブラリのsimilar pitfallを修正しました。

+0

素晴らしい、完全な答え。 +1とありがとう。 – dotwin

+0

私はちょうど多くのタイムストリングをテストし、まだ動作しないいくつかのパターンを見つけました。たとえば、私はあなたとまったく同じデフォルトを実装しましたが、 "yyyy.MM"として解析された "2015.11"は、スレッド "main"で例外をスローします。java.time.format.DateTimeParseException:テキスト '2015.11'を解析できませんでした: 1970年は2015年と異なります。 – dotwin

+1

@dotwin YEARではなくYEAR_OF_ERAを使用して更新してください。 –

0

私は新しいjava.timeパッケージを使用していますが、それに慣れるまでに時間がかかります。しかし、学習曲線の後、私はそれが間違いなく非常に包括的で堅牢な解決策であると言わざるを得ません。 String to Dateを解析するための独自のユーティリティを書きました。私はDateに未知のフォーマットのStringを解析する機能をどのように実装したかを説明する要約記事を書いた。それは役に立つかもしれません。 Java 8 java.time package: parsing any string to date

関連する問題