Adaptace nových verzí Javy jde pomalu. Dodnes vídám, že programátoři neumí či nechtějí používat multi catch, try-with-resources a diamond operátor. Jak chtít složitější posun, který přináší Java 8?

Ovšem sám nejsem bez viny. Java 8 už je tu víc jak dva roky. Při jejím uvedení jsem psal, jak obstojí v Akumulátor testu, ale na projektech ji naplno nevyužíváme. Rozhodl jsem se to napravit tím, že začnu používat java.time.* místo java.util.Date. Jednak kvůli API a taky proto, že jsou nové třídy immutable. Chtěl bych se podělit o to, jak jsem se při tom nachytal.

java.timex

V příkladech testování a mockování času je uvedená třída Instant, ale potřeboval jsem převod do LocalTime. Jakožto empirický programátor (to jest zkouším, co mi nadhodí kontextová nápověda) píšu:

LocalTime.from(Instant.now())

Jenže ouha

java.time.DateTimeException: Unable to obtain LocalTime from TemporalAccessor:
2016-08-22T20:40:05.875Z of type java.time.Instant

Našel jsem vysvětlení na Stack Overflow. Doporučuji k přečtení celé, ale přináším alespoň výtah.

Autoři specifikace JSR-310 nechtěli, aby lidé převáděli mezi strojovým a lidským časem přes statické metody from() v typech jako ZoneId, ZoneOffset, OffsetDateTime, ZonedDateTime atd. Pokud byste pečlivě studovali javadoc, je to tam výslovně specifikované. Místo toho použijte:

OffsetDateTime#toInstant():Instant
ZonedDateTime#toInstant():Instant
Instant#atOffset(ZoneOffset):OffsetDateTime
Instant#atZone(ZoneId):ZonedDateTime

Problém se statickými metodami from() je v tom, že jinak jsou lidé schopní převádět mezi Instant a například LocalDateTime, aniž by přemýšleli o časové zóně.

Všechny statické metody from() jsou až příliš veřejné. Podle mého názoru jsou příliš snadno přístupné a měly by být raději odstraněny z veřejného API nebo by měly používat specifičtější parametr než TemporalAccessor. Slabou stránkou těchto metod je to, že lidé můžou při konverzi zapomenout na související časovou zónu, protože začnou dotaz s lokálním typem. Zvažte například: LocalDate.from(anInstant) (v jaké časové zóně???)

Testování

Nebudu vás víc napínat teorií. Zde je příklad, kdy konfigurace je interval v hodinách (od do), přičemž na serveru běží strojový čas. Součástí příkladu je i test v Groovy.

import java.time.Clock;
import java.time.LocalTime;
public class TimeMachine {
private LocalTime from = LocalTime.MIDNIGHT;
private LocalTime until = LocalTime.of(6, 0);
private Clock clock = Clock.systemDefaultZone();
public boolean isInInterval() {
LocalTime now = LocalTime.now(clock);
return now.isAfter(from) && now.isBefore(until);
}
}
import org.junit.Test
import org.junit.runner.RunWith
import org.junit.runners.Parameterized
import java.time.Clock
import java.time.Instant
import static java.time.ZoneOffset.UTC
import static org.junit.runners.Parameterized.Parameters
@RunWith(Parameterized)
class TimeMachineTest {
@Parameters(name = "{0} - {2}")
static data() {
[
["01:22:00", true, "in interval"],
["23:59:59", false, "before"],
["06:01:00", false, "after"],
]*.toArray()
}
String time
boolean expected
TimeMachineTest(String time, boolean expected, String testName) {
this.time = time
this.expected = expected
}
@Test
void test() {
TimeMachine timeMachine = new TimeMachine()
timeMachine.clock = Clock.fixed(Instant.parse("2010-01-01T${time}Z"), UTC)
def result = timeMachine.isInInterval()
assert result == expected
}
}

Bean Validation

Závěrem ještě jeden zásek, proč je přechod na java.time.* složitější, a tím je chybějící podpora v Bean Validation. Jistě, můžete si validátory napsat sami, ale v Hibernate nejsou. Issue HHH-8844 se sice vztahuje k hibernate-core, ale může vysvětlit i validátor. Prostě drží zpětnou kompatibilitu k Javě 6. Ovšem svítá naděje v podobě Bean Validation 2.0