Directly converting between local and timezone-aware types requires an explicit ZoneId or ZoneOffset to bridge the gap.
If this context is omitted, the operation fails with a DateTimeException at runtime, as the API refuses to resolve the ambiguity by
assuming a default timezone.
The java.time API distinguishes between local and absolute time. Classes such as LocalDate, LocalTime and
LocalDateTime represent "wall-clock" time: human-readable date and time components that exist independent of any specific location. In
contrast, the Instant class represents a single, unambiguous point on the UTC timeline, and ZonedDateTime and
OffsetDateTime correspond to moments in time associated with a specific timezone or UTC offset.
Because local classes lack a geographical context, they cannot be mapped to the global timeline without a time zone (ZoneId) or UTC
offset (ZoneOffset). Consequently, attempting a direct conversion between these types without providing the missing temporal context will
result in a DateTimeException, as the Java runtime cannot make arbitrary assumptions about missing timezone information.
Similarly, an Instant cannot be transformed into a ZonedDateTime or OffsetDateTime without explicitly
specifying the target zone, as a single moment in time translates to different wall-clock values across the globe.
This issue causes runtime failures with DateTimeException, leading to application crashes. It is particularly problematic in
production, where timezone bugs may only surface in different geographical regions.
Provide explicit timezone information (ZoneId or ZoneOffset) when converting between one of the following local date/time
types and Instant, ZonedDateTime, OffsetDateTime or OffsetTime:
LocalDateLocalTimeLocalDateTimeDayOfWeekMonthMonthDayYearYearMonthWhen converting an Instant to a ZonedDateTime, OffsetDateTime or OffsetTime, explicit timezone
or offset information should also be specified.
public LocalDateTime fromInstant(Instant instant) {
return LocalDateTime.from(instant); // Noncompliant - throws a DateTimeException
}
LocalDate date = LocalDate.of(2023, 12, 25); Instant instant = Instant.from(date); // Noncompliant - throws a DateTimeException
public Year fromInstant(Instant instant) {
return Year.from(instant); // Noncompliant - throws a DateTimeException
}
public ZonedDateTime fromInstant(Instant instant) {
return ZonedDateTime.from(instant); // Noncompliant - throws a DateTimeException
}
public ZonedDateTime fromLocalDateTime(LocalDateTime localDateTime) {
return ZonedDateTime.from(localDateTime); // Noncompliant - throws a DateTimeException
}
public LocalDateTime fromInstant(Instant instant, ZoneId zone) {
return LocalDateTime.ofInstant(instant, zone); // Compliant
}
LocalDate date = LocalDate.of(2023, 12, 25); // Option 1: use a ZoneId Instant instant1 = date.atStartOfDay(ZoneId.systemDefault()).toInstant(); // Compliant // Option 2: use a ZoneOffset Instant instant2 = date.atStartOfDay().toInstant(ZoneOffset.UTC); // Compliant
public Year fromInstant(Instant instant, ZoneId zone) {
return Year.from(instant.atZone(zone)); // Compliant
}
public ZonedDateTime fromInstant(Instant instant, ZoneId zone) {
return ZonedDateTime.ofInstant(instant, zone); // Compliant
}
public ZonedDateTime fromLocalDateTime(LocalDateTime localDateTime, ZoneId zone) {
return ZonedDateTime.from(localDateTime.atZone(zone)); // Compliant
}