2014-04-01 16 views
17

Używam nowego pakietu java.time w Javie 8. Mam starszą bazę danych, która daje mi java.util.Date, którą konwertuję na Instant.Java 8 java.time: Dodawanie TemporalUnit w Instant a LocalDateTime

Co próbuję zrobić, to dodać okres czasu, który jest oparty na innej flagi bazy danych. Mogę dodać dni, tygodnie, miesiące lub lata. Nie chcę dbać o to, co dodaję, i chciałbym móc dodać więcej opcji w przyszłości.

Moja pierwsza myśl to Instant.plus(), ale to daje mi UnsupportedTemporalTypeException dla wartości większych niż jeden dzień. Natychmiast wydaje się, że nie obsługuje operacji na dużych jednostkach czasu. W porządku, cokolwiek, LocalDateTime robi.

Więc to daje mi ten kod:

private Date adjustDate(Date myDate, TemporalUnit unit){ 
    Instant instant = myDate.toInstant(); 
    LocalDateTime dateTime = LocalDateTime.ofInstant(instant, ZoneId.systemDefault()); 
    dateTime = dateTime.plus(1, unit); 
    Instant updatedInstant = dateTime.atZone(ZoneId.systemDefault()).toInstant(); 
    return new Date(dueInstant.toEpochMilli()); 
} 

Teraz, to jest mój pierwszy raz przy użyciu nowego API czasu, więc może coś przeoczyć tutaj. Ale wydaje mi się, że niezgrabne muszę iść:

Date --> Instant --> LocalDateTime --> do stuff--> Instant --> Date. 

Nawet gdybym nie musiał używać daty część, ja nadal uważam, że to było trochę niezręczne. Moje pytanie brzmi: czy robię to całkowicie źle i jaki jest najlepszy sposób na zrobienie tego?


Edit: Rozszerzając dyskusji w komentarzach.

Myślę, że mam teraz lepszy pomysł na temat tego, w jaki sposób LocalDateTime i Instant są odtwarzane za pomocą java.util.Date i java.sql.Timestamp. Dziękuję wszystkim.

Teraz bardziej praktyczna uwaga. Załóżmy, że użytkownik przesyła mi datę z dowolnego miejsca na świecie, z dowolnej strefy czasowej. Wysyłają mi 2014-04-16T13:00:00, które można przeanalizować w LocalDateTime. Następnie konwertuję to bezpośrednio na java.sql.Timestamp i zachowuję w mojej bazie danych.

Teraz, bez robienia czegokolwiek innego, wyciągam mój java.sql.timestamp z mojej bazy danych, konwertuję na LocalDateTime przy użyciu timestamp.toLocalDateTime(). Wszystko dobrze. Następnie zwracam tę wartość do mojego użytkownika, używając formatu ISO_DATE_TIME. Wynikiem jest 2014-04-16T09:00:00.

Zakładam, że ta różnica wynika z pewnego rodzaju niejawnej konwersji do/z UTC. Myślę, że moja domyślna strefa czasowa może zostać zastosowana do wartości (EDT, UTC-4), co wyjaśniałoby, dlaczego liczba jest wyłączona o 4 godziny.

Nowe pytanie (pytania). Gdzie odbywa się ukryta zamiana czasu lokalnego na UTC? Jaki jest lepszy sposób na zachowanie stref czasowych. Czy nie powinienem iść bezpośrednio z czasu lokalnego jako ciąg (2014-04-16T13: 00: 00) do LocalDateTime? Czy powinienem oczekiwać strefy czasowej od danych wejściowych użytkownika?

+3

Jaką wartość ma Pan reprezentować? 'Instant' nie * logicznie * wie o systemie kalendarzy - to tylko punkt w czasie - więc dodanie miesiąca do niego nie ma sensu. Powinieneś również dokładnie rozważyć, czy * naprawdę * chcesz korzystać ze strefy czasowej systemu - czy chcesz uzyskać różne wyniki dla tych samych wartości, w zależności od tego, gdzie pracujesz? –

+0

