Java Date and Time
›Short versionOpen if you only have a minute.
- java.time is the API to use. LocalDate is a calendar date. LocalDateTime adds a clock time and still has no zone.
- Instant is a moment on the UTC timeline. ZonedDateTime is a local date-time attached to a ZoneId, so it names one instant.
- These types are immutable. plusDays and plus return a new value. The value you started with stays put.
- Duration is seconds and nanoseconds. Period is years, months, and days. A month is not a fixed number of seconds.
- Store an Instant or an explicit offset for events. Store a zone id with a civil appointment. A bare LocalDateTime loses the zone.
- java.util.Date and Calendar are the older types. Leave them at the boundary when a library still demands them.
A ticket says 8 October 2026, 7:30 in the evening. A server log says the same show started at 14:00 UTC. Both sentences are exact, and they are not the same kind of fact. One is a wall clock in a city. The other is a moment on the timeline. java.time gives each fact its own type so a later line cannot add an hour to a date that never had one.
The date on the form
A is the form. A is the form plus a time of day. Neither knows which region's offset to apply.
LocalDate showDay = LocalDate.of(2026, Month.OCTOBER, 8);
LocalDate nextDay = showDay.plusDays(1);
LocalDateTime doors = LocalDateTime.of(showDay, LocalTime.of(19, 30));showDay is still 8 October after plusDays. nextDay is the 9th. That immutability is why you can hand showDay to several methods and know they cannot move the show. Import the types from java.time.
The moment the log can share
An is comparable across machines. A is how you attach a city to a local date-time and recover that instant, or how you render an instant in a city.
ZoneId kolkata = ZoneId.of("Asia/Kolkata");
ZonedDateTime doorsInKolkata = doors.atZone(kolkata);
Instant starts = doorsInKolkata.toInstant();
ZonedDateTime sameShowInLondon = starts.atZone(ZoneId.of("Europe/London"));doors was 19:30 with no zone. Zoning it as Kolkata picks the offset that region uses on that date. starts is the moment. London's wall clock for that moment is a different local time, and it is the same instant. Store starts when the question is "which happened first." Store the zone id beside a local date-time when the question is "what did the ticket say, and in which city."
ZoneId.of("Asia/Kolkata") is a region. The offset on that evening is +05:30, and the zone id remains right if the region's rules ever change.ZoneOffset.of("+05:30") is only the offset. Use the offset when you really mean a fixed difference from UTC, such as a timestamp that was stored that way.
A length of time, of two kinds
A timeout is a . A billing cycle is a . Adding either one returns a new date or instant.
Instant deadline = starts.plus(Duration.ofHours(2));
LocalDate renews = showDay.plus(Period.ofMonths(1));
String printed = doorsInKolkata.format(
DateTimeFormatter.ofPattern("d MMM uuuu HH:mm z", Locale.ENGLISH));Two hours after the instant is unambiguous. One month after 8 October is 8 November, a calendar step, not 31 * 24 hours. Duration.between(start, end) measures elapsed time between instants. Period.between(startDate, endDate) measures calendar distance between local dates. Formatting belongs at the edge, through a DateTimeFormatter. Keep the typed value in the field, not the already-printed string.
$ javac ShowTime.java$ java ShowTime
You add Period.ofMonths(1) to 31 January. What kind of result do you get, and why is Duration.ofDays(30) a different fact?
You get the last valid day of February, because a period walks the calendar and February does not have a 31st. Duration.ofDays(30) is exactly 30 times 24 hours on the timeline. Added to a zoned date-time it can also cross a daylight-saving change, so the local clock may not read "thirty days later" at the same hour. Use the period for a monthly due date. Use the duration for an elapsed-time deadline.