如何将java.util.Date转换为Java8 java.time.YearMonth

Seb*_*ian 13 jpa java.util.date java-8

我怎样才能最好地将其转换java.util.Date为Java 8 java.time.YearMonth

不幸的是,以下抛出DateTimeException:

YearMonth yearMonth = YearMonth.from(date.toInstant());
Run Code Online (Sandbox Code Playgroud)

结果是:

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)
...
Run Code Online (Sandbox Code Playgroud)

我需要此功能,因为我想YearMonth使用JPA 将值存储在数据库中.目前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);
  }
}
Run Code Online (Sandbox Code Playgroud)

是不是有更好的(更清洁,更短)的解决方案(两个方向)?

was*_*ren 19

简短回答:

// 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());
Run Code Online (Sandbox Code Playgroud)

说明:

YearMonth.from(TemporalAccessor) -method 的JavaDoc 说:

转换将提取YEAR和MONTH_OF_YEAR字段.仅当时态对象具有ISO年表时,或者可以将其转换为LocalDate时,才允许提取.

所以,你需要能够:

  1. 提取YEARMONTH_OF_YEAR字段,或
  2. 你应该使用可以转换为的东西LocalDate.

让我们试试吧!

final Date date = new Date();
final Instant instant = date.toInstant();
instant.get(ChronoField.YEAR); // causes an error
Run Code Online (Sandbox Code Playgroud)

这是不可能的,抛出异常:

java.time.temporal.UnsupportedTemporalTypeException:不支持的字段:java.time.Instant.get的年份(Instant.java:571)...

这意味着替代方案1会离开窗口.在这个关于如何将Date转换为LocalDate的优秀答案中解释了原因.

尽管它的名字,java.util.Date代表了时间线上的瞬间,而不是"日期".存储在对象中的实际数据是自1970-01-01T00:00Z(格林威治标准时间1970 /格林威治标准时间1970年初午夜)以来的长数毫秒数.

java.util.DateJSR-310中的等价类是Instant,因此有一个方便的方法toInstant()来提供转换.

所以,一个Date可以转换成一个Instant但是没有帮助我们,做到了吗?

然而,备选方案2证明是成功的.转换Instant为a LocalDate,然后使用YearMonth.from(TemporalAccessor)-method.

    Date date = new Date();

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

    YearMonth yearMonth = YearMonth.from(localDate);
    System.out.println("YearMonth: " + yearMonth);
Run Code Online (Sandbox Code Playgroud)

输出是(因为代码是在2015年1月执行的;):

YearMonth:2015-01