web-dev-qa-db-ja.com

Java 8 Time API-ZonedDateTime-解析時にデフォルトのZoneIdを指定する

ZonedDateTimeとしての日付とその形式を指定すると、Stringを返す汎用メソッドを記述しようとしています。

日付にZonedDateTimeが指定されていない場合、デフォルトのZoneIdを使用するためにStringをどのように作成しますか?

Java.util.Calendar、しかしJava 8タイムAPIを使用したい。

この質問はこちら は固定タイムゾーンを使用しています。引数としてフォーマットを指定しています。日付とその形式はどちらもString引数です。より一般的です。

以下のコードと出力:

public class DateUtil {
    /** Convert a given String to ZonedDateTime. Use default Zone in string does not have zone.  */
    public ZonedDateTime parseToZonedDateTime(String date, String dateFormat) {
        //use Java.time from Java 8
        DateTimeFormatter formatter = DateTimeFormatter.ofPattern(dateFormat);
        ZonedDateTime zonedDateTime = ZonedDateTime.parse(date, formatter);
        return zonedDateTime;
    }

    public static void main(String args[]) {
        DateUtil dateUtil = new DateUtil();
        System.out.println(dateUtil.parseToZonedDateTime("2017-09-14 15:00:00+0530", "yyyy-MM-dd HH:mm:ssZ"));
        System.out.println(dateUtil.parseToZonedDateTime("2017-09-14 15:00:00", "yyyy-MM-dd HH:mm:ss"));
    }
}

出力

2017-09-14T15:00+05:30
Exception in thread "main" Java.time.format.DateTimeParseException: Text '2017-09-14 15:00:00' could not be parsed: Unable to obtain ZonedDateTime from TemporalAccessor: {},ISO resolved to 2017-09-14T15:00 of type Java.time.format.Parsed
    at Java.time.format.DateTimeFormatter.createError(DateTimeFormatter.Java:1920)
    at Java.time.format.DateTimeFormatter.parse(DateTimeFormatter.Java:1855)
    at Java.time.ZonedDateTime.parse(ZonedDateTime.Java:597)
    at com.nam.sfmerchstorefhs.util.DateUtil.parseToZonedDateTime(DateUtil.Java:81)
    at com.nam.sfmerchstorefhs.util.DateUtil.main(DateUtil.Java:97)
Caused by: Java.time.DateTimeException: Unable to obtain ZonedDateTime from TemporalAccessor: {},ISO resolved to 2017-09-14T15:00 of type Java.time.format.Parsed
    at Java.time.ZonedDateTime.from(ZonedDateTime.Java:565)
    at Java.time.format.Parsed.query(Parsed.Java:226)
    at Java.time.format.DateTimeFormatter.parse(DateTimeFormatter.Java:1851)
    ... 3 more
Caused by: Java.time.DateTimeException: Unable to obtain ZoneId from TemporalAccessor: {},ISO resolved to 2017-09-14T15:00 of type Java.time.format.Parsed
    at Java.time.ZoneId.from(ZoneId.Java:466)
    at Java.time.ZonedDateTime.from(ZonedDateTime.Java:553)
    ... 5 more
8

ZonedDateTimeを構築するにはタイムゾーンまたはオフセットが必要ですが、2番目の入力にはそれがありません。 (日付と時刻のみが含まれます)。

そのため、ZonedDateTimeを構築できるかどうかを確認する必要があります。それができない場合は、任意のゾーンを選択する必要があります(入力に指示がないため使用されているタイムゾーンについては、使用するタイムゾーンを選択する必要があります)。

1つの代替方法は、最初にZonedDateTimeを作成しようとし、それが不可能な場合はLocalDateTimeを作成してタイムゾーンに変換することです。

_public ZonedDateTime parseToZonedDateTime(String date, String dateFormat) {
    // use Java.time from Java 8
    DateTimeFormatter formatter = DateTimeFormatter.ofPattern(dateFormat);
    ZonedDateTime zonedDateTime = null;
    try {
        zonedDateTime = ZonedDateTime.parse(date, formatter);
    } catch (DateTimeException e) {
        // couldn't parse to a ZoneDateTime, try LocalDateTime
        LocalDateTime dt = LocalDateTime.parse(date, formatter);

        // convert to a timezone
        zonedDateTime = dt.atZone(ZoneId.systemDefault());
    }
    return zonedDateTime;
}
_

上記のコードでは、ZoneId.systemDefault()を使用しています。これは、JVMのデフォルトのタイムゾーンを取得しますが、これは 実行時にも予告なしに変更される なので、どちらを使用するかを常に明示的にすることをお勧めします。

APIは IANAタイムゾーン名 を使用します(常に_Region/City_または_America/Sao_Paulo_のような_Europe/Berlin_の形式で)。 3文字の略語(CSTPSTなど)は あいまいで標準ではない であるため、使用しないでください。

