web-dev-qa-db-ja.com

デフォルトのゾーンでZonedDateTimeを解析する方法は?

ZoneDateTimeおよび他のフィールドを含まない文字列からzoneを解析する方法は?

これは再現するためのSpockでのテストです:

import spock.lang.Specification
import spock.lang.Unroll

import Java.time.ZoneId
import Java.time.ZoneOffset
import Java.time.ZonedDateTime
import Java.time.format.DateTimeFormatter

@Unroll
class ZonedDateTimeParsingSpec extends Specification {
    def "DateTimeFormatter.ISO_ZONED_DATE_TIME parsing incomplete date: #value #expected"() {
        expect:
        ZonedDateTime.parse(value, DateTimeFormatter.ISO_ZONED_DATE_TIME) == expected
        where:
        value                           | expected
        '2014-04-23T04:30:45.123Z'      | ZonedDateTime.of(2014, 4, 23, 4, 30, 45, 123_000_000, ZoneOffset.UTC)
        '2014-04-23T04:30:45.123+01:00' | ZonedDateTime.of(2014, 4, 23, 4, 30, 45, 123_000_000, ZoneOffset.ofHours(1))
        '2014-04-23T04:30:45.123'       | ZonedDateTime.of(2014, 4, 23, 4, 30, 45, 123_000_000, ZoneId.systemDefault())
        '2014-04-23T04:30'              | ZonedDateTime.of(2014, 4, 23, 4, 30, 0, 0, ZoneId.systemDefault())
        '2014-04-23'                    | ZonedDateTime.of(2014, 4, 23, 0, 0, 0, 0, ZoneId.systemDefault())
    }
}

最初の2つのテストは成功しましたが、他のすべてのテストはDateTimeParseExceptionで失敗しました:

  • 「2014-04-23T04:30:45.123」はインデックス23で解析できませんでした
  • 「2014-04-23T04:30」はインデックス16で解析できませんでした
  • 「2014-04-23」はインデックス10で解析できませんでした

時間とゾーンをデフォルトに設定して不完全な日付を解析するにはどうすればよいですか?

14

_ISO_ZONED_DATE_TIME_フォーマッタはゾーンまたはオフセット情報を予期しているため、解析は失敗します。ゾーン情報と時間部分の両方にオプションの部分があるDateTimeFormatterを作成する必要があります。 ZonedDateTimeFormatterをリバースエンジニアリングしてオプションのタグを追加するのはそれほど難しくありません。

次に、フォーマッタのparseBest()メソッドを使用してStringを解析します。次に、最適とは言えない解析結果に対して、必要なデフォルトを使用してZonedDateTimeを作成できます。

_DateTimeFormatter formatter = new DateTimeFormatterBuilder()
        .parseCaseInsensitive()
        .append(ISO_LOCAL_DATE)
        .optionalStart()           // time made optional
        .appendLiteral('T')
        .append(ISO_LOCAL_TIME)
        .optionalStart()           // zone and offset made optional
        .appendOffsetId()
        .optionalStart()
        .appendLiteral('[')
        .parseCaseSensitive()
        .appendZoneRegionId()
        .appendLiteral(']')
        .optionalEnd()
        .optionalEnd()
        .optionalEnd()
        .toFormatter();

TemporalAccessor temporalAccessor = formatter.parseBest(value, ZonedDateTime::from, LocalDateTime::from, LocalDate::from);
if (temporalAccessor instanceof ZonedDateTime) {
    return ((ZonedDateTime) temporalAccessor);
}
if (temporalAccessor instanceof LocalDateTime) {
    return ((LocalDateTime) temporalAccessor).atZone(ZoneId.systemDefault());
}
return ((LocalDate) temporalAccessor).atStartOfDay(ZoneId.systemDefault());
_
15
bowmore

フォーマッタにはwithZone()method があり、不足しているタイムゾーンを提供するために呼び出すことができます。

ZonedDateTime.parse(
    value,
    DateTimeFormatter.ISO_ZONED_DATE_TIME.withZone(ZoneId.systemDefault()))

バグがあったことを覚えておいてください。バグが完全に機能するには8u20以降が必要です。

14
JodaStephen