@JonSkeet Czy byłoby bardziej odpowiednie arbitralne wybieranie identyfikatora strefy? Zawsze GMT czy coś? Wygląda na to, że może to pochodzić z własnego zestawu problemów. Być może problemem jest to, że nie wiem, jak reprezentować moje java.util.date. Mam punkt w czasie. Jeśli spełnione są określone warunki, chcę zmienić ten punkt na jeden miesiąc (lub dzień, rok, cokolwiek) w przyszłości. – jacobhyphenated

+0

Korzystanie z UTC może być najlepszą opcją, ale naprawdę musisz pomyśleć o swoich wymaganiach. Co to znaczy zmienić punkt w czasie (bez strefy czasowej lub kalendarza) o jeden miesiąc? –

Odpowiedz

16

Przedstawię odpowiedź na podstawie mojego ostatecznego rozwiązania i podsumowania bardzo długiego łańcucha komentarzy.

Aby rozpocząć, cały łańcuch konwersji:

Date --> Instant --> LocalDateTime --> Do stuff --> Instant --> Date 

jest niezbędne do zachowania informacji o strefie czasowej, a nadal robić operacje na Data podobnego obiektu, który jest świadomy kalendarz i cały kontekst w nim.W przeciwnym razie ryzykujemy niejawną konwersję na lokalną strefę czasową, a jeśli spróbujemy umieścić ją w formacie czytelnym dla człowieka, czasy mogą się zmienić z tego powodu.

Na przykład metoda toLocalDateTime() na klasie java.sql.Timestamp domyślnie konwertuje do domyślnej strefy czasowej. Było to niepożądane dla moich celów, ale niekoniecznie jest to złe zachowanie. Ważne jest jednak, aby być tego świadomym. To jest problem z konwersją bezpośrednio ze starego obiektu daty java do obiektu LocalDateTime. Ponieważ zwykle uważa się, że obiektami starszymi są UTC, konwersja używa lokalnego przesunięcia strefy czasowej.

Teraz możemy powiedzieć, że nasz program pobiera dane wejściowe 2014-04-16T13:00:00 i zapisuje w bazie danych jako java.sql.Timestamp.

//Parse string into local date. LocalDateTime has no timezone component 
LocalDateTime time = LocalDateTime.parse("2014-04-16T13:00:00"); 

//Convert to Instant with no time zone offset 
Instant instant = time.atZone(ZoneOffset.ofHours(0)).toInstant(); 

//Easy conversion from Instant to the java.sql.Timestamp object 
Timestamp timestamp = Timestamp.from(instant); 

Teraz bierzemy znacznik czasu i dodać liczbę dni do niego:

Timestamp timestamp = ... 

//Convert to LocalDateTime. Use no offset for timezone 
LocalDateTime time = LocalDateTime.ofInstant(timestamp.toInstant(), ZoneOffset.ofHours(0)); 

//Add time. In this case, add one day. 
time = time.plus(1, ChronoUnit.DAYS); 

//Convert back to instant, again, no time zone offset. 
Instant output = time.atZone(ZoneOffset.ofHours(0)).toInstant(); 

Timestamp savedTimestamp = Timestamp.from(output); 

Teraz wystarczy do wyjścia jako czytelny dla człowieka napis w formacie ISO_LOCAL_DATE_TIME.

Timestamp timestamp = .... 
LocalDateTime time = LocalDateTime.ofInstant(timestamp.toInstant(), ZoneOffset.ofHours(0)); 
String formatted = DateTimeFormatter.ISO_LOCAL_DATE_TIME.format(time); 
+6

ZoneOffset.ofHours (0) można lepiej wyrazić jako ZoneOffset.UTC. Możesz także łatwiej pracować z ZonedDateTime, ponieważ ta klasa obsługuje wszystkie zachowania typu plus/minus LocalDateTime, ale zachowuje informacje o strefie czasowej. – JodaStephen

+0

Dla konkretnego przypadku użycia dni, nie potrzebujesz (Lokalny/Strefowy) Data, ponieważ czas trwania dni można dodać bezpośrednio do chwili: 'Timestamp.from (timestamp.toInstant(). Plus (Czas trwania. ofDays (1))) ' –

+1

Dodanie' Duration.ofDays' do 'Instant' nie będzie uwzględniało zmian czasu letniego lub podobnych, więc nie jest zalecane. Konwertuj na 'ZonedDateTime' zamiast tego. –

Powiązane problemy