Add or Subtract Hours, Minutes and Seconds in Java

Add or subtract hours, minutes and seconds in Java with LocalDateTime, ZonedDateTime, Instant and Duration, including DST changes and midnight rollover.

Adding one hour at 01:30 on the New York spring-forward day gives 03:30 with ZonedDateTime and 02:30 with LocalDateTime

Adding hours, minutes and seconds in Java takes one call, such as plusHours(2), plusMinutes(45) or plusSeconds(90) on a LocalDateTime, LocalTime or ZonedDateTime, and the matching minus method subtracts. An Instant has no plusHours(), so we use plus(2, ChronoUnit.HOURS) or plus(Duration) there.

We need time arithmetic for token expiry, reminders before an appointment, timeouts, shift ends and “last 15 minutes” queries. Every method returns a new object, so the original value never changes.

The following example moves a 9:30 meeting on October 10, 2026 in several ways.

LocalDateTime meeting = LocalDateTime.of(2026, 10, 10, 9, 30);
LocalDateTime later = meeting.plusHours(2);                     // 2026-10-10T11:30
LocalDateTime earlier = meeting.minusMinutes(45);               // 2026-10-10T08:45
LocalDateTime withSeconds = meeting.plusSeconds(90);            // 2026-10-10T09:31:30
LocalDateTime longCall = meeting.plus(Duration.ofMinutes(150)); // 2026-10-10T12:00
Instant expiresAt = Instant.parse("2026-10-10T09:30:00Z").plus(2, ChronoUnit.HOURS);   // 2026-10-10T11:30:00Z

Notice that the date rolls over by itself when the time passes midnight. The sections cover the methods per class, Duration and units, the Instant limits, and the daylight saving cases where adding one hour does not move the clock by one hour.

1. Time Arithmetic Methods per Class

The classes with a time part share the same method names, and each method takes a long that can also be negative. The class decides what happens at midnight and at a daylight saving change.

ClassplusHours / plusMinutes / plusSeconds / plusNanosAt midnightAt a DST change
LocalTimeYesWraps to the next day’s time, no dateNot applicable
LocalDateTimeYesMoves to the next dateIgnores DST, has no zone
ZonedDateTimeYesMoves to the next dateAdds real elapsed time
OffsetDateTimeYesMoves to the next dateKeeps the fixed offset
InstantOnly plusSeconds, plusMillis, plusNanosNot applicable, UTC time-lineNot applicable, UTC time-line

A LocalTime holds only a clock time, so 23:30 plus two hours is 01:30 with no hint that a day passed. When the day matters, for example for a shift that ends after midnight, we keep a LocalDateTime instead.

LocalTime lateShift = LocalTime.of(23, 30);
LocalTime shiftEnd = lateShift.plusHours(2);                    // 01:30
LocalDateTime nightShift = LocalDateTime.of(2026, 10, 10, 23, 30);
LocalDateTime nightEnd = nightShift.plusHours(2);               // 2026-10-11T01:30
LocalDateTime cutoff = nightShift.minusSeconds(30);             // 2026-10-10T23:29:30

2. Adding a Duration or a ChronoUnit Amount

A Duration is an amount of time in seconds and nanoseconds, such as 90 minutes, and every time class accepts it in plus() and minus(). It is the right type for a timeout or a session length that comes from configuration, because Duration.parse() reads ISO-8601 text such as PT1H30M.

Duration sessionLength = Duration.parse("PT1H30M");             // PT1H30M
LocalDateTime login = LocalDateTime.of(2026, 10, 10, 22, 45);
LocalDateTime logout = login.plus(sessionLength);               // 2026-10-11T00:15
LocalDateTime warning = logout.minus(Duration.ofMinutes(5));    // 2026-10-11T00:10
LocalDateTime halfDay = login.plus(1, ChronoUnit.HALF_DAYS);    // 2026-10-11T10:45

Milliseconds have no plusMillis() method on LocalDateTime, so we add Duration.ofMillis() or use ChronoUnit.MILLIS. To cut off the seconds after the arithmetic, we call truncatedTo(ChronoUnit.MINUTES).

LocalDateTime request = LocalDateTime.of(2026, 10, 10, 9, 30, 15);
LocalDateTime retryAt = request.plus(Duration.ofMillis(2500));        // 2026-10-10T09:30:17.500
LocalDateTime sameRetry = request.plus(2500, ChronoUnit.MILLIS);      // 2026-10-10T09:30:17.500
LocalDateTime minuteOnly = retryAt.truncatedTo(ChronoUnit.MINUTES);   // 2026-10-10T09:30

3. Adding Hours to an Instant

An Instant is a point on the UTC time-line, for example the moment a token was issued. It has no hours or minutes fields, so it offers only plusSeconds(), plusMillis() and plusNanos() as shortcuts, and we add hours through a unit or a Duration.

Instant issued = Instant.parse("2026-10-10T09:30:00Z");
Instant accessExpiry = issued.plus(15, ChronoUnit.MINUTES);      // 2026-10-10T09:45:00Z
Instant refreshExpiry = issued.plus(Duration.ofHours(12));       // 2026-10-10T21:30:00Z
Instant skewed = issued.minusSeconds(30);                        // 2026-10-10T09:29:30Z
Instant nextDay = issued.plus(1, ChronoUnit.DAYS);               // 2026-10-11T09:30:00Z

The unit DAYS works on an Instant as a fixed 86,400 seconds, but months and years have no fixed length, so they throw an exception. For calendar units, we convert the instant to a ZonedDateTime first.

Instant tokenIssued = Instant.parse("2026-10-10T09:30:00Z");
Instant monthLater = tokenIssued.plus(1, ChronoUnit.MONTHS);         // UnsupportedTemporalTypeException: Unsupported unit: Months

