Date & Time API

Manipulating Dates & Times

15 min Lesson 5 of 13

Manipulating Dates & Times

The java.time API is immutable by design. You never modify a LocalDate in place; every manipulation returns a brand-new object. This lesson covers the three families of manipulation methods — plus/minus, with, and TemporalAdjusters — and explains when each is the right tool.

Why Immutability Matters Here

Before java.time, the old java.util.Date and Calendar were mutable, which caused hard-to-track bugs: methods mutated shared state, and objects passed between threads needed synchronisation. The new API solves both problems. The trade-off is verbosity — every operation produces a new value — but modern JVMs optimise short-lived objects extremely well, so the performance cost is negligible in practice.

Key mental model: treat LocalDate, LocalDateTime, ZonedDateTime, and their siblings exactly like String. You never change a String; you produce a new one. Same here.

plus and minus — Moving Along the Timeline

The most common need is "give me a date N days/months/years from now". Every Temporal implementation exposes typed convenience methods and a generic form that accepts a TemporalAmount.

import java.time.LocalDate; import java.time.LocalDateTime; import java.time.Period; import java.time.Duration; public class PlusMinusDemo { public static void main(String[] args) { LocalDate today = LocalDate.of(2026, 6, 9); // typed convenience methods LocalDate nextWeek = today.plusDays(7); LocalDate nextMonth = today.plusMonths(1); LocalDate nextYear = today.plusYears(1); LocalDate lastWeek = today.minusDays(7); LocalDate lastQuarter = today.minusMonths(3); System.out.println("Today: " + today); System.out.println("Next week: " + nextWeek); System.out.println("Next month: " + nextMonth); System.out.println("Next year: " + nextYear); System.out.println("Last week: " + lastWeek); System.out.println("Last quarter: " + lastQuarter); // generic form with a TemporalAmount (Period or Duration) LocalDate in90Days = today.plus(Period.ofDays(90)); System.out.println("In 90 days: " + in90Days); // DateTime — plus supports Duration too LocalDateTime now = LocalDateTime.of(2026, 6, 9, 14, 30); LocalDateTime soon = now.plusHours(2).plusMinutes(45); System.out.println("Soon: " + soon); } }

Period models a date-based amount (years, months, days). Duration models a time-based amount (hours, minutes, seconds, nanos). You can pass either to the generic plus(TemporalAmount), but the receiving temporal must support the relevant fields — calling LocalDate.plus(Duration.ofHours(3)) throws an UnsupportedTemporalTypeException because a date has no hours.

Month-length surprises: LocalDate.of(2026, 1, 31).plusMonths(1) returns 2026-02-28, not 2026-03-03. Java.time clips to the last valid day of the target month (called end-of-month rule). This is the correct calendar behaviour, but if you later subtract the month back you may not get the original date. Design round-trip logic with this in mind.

with — Replacing a Specific Field

plus/minus are relative adjustments. with is an absolute replacement: "set the day-of-month to 1", "set the hour to 0", "set the year to 2030". It comes in typed overloads and a generic form.

import java.time.LocalDate; import java.time.LocalDateTime; import java.time.temporal.ChronoField; public class WithDemo { public static void main(String[] args) { LocalDate date = LocalDate.of(2026, 6, 15); // typed overloads LocalDate firstOfMonth = date.withDayOfMonth(1); LocalDate lastDayOfYear = date.withDayOfYear(365); // non-leap 2026 LocalDate sameMonthDay2030 = date.withYear(2030); System.out.println("First of month: " + firstOfMonth); System.out.println("Last day of year: " + lastDayOfYear); System.out.println("Same date in 2030: " + sameMonthDay2030); // generic form using ChronoField LocalDate withMonth = date.with(ChronoField.MONTH_OF_YEAR, 12); System.out.println("Same day, December: " + withMonth); // works on LocalDateTime too LocalDateTime noon = LocalDateTime.of(2026, 6, 15, 9, 45) .with(ChronoField.HOUR_OF_DAY, 12) .with(ChronoField.MINUTE_OF_HOUR, 0); System.out.println("Noon: " + noon); } }

The generic with(TemporalField, long) accepts any ChronoField constant, giving you fine-grained control — including niche fields like ALIGNED_WEEK_OF_MONTH or EPOCH_DAY. The typed overloads are preferred for clarity in most cases.

TemporalAdjusters — Business-Rule Adjustments

Many real-world needs cannot be expressed as "plus N units" or "set field to X": "the last Friday of this month", "the next business day", "the first Monday after a given date". The TemporalAdjusters factory class covers the common cases, and you can write your own.

import java.time.DayOfWeek; import java.time.LocalDate; import java.time.temporal.TemporalAdjusters; public class AdjustersDemo { public static void main(String[] args) { LocalDate date = LocalDate.of(2026, 6, 9); // a Tuesday LocalDate firstDay = date.with(TemporalAdjusters.firstDayOfMonth()); LocalDate lastDay = date.with(TemporalAdjusters.lastDayOfMonth()); LocalDate firstOfNext = date.with(TemporalAdjusters.firstDayOfNextMonth()); LocalDate nextFriday = date.with(TemporalAdjusters.next(DayOfWeek.FRIDAY)); LocalDate prevMonday = date.with(TemporalAdjusters.previous(DayOfWeek.MONDAY)); LocalDate firstMonday = date.with(TemporalAdjusters.firstInMonth(DayOfWeek.MONDAY)); LocalDate lastFriday = date.with(TemporalAdjusters.lastInMonth(DayOfWeek.FRIDAY)); System.out.println("First of month: " + firstDay); System.out.println("Last of month: " + lastDay); System.out.println("First of next month: " + firstOfNext); System.out.println("Next Friday: " + nextFriday); System.out.println("Previous Monday: " + prevMonday); System.out.println("First Monday in June: " + firstMonday); System.out.println("Last Friday in June: " + lastFriday); } }

Writing a Custom TemporalAdjuster

TemporalAdjuster is a functional interface with a single method adjustInto(Temporal). Because it is functional, you can implement it as a lambda. A classic example is a "next business day" adjuster that skips weekends.

import java.time.DayOfWeek; import java.time.LocalDate; import java.time.temporal.Temporal; import java.time.temporal.TemporalAdjuster; public class BusinessDayAdjuster { // skip Saturday and Sunday public static final TemporalAdjuster NEXT_BUSINESS_DAY = (Temporal temporal) -> { LocalDate date = LocalDate.from(temporal); do { date = date.plusDays(1); } while (date.getDayOfWeek() == DayOfWeek.SATURDAY || date.getDayOfWeek() == DayOfWeek.SUNDAY); return date; }; public static void main(String[] args) { LocalDate friday = LocalDate.of(2026, 6, 5); // Friday LocalDate saturday = LocalDate.of(2026, 6, 6); // Saturday LocalDate sunday = LocalDate.of(2026, 6, 7); // Sunday System.out.println("After Friday: " + friday.with(NEXT_BUSINESS_DAY)); // Monday 2026-06-08 System.out.println("After Saturday: " + saturday.with(NEXT_BUSINESS_DAY)); // Monday 2026-06-08 System.out.println("After Sunday: " + sunday.with(NEXT_BUSINESS_DAY)); // Monday 2026-06-08 } }
Compose adjusters for complex rules. A payment-due-date rule might be "last calendar day of the month, but if that is a weekend move to the previous Friday". Compose two adjusters: lastDayOfMonth() then your weekend-rollback adjuster. Keep each adjuster small and single-purpose.

Choosing the Right Tool

  • Use plus/minus when moving a fixed amount along the timeline (7 days from now, 3 months earlier).
  • Use with when you need to pin a specific field to a concrete value (first day of this month, midnight of this day).
  • Use TemporalAdjusters when the rule is calendar- or business-logic-based and cannot be expressed as a fixed offset or field assignment.

Chaining Manipulations

Because every method returns a new object, you can chain calls fluently. Readability is the only limit.

import java.time.DayOfWeek; import java.time.LocalDateTime; import java.time.temporal.TemporalAdjusters; public class ChainingDemo { public static void main(String[] args) { // "the start of the last business day of next month" LocalDateTime result = LocalDateTime.now() .plusMonths(1) .with(TemporalAdjusters.lastDayOfMonth()) .with(TemporalAdjusters.previousOrSame(DayOfWeek.FRIDAY)) .withHour(9) .withMinute(0) .withSecond(0) .withNano(0); System.out.println(result); } }
previousOrSame vs previous: TemporalAdjusters.previousOrSame(DayOfWeek.FRIDAY) returns the date itself if it already is a Friday; previous(DayOfWeek.FRIDAY) always moves backwards at least one day. Pick the right variant so your rule handles the edge case correctly.

Summary

java.time manipulation is immutable and composable. plus/minus move along the timeline by a fixed amount; with pins a field to an absolute value; TemporalAdjusters express higher-level calendar rules and are extensible via the TemporalAdjuster functional interface. Together these three families cover virtually every date-manipulation scenario you will encounter in production code.