Java LocalTime
Represent times without dates or time zones in Java with LocalTime.
Java LocalTime
LocalTime is the mirror of LocalDate: a time of day — hour, minute, second, nanosecond — with no date and no time zone. It represents the same wall-clock reading on every calendar day everywhere: LocalTime.of(9, 30) is half past nine on any day in any city.
That's the right type for a recurring time of day — store opening at 09:30, a daily cron-like job at 03:15, a meeting that starts at 14:00 regardless of the date. It is not the right type for "the moment the user clicked submit at 14:30 today" — that needs a date, and probably a zone. The two combined give you LocalDateTime, the topic of the next chapter.
Creating
LocalTime now = LocalTime.now(); // current time in the JVM default zone
LocalTime nine = LocalTime.of(9, 0); // hour, minute
LocalTime nineThirty = LocalTime.of(9, 30, 15); // hour, minute, second
LocalTime nineThirtyNanos = LocalTime.of(9, 30, 15, 500_000_000); // + nanosecond (0 ..999_999_999)
LocalTime parsed = LocalTime.parse("09:30:15"); // ISO-8601 HH:mm[:ss[.nnnnnnnnn]]The pre-built constants are useful for boundary conditions:
LocalTime.MIDNIGHT // 00:00
LocalTime.NOON // 12:00
LocalTime.MIN // 00:00:00.000000000
LocalTime.MAX // 23:59:59.999999999MIN and MAX are particularly useful when combining with a LocalDate to span a whole day: LocalDateTime.of(date, LocalTime.MIN) is "midnight at the start of the date"; LocalTime.MAX is the last representable nanosecond of the day.
Resolution: nanoseconds
LocalTime is precise to the nanosecond — nine fields' worth of resolution (1 second = 1,000,000,000 ns). On most operating systems the actual clock resolution is millisecond (1,000,000 ns each) or microsecond (1,000 ns each); the extra precision is there so the type doesn't lose information when interacting with systems that have higher-resolution clocks.
Direct accessors:
time.getHour(); // 0-23
time.getMinute(); // 0-59
time.getSecond(); // 0-59
time.getNano(); // 0-999_999_999There's no getMilli(); if you want milliseconds, divide the nano: time.getNano() / 1_000_000.
24-hour, no AM/PM
LocalTime is internally 24-hour. LocalTime.of(13, 0) is "1 PM" and there is no AM/PM in the type. Parsing strings with "AM"/"PM" requires a custom DateTimeFormatter (the Date Parsing chapter covers this) — the default parse is ISO-8601 24-hour only.
Arithmetic and modifications
Same fluent shape as LocalDate:
time.plusHours(2);
time.plusMinutes(30);
time.plusSeconds(45);
time.plusNanos(500_000);
time.withHour(14); // replace one field
time.withMinute(0);The wraparound behaviour: every plus/minus method on LocalTime wraps around midnight silently. LocalTime.of(23, 0).plusHours(2) is 01:00, not "tomorrow at 01:00" — there is no "tomorrow" in LocalTime. If you need to know whether a wraparound happened, use LocalDateTime or do the math yourself:
LocalTime late = LocalTime.of(23, 0);
LocalTime later = late.plusHours(2); // 01:00 — silently wrapped
// To detect wrap: compare the new value's getHour with what you expected, or use LocalDateTime.This wraparound is documented and intentional, but it's a sharp edge if you forget. For "when does this shift end?" calculations that can cross midnight, the right type is LocalDateTime, not LocalTime.
Comparing
time.isBefore(other);
time.isAfter(other);
time.compareTo(other);
time.equals(other);Lexical ordering by hour:minute:second:nano. LocalTime implements Comparable<LocalTime>, so you can sort a list of times or use it as a TreeMap key directly.
Distance
Duration.between and ChronoUnit.X.between both work:
Duration d = Duration.between(start, end);
long minutes = ChronoUnit.MINUTES.between(start, end);
long seconds = ChronoUnit.SECONDS.between(start, end);The sign: positive when end is after start, negative otherwise. The same wraparound caveat applies — Duration.between(LocalTime.of(23, 0), LocalTime.of(1, 0)) is −22 hours, not +2 hours; the API treats 01:00 as earlier than 23:00 of the same notional day. For "the shift went past midnight," LocalDateTime-based arithmetic is the right tool.
Combining with LocalDate
You'll convert to a date-with-time often:
LocalDate date = LocalDate.of(2025, 11, 4);
LocalTime time = LocalTime.of(9, 30);
LocalDateTime dt = date.atTime(time); // 2025-11-04T09:30
LocalDateTime dt2 = time.atDate(date); // same thingatTime / atDate are the bridge methods. The result is a LocalDateTime — still no zone, but now anchored to a calendar day. The next chapter takes that further.
A worked example: a tiny scheduling helper
The program below uses LocalTime for a daily-schedule kind of task: define an "office hours" window, check whether a given moment falls inside it, compute how long until the next opening, and demonstrate the midnight-wrap gotcha.
What to take from the run:
LocalTimeprinted as09:00,17:30,12:30— the canonical ISO-8601 24-hour form. No AM/PM in the type. If you need to display "5:30 PM" to a user, the Date Formatting chapter has the formatter for that; the type itself doesn't know about it.- The "is the time inside the window" check used
!isBefore(open) && !isAfter(close). That's the half-open-vs-closed-interval idiom — both endpoints are included. For "strictly inside," switch to the un-negated forms. Duration.between(LocalTime.of(22, 0), LocalTime.of(2, 0))returnedPT-20H, notPT4H.LocalTimehas no notion of "next day" — whenendis earlier thanstartin clock terms, the duration goes negative. For a shift that crosses midnight, switch the inputs toLocalDateTimeand let the dates resolve the ambiguity. This is the single biggestLocalTimefootgun.LocalTime.of(23, 30).plusHours(2)returned01:30. The wraparound is silent — no exception, no flag, no carry into the date. If you need to know "did this wrap?", useLocalDateTime. If you genuinely want clock-arithmetic-mod-24 (a recurring schedule, say), the wraparound is the correct behaviour.date.atTime(time)was the canonical bridge toLocalDateTime. The mirrortime.atDate(date)produces the same result. You'll use these constantly when reading a time from one source and a date from another, then combining them into the single object the downstream API wants.
What's next
The next chapter, Java LocalDateTime, combines LocalDate and LocalTime into the third "local" type: a date and a time, still with no time zone attached. That's the natural unit for "this happened at 14:30 on November 4" when the zone is irrelevant or recorded separately.
Practice
What does `LocalTime.of(23, 0).plusHours(3)` return, and why?