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

时间:2015-01-08 15:04:45

标签: jpa java-8 java.util.date

如何才能最好地将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);
  }
}

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

1 个答案:

答案 0 :(得分:18)

简答:

// 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. 提取YEARMONTH_OF_YEAR字段,或
  2. 你应该使用可以转换为LocalDate的东西。
  3. 让我们试试吧!

    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会消失。关于how to convert Date to LocalDate的优秀答案解释了原因。

      

    尽管有名称,java.util.Date代表时间线上的瞬间,而不是&#34;日期&#34;。存储在对象中的实际数据是自1970-01-01T00:00Z(GMT / UTC 1970年初午夜)以来的长时间毫秒数。

         

    JSR-310中与java.util.Date的等价类是Instant,因此有一个方便的方法可以让Instant()提供转换。

    所以,Date可以转换为Instant,但这对我们没有帮助,是吗?

    备选方案2然而被证明是成功的。将Instant转换为LocalDate,然后使用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月执行的;):

      

    YearMonth:2015-01