LocalDateTime
java.time.LocalDateTime combines a date and a time of day, such as 2026-11-13T21:45, without a time zone. It is a LocalDate and a LocalTime joined together, and supports everything both of them do. It is useful for values that are naturally "wall-clock" readings in a known context: an appointment entered on a form, a timestamp printed on a parking ticket, or a scheduled time inside a system that runs in a single zone. Because it has no zone, it does not identify a single moment worldwide; that distinction is the most important thing to understand about it.
Creating a LocalDateTime
import java.time.*;
LocalDateTime now = LocalDateTime.now(); // current date-time, default zone
LocalDateTime dt = LocalDateTime.of(2026, 11, 13, 21, 45); // year, month, day, hour, minute
LocalDateTime withSeconds = LocalDateTime.of(2026, 11, 13, 21, 45, 30);
LocalDateTime parsed = LocalDateTime.parse("2026-11-13T09:30"); // ISO format with 'T'
LocalDateTime combined = LocalDate.of(2026, 11, 13).atTime(LocalTime.NOON); // 2026-11-13T12:00
LocalDateTime midnight = LocalDate.of(2026, 11, 13).atStartOfDay(); // 2026-11-13T00:00
LocalDateTime fromParts = LocalDateTime.of(LocalDate.of(2026, 11, 13), LocalTime.of(9, 0));
The ISO text form uses a T between the date and the time: 2026-11-13T21:45. A string with a space instead (2026-11-13 21:45) needs a custom formatter to parse.
Reading and Splitting
LocalDateTime dt = LocalDateTime.of(2026, 11, 13, 21, 45);
dt.getYear(); // 2026
dt.getMonth(); // NOVEMBER
dt.getDayOfWeek(); // FRIDAY
dt.getHour(); // 21
dt.toLocalDate(); // 2026-11-13
dt.toLocalTime(); // 21:45
Arithmetic Crosses Day Boundaries Correctly
Unlike LocalTime, which wraps at midnight, LocalDateTime carries over into the next day, month, or year:
dt.plusHours(3); // 2026-11-14T00:45: next day
dt.plusMinutes(20).withSecond(5); // 2026-11-13T22:05:05
dt.minusMonths(11); // 2025-12-13T21:45
dt.truncatedTo(ChronoUnit.HOURS); // 2026-11-13T21:00
The plus, minus, with, and truncatedTo methods all return new objects; the original is unchanged.
Comparing and Measuring
LocalDateTime start = LocalDateTime.of(2026, 11, 13, 21, 45);
LocalDateTime end = start.plusDays(1).plusMinutes(5);
start.isBefore(end); // true
Duration.between(start, end); // PT24H5M
ChronoUnit.HOURS.between(start, end); // 24
ChronoUnit.MINUTES.between(start, end); // 1445
Duration.between gives the exact time difference, including spans across midnight. Its toHours(), toMinutesPart(), and similar methods split it into units for display.
The Missing Time Zone
LocalDateTime stores no time zone, and that has two consequences.
It does not represent a unique moment. 2026-11-13T21:45 happens in Chennai about five and a half hours before it happens in London. If a server stores this value for an order placed by a user, there is no way to tell later which moment the order actually happened.
It ignores daylight-saving time. In zones with DST, local clocks skip an hour in spring and repeat an hour in autumn. LocalDateTime arithmetic assumes every day has exactly 24 hours, so durations across a DST change can be off by an hour:
// In New York, clocks jump from 02:00 to 03:00 on 8 March 2026.
LocalDateTime before = LocalDateTime.of(2026, 3, 8, 1, 0);
LocalDateTime after = LocalDateTime.of(2026, 3, 8, 4, 0);
Duration.between(before, after); // PT3H by the wall clock; only 2 hours actually passed
ZonedDateTime, covered in the next lesson, applies the zone's rules and gives the true elapsed time. India does not observe daylight-saving time, so this issue does not arise with Asia/Kolkata, but software serving users in other countries must account for it.
When to use LocalDateTime
Use LocalDateTime for | Use something else for |
|---|---|
| Wall-clock values in a single, known zone (a local parking system, a shop's till) | Events that must be compared across zones (use Instant or ZonedDateTime) |
| Form input before the user's zone is attached | Stored timestamps of when things happened (Instant, OffsetDateTime) |
Future appointments where the local time is what matters, stored together with a ZoneId | Durations that must be exact across DST changes (ZonedDateTime) |
A common, sound design is to store when something happened as an Instant (UTC) and convert to the user's zone only for display, while storing future local appointments as a LocalDateTime plus a ZoneId, so that the appointment keeps its wall-clock time even if the zone's rules change before it occurs.
Attaching a Zone or Offset
LocalDateTime dt = LocalDateTime.of(2026, 11, 13, 21, 45);
ZonedDateTime zdt = dt.atZone(ZoneId.of("Asia/Kolkata")); // 2026-11-13T21:45+05:30[Asia/Kolkata]
OffsetDateTime odt = dt.atOffset(ZoneOffset.ofHoursMinutes(5, 30));
Instant instant = zdt.toInstant(); // 2026-11-13T16:15:00Z
Once a zone is attached, the value becomes a real moment that can be converted, stored, and compared globally.
LocalDateTime and Databases
JDBC 4.2 drivers map SQL TIMESTAMP (without time zone) columns to LocalDateTime, and TIMESTAMP WITH TIME ZONE to OffsetDateTime. When storing moments, prefer a zone-aware column or store UTC values consistently; mixing local timestamps from servers in different zones is a frequent source of hard-to-trace bugs.
Practical Example: Parking Fees
A parking lot runs entirely in local time, so LocalDateTime is a good fit. Parking is free for 15 minutes; after that, every started hour costs ₹40, capped at ₹300 per 24 hours.
import java.time.*;
import java.util.List;
public class Parking {
static long fee(LocalDateTime in, LocalDateTime out) {
if (out.isBefore(in)) throw new IllegalArgumentException("exit before entry");
long minutes = Duration.between(in, out).toMinutes();
if (minutes <= 15) return 0; // grace period
long hours = (minutes + 59) / 60; // every started hour is charged
long days = hours / 24;
long rest = hours % 24;
return days * 300 + Math.min(rest * 40, 300); // 40 per hour, 300 per day max
}
public static void main(String[] args) {
record Visit(String plate, LocalDateTime in, LocalDateTime out) { }
List<Visit> visits = List.of(
new Visit("TN01AB1234", LocalDateTime.of(2026, 11, 13, 9, 0), LocalDateTime.of(2026, 11, 13, 9, 12)),
new Visit("TN09CD5678", LocalDateTime.of(2026, 11, 13, 9, 0), LocalDateTime.of(2026, 11, 13, 11, 1)),
new Visit("KA05EF9012", LocalDateTime.of(2026, 11, 13, 21, 45), LocalDateTime.of(2026, 11, 14, 1, 10)),
new Visit("TN22GH3456", LocalDateTime.of(2026, 11, 13, 8, 0), LocalDateTime.of(2026, 11, 15, 10, 30))
);
for (Visit v : visits) {
Duration stay = Duration.between(v.in(), v.out());
System.out.printf("%s %s -> %s %2dh %02dm fee %d%n", v.plate(), v.in(), v.out(), stay.toHours(), stay.toMinutesPart(), fee(v.in(), v.out()));
}
}
}
Output:
TN01AB1234 2026-11-13T09:00 -> 2026-11-13T09:12 0h 12m fee 0
TN09CD5678 2026-11-13T09:00 -> 2026-11-13T11:01 2h 01m fee 120
KA05EF9012 2026-11-13T21:45 -> 2026-11-14T01:10 3h 25m fee 160
TN22GH3456 2026-11-13T08:00 -> 2026-11-15T10:30 50h 30m fee 720
Duration.between measures each stay correctly even when it crosses midnight (the third vehicle) or spans several days (the fourth). The fee rules then use plain integer arithmetic on the minutes:
- Grace period. The first stay is within the 15-minute free window.
- Started hours. The second stay is 2 hours 1 minute, which is 3 started hours, ₹120.
- Overnight stay. The third stay crosses midnight and is 3 hours 25 minutes, which is 4 started hours, ₹160.
- Multi-day stay. The fourth stay is 50 hours 30 minutes, which is 51 started hours: two full days at the ₹300 cap plus 3 extra hours at ₹40, totalling ₹720.
The lot operates in one location, which never changes clocks, so local date-times are a safe choice here. A multi-city system would add a ZoneId to each site.