4. Adding Hours Across a Daylight Saving Change

A ZonedDateTime adds hours, minutes and seconds on the real time-line, so plusHours(1) is always 60 elapsed minutes, even when the clock jumps. A LocalDateTime knows nothing about time zones, so it adds to the clock face and can produce a time that never existed in that zone.

Adding one hour at 01:30 on the New York spring-forward day gives 03:30 with ZonedDateTime and 02:30 with LocalDateTime
On March 8, 2026 New York skips 02:00 to 03:00, so one real hour after 01:30 is 03:30

In New York, clocks jumped from 02:00 to 03:00 on March 8, 2026. One hour after 01:30 was 03:30, which ZonedDateTime gets right, whereas LocalDateTime gives 02:30, a time that did not exist that night.

ZoneId newYork = ZoneId.of("America/New_York");
ZonedDateTime beforeJump = ZonedDateTime.of(2026, 3, 8, 1, 30, 0, 0, newYork);
ZonedDateTime oneHourLater = beforeJump.plusHours(1);           // 2026-03-08T03:30-04:00[America/New_York]
LocalDateTime naive = beforeJump.toLocalDateTime().plusHours(1); // 2026-03-08T02:30

In autumn the clock goes back, so 01:30 happens twice. On November 1, 2026, adding one hour to the first 01:30 gives the second 01:30 with a different offset. Adding 24 hours on the day before the change also lands one clock hour earlier than plusDays(1), because that day is 25 hours long.

ZoneId newYork = ZoneId.of("America/New_York");
ZonedDateTime firstPass = ZonedDateTime.of(2026, 11, 1, 1, 30, 0, 0, newYork);   // 2026-11-01T01:30-04:00[America/New_York]
ZonedDateTime secondPass = firstPass.plusHours(1);                                 // 2026-11-01T01:30-05:00[America/New_York]
ZonedDateTime evening = ZonedDateTime.of(2026, 10, 31, 18, 0, 0, 0, newYork);
ZonedDateTime plusDay = evening.plusDays(1);                    // 2026-11-01T18:00-05:00[America/New_York]
ZonedDateTime plus24h = evening.plusHours(24);                  // 2026-11-01T17:00-05:00[America/New_York]

Say a travel app sends a reminder three hours before a 04:00 flight on March 8 in New York. With ZonedDateTime.minusHours(3), the reminder goes out at 00:00, three real hours earlier. With LocalDateTime, it would go out at 01:00, only two real hours before departure. We store such times as Instant or ZonedDateTime and convert to local time only for display, as in converting dates between time zones.

ZoneId newYork = ZoneId.of("America/New_York");
ZonedDateTime departure = ZonedDateTime.of(2026, 3, 8, 4, 0, 0, 0, newYork);
ZonedDateTime reminder = departure.minusHours(3);               // 2026-03-08T00:00-05:00[America/New_York]
LocalDateTime wrongReminder = departure.toLocalDateTime().minusHours(3);   // 2026-03-08T01:00

5. Adding Time to a java.util.Date

A java.util.Date from an old API is an instant, so the conversion is one call each way. We add the time to the Instant and wrap the result again only when the old API needs it.

Date legacyStart = Date.from(Instant.parse("2026-10-10T09:30:00Z"));
Instant shifted = legacyStart.toInstant().plus(Duration.ofHours(2)).minusSeconds(15);   // 2026-10-10T11:29:45Z
Date legacyEnd = Date.from(shifted);
long endMillis = legacyEnd.getTime();                           // 1791631785000

The old Calendar.add(Calendar.HOUR_OF_DAY, 2) call did the same job, but it changed the Calendar object in place and depended on the JVM default time zone. New code keeps java.time types and converts only at the boundary.

6. Hours, Minutes and Seconds in Java FAQs

Daylight saving days and immutability cause most surprises after the first plusHours() call.

6.1. How do we add hours to the current time in Java?

We call ZonedDateTime.now(zone).plusHours(n), or Instant.now().plus(n, ChronoUnit.HOURS) when we store the value. The current time differs on every run, so the result is never a fixed value.

6.2. Is plusHours(24) the same as plusDays(1)?

Only on days without a daylight saving change. On a ZonedDateTime, plusDays(1) keeps the clock time and plusHours(24) keeps the elapsed time, so on November 1, 2026 in New York they differ by one hour, as section 4 shows.

6.3. How do we add hours to a LocalDate?

A LocalDate has no time, so plusHours() does not exist on it. We first add a time with atStartOfDay() or atTime(9, 0), which returns a LocalDateTime, and add hours to that.

6.4. How do we find the hours and minutes between two times?

We use Duration.between(start, end) and read toHours() and toMinutesPart(). The difference between two dates article covers it in detail.

6.5. Why does plusMinutes() not change my variable?

All java.time classes are immutable, so plusMinutes() returns a new object. We assign the result, for example end = end.plusMinutes(5);.

7. Conclusion

The plusHours(), plusMinutes(), plusSeconds() and plusNanos() methods and their minus versions work on every class with a time part. An Instant has only the seconds, milliseconds and nanoseconds versions, so we add hours and minutes to it with a ChronoUnit amount or a Duration.

For values that must follow real time, such as reminders and expiry times, we use ZonedDateTime or Instant, because LocalDateTime ignores daylight saving changes. For whole days, months and years, the add days, months and years article has the calendar rules, and the Java date and time guide lists the rest of the series.

8. References

Happy Learning !!

Source Code on Github

About Us

HowToDoInJava provides tutorials and how-to guides on Java and related technologies.

It also shares the best practices, algorithms & solutions and frequently asked interview questions.