ZoneId.getAvailableZoneIds()を呼び出すと、使用可能なタイムゾーンのリストを取得できます(システムに最適なものを選択できます)。

特定のタイムゾーンを使用したい場合は、ZoneId.of("America/New_York")の代わりにZoneId.getAvailableZoneIds()(またはZoneId.systemDefault()から返される他の有効な名前、ニューヨークは単なる例です)を使用してください。 。


別の方法は parseBest() method を使用することです。これは、必要な型が作成されるまで(TemporalQueryのリストを使用して)適切な日付オブジェクトを作成しようとします。

_public ZonedDateTime parseToZonedDateTime(String date, String dateFormat) {
    DateTimeFormatter formatter = DateTimeFormatter.ofPattern(dateFormat);

    // try to create a ZonedDateTime, if it fails, try LocalDateTime
    TemporalAccessor parsed = formatter.parseBest(date, ZonedDateTime::from, LocalDateTime::from);

    // if it's a ZonedDateTime, return it
    if (parsed instanceof ZonedDateTime) {
        return (ZonedDateTime) parsed;
    }
    if (parsed instanceof LocalDateTime) {
        // convert LocalDateTime to JVM default timezone
        LocalDateTime dt = (LocalDateTime) parsed;
        return dt.atZone(ZoneId.systemDefault());
    }

    // if it can't be parsed, return null or throw exception?
    return null;
}
_

この場合、私は_ZonedDateTime::from_と_LocalDateTime::from_を使用しただけなので、フォーマッターは最初にZonedDateTimeを作成しようとし、それが不可能な場合はLocalDateTime

次に、返されたタイプを確認し、それに応じてアクションを実行します。必要な数のタイプを追加できます(LocalDateLocalTimeOffsetDateTimeなどのすべての主要なタイプには、機能するfromメソッドがあります。 with parseBest-必要に応じて 独自のカスタムTemporalQuery を作成することもできますが、この場合は組み込みのメソッドで十分だと思います)。


夏時間

atZone()メソッドを使用してLocalDateTimeZonedDateTimeに変換する場合、 夏時間 (DST)に関していくつかのトリッキーなケースがあります。

例として(_America/Sao_Paulo_)に住んでいるタイムゾーンを使用しますが、これはDSTのどのタイムゾーンでも発生する可能性があります。

サンパウロでは、DSTは10月16日に始まりました番目 2016:午前0時に、時計が午前0時から午前1時に1時間forwardシフトしました(オフセットは_-03:00_から_-02:00_に変わります) )。したがって、00:00から00:59までのすべての現地時間はこのタイムゾーンに存在しませんでした(時計が23:59:59.999999999から直接01:00に変更されたと考えることもできます)。この間隔でローカル日付を作成すると、次の有効な瞬間に調整されます。

_ZoneId zone = ZoneId.of("America/Sao_Paulo");

// October 16th 2016 at midnight, DST started in Sao Paulo
LocalDateTime d = LocalDateTime.of(2016, 10, 16, 0, 0, 0, 0);
ZonedDateTime z = d.atZone(zone);
System.out.println(z);// adjusted to 2017-10-15T01:00-02:00[America/Sao_Paulo]
_

DSTの終了時:2月19日番目 2017年深夜、時計が戻る深夜から23に1時間PM of18番目(およびオフセットが_-02:00_から_-03:00_に変更されます)。したがって、23:00から23:59までのすべてのローカル時間が存在しましたtwice(両方のオフセット:_-03:00_および_-02:00_)、どちらを使用するかを決める必要があります。デフォルトでは、DSTが終了する前にオフセットを使用しますが、withLaterOffsetAtOverlap()メソッドを使用して、DSTが終了した後にオフセットを取得できます。

_// February 19th 2017 at midnight, DST ends in Sao Paulo
// local times from 23:00 to 23:59 at 18th exist twice
LocalDateTime d = LocalDateTime.of(2017, 2, 18, 23, 0, 0, 0);
// by default, it gets the offset before DST ends
ZonedDateTime beforeDST = d.atZone(zone);
System.out.println(beforeDST); // before DST end: 2018-02-17T23:00-02:00[America/Sao_Paulo]

// get the offset after DST ends
ZonedDateTime afterDST = beforeDST.withLaterOffsetAtOverlap();
System.out.println(afterDST); // after DST end: 2018-02-17T23:00-03:00[America/Sao_Paulo]
_

DSTの終了前と終了後の日付ではオフセットが異なることに注意してください(_-02:00_および_-03:00_)。夏時間のあるタイムゾーンを使用している場合は、これらのケースが発生する可能性があることに注意してください。

10
user7605325

