web-dev-qa-db-ja.com

Java.util.DateをJava8に変換する方法Java.time.YearMonth

Java.util.DateをJava 8 Java.time.YearMonthに変換するにはどうすればよいですか?

残念ながら、以下はDateTimeExceptionをスローします。

YearMonth yearMonth = YearMonth.from(date.toInstant());

結果:

Java.time.DateTimeException: Unable to obtain YearMonth from TemporalAccessor: 2015-01-08T14:28:39.183Z of type Java.time.Instant
at Java.time.YearMonth.from(YearMonth.Java:264)
...

JPAを使用してYearMonth値をデータベースに格納したいので、この機能が必要です。現在、JPAはYearMonthをサポートしていないため、次のYearMonthConverterを作成しました(インポートは省略)。

// TODO (future): delete when next version of JPA (i.e. Java 9?) supports YearMonth. See https://Java.net/jira/browse/JPA_SPEC-63
@Converter(autoApply = true)
public class YearMonthConverter implements AttributeConverter<YearMonth, Date> {

  @Override
  public Date convertToDatabaseColumn(YearMonth attribute) {
    // uses default zone since in the end only dates are needed
    return attribute == null ? null : Date.from(attribute.atDay(1).atStartOfDay(ZoneId.systemDefault()).toInstant());
  }

  @Override
  public YearMonth convertToEntityAttribute(Date dbData) {
    // TODO: check if Date -> YearMonth can't be done in a better way
    if (dbData == null) return null;
    Calendar calendar = Calendar.getInstance();
    calendar.setTime(dbData);
    return YearMonth.of(calendar.get(Calendar.YEAR), calendar.get(Calendar.MONTH) + 1);
  }
}

より良い(よりクリーンでより短い)解決策(両方向)はありませんか?

13
Sebastian

短い答え:

_// From Date to YearMonth
YearMonth yearMonth =
        YearMonth.from(date.toInstant()
                           .atZone(ZoneId.systemDefault())
                           .toLocalDate());

// From YearMonth to Date
// The same as the OP:s answer
final Date convertedFromYearMonth = 
        Date.from(yearMonth.atDay(1).atStartOfDay(ZoneId.systemDefault()).toInstant());
_

説明:

YearMonth.from(TemporalAccessor) -メソッドのJavaDocによると:

変換により、YEARフィールドとMONTH_OF_YEARフィールドが抽出されます。抽出は、時間オブジェクトにISO年代順がある場合、またはLocalDateに変換できる場合にのみ許可されます。

したがって、次のいずれかができる必要があります。

  1. YEARおよび_MONTH_OF_YEAR_フィールドを抽出するか、または
  2. LocalDateに変換できるものを使用する必要があります。

試してみよう!

_final Date date = new Date();
final Instant instant = date.toInstant();
instant.get(ChronoField.YEAR); // causes an error
_

これは不可能であり、例外がスローされます。

Java.time.temporal.UnsupportedTemporalTypeException:サポートされていないフィールド:Java.time.Instant.get(Instant.Java:571)の年.。

これは、代替案1がウィンドウの外に出ることを意味します。その理由は、 DateをLocalDateに変換する方法 に関するこの優れた回答で説明されています。

その名前にもかかわらず、_Java.util.Date_は「日付」ではなく、タイムライン上の瞬間を表します。オブジェクト内に保存される実際のデータは、1970-01-01T00:00Z(1970 GMT/UTCの開始時の真夜中)からの長いミリ秒数です。

JSR-310の_Java.util.Date_と同等のクラスはInstantであるため、変換を提供するための便利なメソッドtoInstant()があります。

したがって、DateInstantに変換できますが、それは役に立ちませんでしたね。

ただし、代替案2は成功することが証明されています。 InstantLocalDateに変換してから、YearMonth.from(TemporalAccessor)-メソッドを使用します。

_    Date date = new Date();

    LocalDate localDate = date.toInstant()
                              .atZone(ZoneId.systemDefault())
                              .toLocalDate();

    YearMonth yearMonth = YearMonth.from(localDate);
    System.out.println("YearMonth: " + yearMonth);
_

出力は次のとおりです(コードは2015年1月に実行されたため;):

年月:2015-01

19
wassgren