2015-01-08 14 views
9

Come posso convertire in modo ottimale un java.util.Date in un Java 8 java.time.YearMonth?Come convertire java.util.Date in Java8 java.time.YearMonth

Purtroppo il seguente tiri un DateTimeException:

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

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

ho bisogno di questa funzionalità perché voglio conservare YearMonth valori in un database utilizzando JPA. Attualmente JPA non supporta YearMonth 's, così mi è venuta in mente la seguente YearMonthConverter (importazioni omessi):

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

Non c'è una soluzione migliore (più pulito, più breve) (per entrambe le direzioni)?

+1

Personalmente eviterei mappatura a 'java.util.Date 'perché a) non è' java.sql. * '-tipo, b) include il fuso orario implicito (prestazioni scadenti e uso sfocato del fuso orario del sistema), c) SQL non definisce tale tipo per anno-mese. Meglio mappare 'YearMonth' su intero usando [PROLEPTIC_MONTH] (http://docs.oracle.com/javase/8/docs/api/java/time/temporal/ChronoField.html#PROLEPTIC_MONTH) –

+0

@MenoHochschild: questa è una buona idea - solo l'avvertenza è che non è leggibile quando si guarda al database stesso. Nessun problema nel mio caso ... – Sebastian

+0

Beh, solo un numero intero non è leggibile per la visualizzazione diretta del contenuto di db, ma attenzione, il mapping a juDate può cambiare la data e il mese visualizzati (come l'amministratore di db lo vede) a causa del fuso orario conversioni e impostazioni db/server. –

risposta

13

Risposta breve:

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

Spiegazione:

La JavaDoc del YearMonth.from(TemporalAccessor) -Metodo dice:

La conversione estrae i campi anno e MONTH_OF_YEAR. L'estrazione è consentito solo se l'oggetto temporale ha una cronologia ISO, o può essere convertito in un LocalDate.

Quindi, è necessario sia in grado di:

  1. estratto i YEAR e MONTH_OF_YEAR campi o
  2. si dovrebbe usare qualcosa che può essere convertito in un LocalDate.

Lets try it!

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

Questo non è possibile, viene generata un'eccezione:

java.time.temporal.UnsupportedTemporalTypeException: campo non supportato: Anno a java.time.Instant.get (Instant.java:571) ...

Ciò significa che l'alternativa 1 va fuori dalla finestra. La ragione di questo è spiegato in ottima risposta su how to convert Date to LocalDate.

Nonostante il nome, java.util.Date rappresenta un istante sulla linea del tempo, non una "data". I dati effettivi memorizzati all'interno dell'oggetto è un lungo conto di millisecondi dal 1970-01-01T00: 00Z (mezzanotte all'inizio del 1970 GMT/UTC).

La classe equivalente a java.util.Date in JSR-310 è Instant, quindi esiste un metodo conveniente perInstant() per fornire la conversione.

Così, un Date può essere convertito in un Instant ma questo non ci ha aiutato, lo ha fatto?

L'alternativa 2 si rivela comunque valida. Convertire il Instant in un LocalDate e quindi utilizzare il metodo 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); 

L'uscita è (dato che il codice è stato eseguito nel mese di gennaio 2015;):

annoMese: 2015-01