Java.timeライブラリーにはデフォルトがほとんどありません。これは主に良いことです。表示されているのは取得したもの、期間です。

あなたの日付文字列にゾーンが含まれていない場合、それはLocalDateTimeであり、ZonedDateTimeにはなり得ないことをお勧めします。これは、取得している例外の意味です(たとえ過度に柔軟なコード構造のために言葉遣いが苦しみました)。

私の主な提案は、パターンにゾーン情報がないことがわかっている場合は、ローカルの日付時刻を解析することです。

ただし、本当に必要な場合は、ここにあなたが望むことを行う別の方法があります(フローを制御するために例外を使用しない代替ソリューション):

TemporalAccessor parsed = f.parse(string);
if (parsed.query(TemporalQueries.zone()) == null) {
  parsed = f.withZone(ZoneId.systemDefault()).parse(string);
}
return ZonedDateTime.from(parsed);

ここでは、中間解析結果を使用して、文字列にゾーン情報が含まれているかどうかを判断し、含まれていない場合は(同じ文字列で異なるプリンターパーサーを使用して)再度解析して、今回はゾーンが含まれるようにします。

または、このクラスを作成して、2回目の解析から節約し、shouldを使用して、他のすべてのフィールドが存在すると仮定して、ゾーン化された日付時刻を解析できます。

class TemporalWithZone implements TemporalAccessor {
  private final ZoneId zone;
  private final TemporalAccessor delegate;
  public TemporalWithZone(TemporalAccessor delegate, ZoneId zone) {
    this.delegate = requireNonNull(delegate);
    this.zone = requireNonNull(zone);
  }

  <delegate methods: isSupported(TemporalField), range(TemporalField), getLong(TemporalField)>

  public <R> R query(TemporalQuery<R> query) {
    if (query == TemporalQueries.zone() || query == TemporalQueries.zoneId()) {
      return (R) zone;
    }
    return delegate.query(query);
  }
}
3
M. Prokhorov

Java 8 ZonedDateTime実装、のように、ZonedDateTimeでゾーンなしで日付を解析することはできません。

特定の問題に対応するには、デフォルトのタイムゾーンと見なされる例外がある場合に備えて、try catchを配置する必要があります。

以下の改訂プログラムを見つけてください:

public class DateUtil {
     /** Convert a given String to ZonedDateTime. Use default Zone in string does not have zone.  */
    public ZonedDateTime parseToZonedDateTime(String date, String dateFormat) {
        //use Java.time from Java 8
        DateTimeFormatter formatter = DateTimeFormatter.ofPattern(dateFormat);
        ZonedDateTime zonedDateTime = null;
        try {
            zonedDateTime = ZonedDateTime.parse(date, formatter);
        } catch (DateTimeException e) {
            // If date doesn't contains Zone then parse with LocalDateTime 
            LocalDateTime localDateTime = LocalDateTime.parse(date, formatter);
            zonedDateTime = localDateTime.atZone(ZoneId.systemDefault());
        }
        return zonedDateTime;
    }

    public static void main(String args[]) {
        DateUtil dateUtil = new DateUtil();
        System.out.println(dateUtil.parseToZonedDateTime("2017-09-14 15:00:00+0530", "yyyy-MM-dd HH:mm:ssZ"));
        System.out.println(dateUtil.parseToZonedDateTime("2017-09-14 15:00:00", "yyyy-MM-dd HH:mm:ss"));
    }
}

今後のJava機能の詳細については、 http://www.codenuclear.com/Java-8-date-time-intro を参照してください。

3
viviek

OFFSET_SECONDがない場合は、DateTimeFormatterBuilderにデフォルト値を追加するだけです。


編集:システムのデフォルトZoneOffsetを取得するには、ZoneRulesを現在のInstantに適用する必要があります。結果は次のようになります。

class DateUtil {
  public ZonedDateTime parseToZonedDateTime(String date, String dateFormat) {
    DateTimeFormatter formatter = DateTimeFormatter.ofPattern(dateFormat);
    LocalDateTime localDateTime = LocalDateTime.parse(date, formatter);
    ZoneOffset defaultOffset =  ZoneId.systemDefault().getRules().getOffset(localDateTime);
    DateTimeFormatter dateTimeFormatter = new DateTimeFormatterBuilder()
            .append(formatter)
            .parseDefaulting(ChronoField.OFFSET_SECONDS, defaultOffset.getTotalSeconds())
            .toFormatter();
    return ZonedDateTime.parse(date, dateTimeFormatter);
  }
}

出力:

2017-09-14T15:00+05:30
2017-09-14T15:00+02:00
2
Flown

ZoneIdは、DateTimeFormatterでwithZoneメソッドを使用して指定できます。

DateTimeFormatter formatter = DateTimeFormatter.ofPattern(dateFormat).withZone("+0530");
1
RSloeserwij