2015-01-08 10 views
9

Wie kann ich am besten ein java.util.Date in ein Java 8 java.time.YearMonth konvertieren?Wie konvertiert man java.util.Date in Java8 java.time.YearMonth

Leider wirft die folgenden ein DateTimeException:

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

Ergebnisse in:

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) 
... 

ich diese Funktionalität benötigen, da ich YearMonth Werte in einer Datenbank mit JPA speichern möchten. Derzeit JPA nicht YearMonth ‚s nicht unterstützt, also habe ich mit folgendem YearMonthConverter kommen (Importe weggelassen):

// 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); 
    } 
} 

Gibt es nicht eine bessere (sauberere, kürzer) -Lösung (für beide Richtungen)?

+1

persönlich Mapping vermeide ich würde 'java.util.Date 'weil a) es nicht 'java.sql. *' - Typ ist, b) implizite Zeitzone enthält (schlechte Leistung und unscharfe Verwendung der Systemzeitzone), c) SQL definiert keinen solchen Typ für Jahr-Monat. Bessere Karte 'YearMonth' auf ganzzahlige mit [PROLEPTIC_MONTH] (http://docs.oracle.com/javase/8/docs/api/java/time/temporal/ChronoField.html#PROLEPTIC_MONTH) –

+0

@MenoHochschild: Das ist eine gute Idee, - Einziger Nachteil ist, dass es nicht lesbar ist, wenn man sich die Datenbank ansieht. Kein Problem in meinem Fall ... – Sebastian

+0

Nun, nur eine Ganzzahl ist nicht lesbar für die direkte Ansicht von db-Inhalten, aber beachten Sie, dass die Zuordnung zu juDate das visualisierte Datum und den Monat (wie ein db-Administrator sieht) aufgrund der Zeitzone ändern kann Konvertierungen und db/server-Einstellungen. –

Antwort

13

Kurze Antwort:

// 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()); 

Erläuterung:

Die JavaDoc des YearMonth.from(TemporalAccessor) -Methode sagt:

Die Umwandlung extrahiert die YEAR und MONTH_OF_YEAR Felder aus. Die Extraktion ist nur erlaubt, wenn das temporäre Objekt eine ISO-Chronologie hat oder in ein LocalDate konvertiert werden kann.

Also, müssen Sie entweder in der Lage:

  1. Extrakt die YEAR und MONTH_OF_YEAR Felder oder
  2. Sie etwas verwenden sollten, die zu einem LocalDate umgewandelt werden kann.

Lasst es versuchen!

java.time.temporal.UnsupportedTemporalTypeException: Nicht unterstütztes Feld: Jahr bei java.time.Instant.get (Instant.java:571

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

dies nicht möglich ist, wird eine Ausnahme ausgelöst) ...

Dies bedeutet, dass die Alternative 1 das Fenster verlässt. Der Grund dafür ist in dieser ausgezeichneten Antwort über how to convert Date to LocalDate erklärt.

Trotz seines Namens repräsentiert java.util.Date einen Zeitpunkt auf der Zeitlinie, kein "Datum". Die tatsächlichen Daten, die in dem Objekt gespeichert sind, sind eine lange Anzahl von Millisekunden seit 1970-01-01T00: 00Z (Mitternacht zu Beginn von 1970 GMT/UTC).

Die entsprechende Klasse zu java.util.Date in JSR-310 ist Instant, daher gibt es eine bequeme Methode toInstant(), um die Konvertierung bereitzustellen.

So ein Date kann zu einer Instant umgewandelt werden, aber das hat uns nicht helfen, es getan hat?

Alternative 2 beweist jedoch erfolgreich zu sein. Konvertieren Sie die Instant in eine LocalDate und verwenden Sie dann die YearMonth.from(TemporalAccessor) -Methode.

Date date = new Date(); 

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

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

Der Ausgang ist (da der Code im Januar 2015 durchgeführt wurde;):

YearMonth: 2015-01