Java Dates and Times
Choose the right type for a calendar date, a local appointment, or a global moment. Parse and format deliberately, understand calendar arithmetic, and build a due-date calculator whose tests do not depend on today's real date.
Before you start: review imports and API documentation, constructors, and String value comparison. Each example is a complete runnable Java 21 Main.java program with no input required. Fixed dates and explicit zones make the displayed results reproducible.
What you will be able to do
- Choose a date/time type based on whether the value represents a calendar date, wall-clock reading, or global moment.
- Retain results from immutable operations and compare date values correctly.
- Parse real dates strictly and format text with explicit patterns and locale.
- Distinguish calendar arithmetic and Period from elapsed Duration.
- Convert one Instant between explicit zones and recognize daylight-saving gaps and overlaps.
- Calculate a date-only due date and test overdue boundaries using a supplied Clock.
Start with what the value means
A birthday, an appointment on a wall calendar, and the exact moment a server receives a request are different kinds of information. Storing all three as an unexplained String or timestamp hides that difference. Start by asking which facts are present and which facts are deliberately absent.
LocalDate holds an ISO calendar date; LocalTime holds a time of day. LocalDateTime combines those fields but still has no zone or UTC offset. The first example's 09:30 appointment could occur in many places, so those fields alone do not identify one global moment.
Instant represents a moment on the timeline. ZonedDateTime combines local fields with a named zone and resolved offset, while OffsetDateTime carries an offset without a region's full rules. We will use the modern java.time APIs rather than start new code with legacy Date or Calendar.
Main.java · A date and a wall-clock time are not a global timestamp
Predict the result, edit Main.java, then run your program.
Standard input · optional
Select Run code to compile and execute this program.
2026-10-23
09:30
2026-10-23T09:30
Date operations return new values instead of changing the original
The java.time value types used here are immutable. start.plusDays(14) calculates another date but does not alter start. The intentionally ignored first call demonstrates a common bug: a valid calculation can have no visible effect if its returned value is discarded.
Assign the result to due, or explicitly reassign a variable when that is the intended design. The original start still represents 23 October while due represents 6 November. Immutability keeps shared references predictable; it does not stop a variable from being reassigned to another value.
Use equals for value equality and isBefore or isAfter for chronological questions. Do not use == to decide whether separately obtained date objects represent the same date. Two distinct object references can represent the same calendar value, and reference identity is not the contract you need.
Main.java · Keep the returned date and compare its value
Predict the result, edit Main.java, then run your program.
Standard input · optional
Select Run code to compile and execute this program.
Start: 2026-10-23
Due: 2026-11-06
Same date: true
Due after start: true
Parsing reads a date; formatting creates text for a reader
LocalDate.parse accepts an ISO local date such as 2026-10-23. For a different input contract, provide an explicit formatter. The example deliberately reads day/month/year and uses ResolverStyle.STRICT so an impossible date fails rather than being adjusted to a nearby valid date.
Pattern letters are case-sensitive: MM means month, mm means minute, and uuuu is a proleptic year appropriate for this strict date pattern. yyyy is year-of-era and can need era information during strict resolution; YYYY is a week-based year and is not a substitute for a calendar year.
Formatting does not change the stored date. The English month abbreviation is requested explicitly with Locale.ENGLISH instead of depending on the server's language. DateTimeFormatter is immutable too. Invalid date text throws DateTimeParseException; a caller should report that input error rather than invent a fallback due date.
Main.java · Parse strictly, then format for a reader
Predict the result, edit Main.java, then run your program.
Standard input · optional
Select Run code to compile and execute this program.
2024-02-29
29 Feb 2024
Rejected: 29/02/2025 is not a real calendar date.
Trace the leap-day example
- The input pattern reads day, month, and proleptic year.
- Strict resolution accepts 29 February 2024 because it is a leap year.
- The display formatter produces an English month abbreviation without changing the date.
- 29 February 2025 fails and reaches the specific parsing-error handler.
Try 31/04/2026. April has only thirty days, so it should also fail. Do not switch to lenient parsing merely to make invalid input disappear.
Choose calendar units deliberately, especially near month ends
The example starts on 31 January in a leap year. plusMonths(1) resolves to the last valid day of February, while plusDays(30) reaches 1 March. Those operations answer different questions, even when people loosely call both of them adding a month.
Adding one year to 29 February similarly resolves to 28 February in a non-leap year. That default arithmetic rule is not automatically the correct birthday or billing policy for every application. Also, adding a month and then subtracting one need not recover the original day after a month-end adjustment.
Period represents calendar years, months, and days; Duration represents seconds and nanoseconds. Period.between returns calendar components rather than a universal total-day counter, and getDays returns only its day component. Use ChronoUnit.DAYS.between on LocalDate values when the question is the signed number of calendar dates between them.
Main.java · Calendar arithmetic handles month and leap-year boundaries
Predict the result, edit Main.java, then run your program.
Standard input · optional
Select Run code to compile and execute this program.
2024-02-29
2024-03-01
2025-02-28
Total days: 29
Calendar period: P29D
Use Instant and Duration for elapsed time
The Instant input ends with Z, indicating UTC in this timestamp representation. Adding a ninety-minute Duration identifies a later moment regardless of which user's zone is used to display it. Duration.between measures the elapsed amount between the two instants.
Instant is not a local appointment, and a LocalDate cannot supply elapsed hours by itself because it has no time or zone. Do not call Duration.between on two bare dates and expect Java to invent midnight or a location. Choose the required missing information first if the domain actually needs a timestamp.
Duration.toMinutes gives whole minutes, not a formatted clock time; a negative interval can also be meaningful when the arguments are reversed. For these examples we use whole-minute fixed inputs. Performance benchmarking needs a suitable monotonic time source rather than assuming a wall clock can never be adjusted.
Main.java · Measure elapsed time between two moments
Predict the result, edit Main.java, then run your program.
Standard input · optional
Select Run code to compile and execute this program.
2026-10-23T09:00:00Z
2026-10-23T10:30:00Z
PT1H30M
Minutes: 90
A zone changes the local reading, not the identity of the moment
At one fixed instant, UTC is still on 6 November while Tokyo is already on 7 November. withZoneSameInstant preserves the moment and computes the new local fields. Comparing toInstant values therefore says true while comparing only their LocalDate portions says false.
A ZoneId such as Europe/Paris describes regional rules, not just a permanently fixed offset. Region rules may change as time-zone data is updated. Use an explicit relevant region for a local business policy, or a deliberate fixed offset where that is what the input actually promises.
Do not assume the server's default zone is the customer's zone. A LocalDateTime needs an offset or zone before it can be resolved to an Instant, and that resolution can involve daylight-saving ambiguity. A date-only deadline should remain a date unless the product defines how it becomes a precise cutoff moment.
Main.java · One instant can have different local dates
Predict the result, edit Main.java, then run your program.
Standard input · optional
Select Run code to compile and execute this program.
2026-11-06T23:30Z[UTC]
2026-11-07T08:30+09:00[Asia/Tokyo]
Same instant: true
Same local date: false
Separate a calendar day from a fixed 24-hour duration
The Paris example crosses the spring clock change. Adding Period.ofDays(1) preserves noon on the next calendar date, but only twenty-three elapsed hours pass. Adding Duration.ofHours(24) instead reaches 13:00 on that date. Calendar arithmetic and elapsed arithmetic are intentionally different.
Some local clock readings do not exist when clocks move forward, and some occur twice when clocks move backward. LocalDateTime.atZone normally shifts a reading in a gap forward by the gap length and chooses the earlier offset in an overlap; it is not strict rejection of every ambiguous appointment.
If a booking policy must reject nonexistent times or ask which repeated time was intended, inspect the zone's valid offsets and define that policy explicitly. withLaterOffsetAtOverlap can select the later occurrence when appropriate. We explain the defaults here without pretending this short lesson implements a complete scheduling system.
Main.java · A calendar day is not always 24 elapsed hours
Predict the result, edit Main.java, then run your program.
Standard input · optional
Select Run code to compile and execute this program.
2026-03-29T12:00+02:00[Europe/Paris]
2026-03-29T13:00+02:00[Europe/Paris]
Calendar-day hours: 23
Duration hours: 24
Why do noon and 13:00 both appear?
- The starting local time is noon before the spring clock change.
- A calendar-day addition aims for noon on the next local date.
- The clock jumps forward, so that path spans only 23 elapsed hours.
- A fixed 24-hour duration therefore reaches 13:00 instead.
Neither operation is wrong. Pick the one matching the promise your application makes: a calendar schedule or an elapsed interval.
Make today's date a dependency you can control in tests
LocalDate.now() silently uses the system clock and default zone. Its result changes over time and can differ between machines. Clock.fixed lets the example supply both one known instant and the zone used to interpret that instant, so the output remains reproducible.
The loan is due on 6 November. Our date-only rule says it becomes overdue when the chosen local date is after the due date, not on the due date itself. The fixed UTC clock therefore says false while the Tokyo clock says true at the same instant. A real application must choose its authoritative business zone rather than let callers arbitrarily change policy.
A supplied Clock is ordinary dependency injection; no framework is required. Production can supply an appropriate system clock and tests can supply fixed clocks. The loan's query methods do not alter the loan, send reminders, run timers, or persist anything; they calculate answers for the supplied clock.
Main.java · Supply a fixed clock instead of guessing today's date
Predict the result, edit Main.java, then run your program.
Standard input · optional
Select Run code to compile and execute this program.
Due: 2026-11-06
UTC today: 2026-11-06
UTC overdue: false
Tokyo today: 2026-11-07
Tokyo overdue: true
Read the overdue boundary step by step
- The loan starts on 23 October and adds fourteen calendar days.
- The stored due date is 6 November, with no time or zone.
- The fixed instant is still 6 November in UTC but already 7 November in Tokyo.
- The date-only rule asks whether that chosen local today is strictly after 6 November.
This demonstrates why selecting the business zone is part of the policy. It is not permission for a real borrower to select whichever zone avoids a deadline.
Define a due-date contract before writing the lab
A loan begins on a LocalDate and lasts one to 365 calendar days. Its due date is start.plusDays(days), so a fourteen-day loan from 23 October is due on 6 November. The start date is not counted as day one in an inclusive counting scheme; weekends and holidays are not skipped.
daysRemaining returns the signed calendar-day distance from today to due. A result of zero means due today, a positive result means dates remain, and a negative result means the date has passed. This number is not a promise about remaining elapsed hours or the exact instant of a notification.
Validation rejects missing starts, missing clocks, and out-of-policy loan lengths. The parser rejects blank input and lets invalid ISO date text surface as DateTimeParseException. Calendar overflow near LocalDate's supported limits remains a DateTimeException rather than being silently clamped to an unrelated due date.
Test the day before, the due date, and the day after
Use fixed clocks to test all three boundaries without waiting for real time to pass. Verify overdue is false before and on the due date, true after it, and that daysRemaining has the matching positive, zero, and negative values. Repeat the queries and confirm the due date never changes.
Include leap days, month ends, year ends, invalid text, and both accepted loan-length boundaries. Change the JVM default zone or locale in a test and confirm the explicitly configured examples still behave the same. A calendar date and a timestamp should never be compared by guessing a hidden zone.
Finally distinguish an ignored immutable result from a parsing exception or a compile-time type mismatch. Those problems need different repairs: keep the returned value, fix or reject the input, or select a compatible type. Removing validation or relying on the machine's current date only hides the underlying mistake.
Repair exercise: the calculation was discarded
The program compiles, but the printed due date is still the start date. Keep the fourteen-day calculation's result instead of discarding it. Do not change the input date or the required loan length.
Main.java · Find the ignored immutable result
Predict the result, edit Main.java, then run your program.
Standard input · optional
Select Run code to compile and execute this program.
The starter compiles but currently prints:
Due: 2026-10-23
Reveal the explained repair
plusDays returns a new immutable date. Reassigning due preserves that returned value. The original expression was legal Java, so compilation alone could not reveal the incorrect business result; comparing expected output exposes the error.
Main.java · Keep the calculated date
Predict the result, edit Main.java, then run your program.
Standard input · optional
Select Run code to compile and execute this program.
Due: 2026-11-06
Independent lab: a date-only loan with a testable clock
Build a date-only loan calculator with a supplied clock. Calculate due dates, strict input parsing, signed remaining calendar days, and the overdue boundary. Use fixed UTC and Tokyo clocks to make the same-instant/different-date distinction visible. Nothing is scheduled, sent, or saved.
- Implement Loan(LocalDate start, int days), storing a private final dueDate = start.plusDays(days). Reject null start with Start date must not be null. and lengths outside 1..365 with Loan days must be between 1 and 365., using IllegalArgumentException.
- Implement static parseStart(String): reject null/blank with IllegalArgumentException and Start date must not be blank.; strip accepted text and parse with DateTimeFormatter.ISO_LOCAL_DATE. Let impossible or malformed dates produce DateTimeParseException.
- Expose dueDate(), isOverdue(Clock), and daysRemaining(Clock). Reject null clocks with IllegalArgumentException and Clock must not be null. Derive today from LocalDate.now(clock), not from the machine's default clock.
- Define overdue as today strictly after dueDate. Define remaining days with ChronoUnit.DAYS.between(today, dueDate), preserving negative results. The model is calendar-day based, includes weekends, and does not calculate a precise timestamp cutoff.
- In main parse padded 2026-10-23, create a fourteen-day loan, format the due date with dd MMM uuuu and Locale.ENGLISH, and use Clock.fixed at 2026-11-06T23:30:00Z in UTC and Asia/Tokyo. Print the supplied dates, overdue results, and remaining-day counts.
- Catch the rejected 2025-02-29 parse and print the specified friendly message. Test boundary dates, invalid lengths, missing input, leap days, immutable repeated queries, and supported-range overflow without substituting invented dates.
The due date remains valid throughout its local calendar date under this rule. The completed program should produce:
Start: 2026-10-23
Due: 06 Nov 2026
UTC today: 2026-11-06
UTC overdue: false
UTC days remaining: 0
Tokyo today: 2026-11-07
Tokyo overdue: true
Tokyo days remaining: -1
Rejected date: 2025-02-29 is not a valid ISO date.
Main.java · Build a testable loan calculator
Predict the result, edit Main.java, then run your program.
Standard input · optional
Select Run code to compile and execute this program.
Stage 1 hints: keep dates as dates
Validate the start and 1..365 length before calculating start.plusDays(days). Store the returned LocalDate in a private final field. For accepted input, strip surrounding whitespace before ISO parsing; do not create a fallback date when parsing fails.
Stage 2 hints: make today explicit
Use a helper that rejects null Clock and returns LocalDate.now(clock). Compare with isAfter for overdue. Use ChronoUnit.DAYS.between(today, dueDate) for the signed calendar distance, not Duration on dates or guessed milliseconds.
Stage 3 hints: prove the boundary
Create one fixed instant and two clocks with explicit zones. Format the due date with an explicit English locale. Verify before/on/after due using additional fixed clocks, and confirm repeated reads leave the stored due date unchanged.
Reveal the complete explained solution
Main.java · Complete date-only loan calculator
Predict the result, edit Main.java, then run your program.
Standard input · optional
Select Run code to compile and execute this program.
The immutable due date is derived from the supplied start, not the wall clock. Both clocks share one instant but produce different local dates. The due-day boundary is explicit, so zero remaining days is still not overdue. Strict parsing rejects an impossible date instead of adjusting it or silently starting a different loan.
Test changes and boundaries
- Use fixed clocks one day before, on, and one day after due. Expect remaining days 1, 0, -1 and overdue false, false, true. Repeat queries and verify the stored due date is unchanged.
- Test leap-day starts, February boundaries, December-to-January rollover, and loan lengths 1 and 365. Reject 0, negative lengths, 366, null start, and null clocks with the documented messages.
- Accept valid padded ISO dates and reject null/blank, malformed dates, and impossible days. Test strict custom parsing separately so invalid input is not normalized into a different real date.
- Compare the UTC and Tokyo clocks at the same instant, and repeat with changed JVM defaults. Explicit zone and locale choices must control the result, not hidden environment assumptions.
- Verify calendar-day versus duration arithmetic across a known daylight-saving boundary. Inspect gap/overlap behavior before treating a local appointment as a unique instant, and confirm overflow fails rather than returning an invented date.
Common mistakes and how to diagnose them
Ignoring plusDays or another immutable result
The original date is unchanged. Store the returned value in a new variable or explicitly reassign the existing variable when that is the intended calculation.
Using LocalDateTime as if it were already UTC
It contains no offset or zone. Supply the required information and ambiguity policy before converting to a global Instant.
Using MM, mm, yyyy, and YYYY interchangeably
Pattern case and year semantics matter. Use the documented dd/MM/uuuu strict pattern here, and specify a display locale when month names appear.
Treating one calendar day as exactly 24 elapsed hours
Regional clock changes can make a local calendar day shorter or longer. Choose Period or calendar arithmetic for date-based rules and Duration for fixed elapsed amounts.
Making tests depend on today's real date or server defaults
Supply a fixed Clock with an explicit zone. Otherwise midnight, deployment location, and language settings can change expected results unexpectedly.
Silently repairing invalid input or confusing components with totals
Reject impossible dates deliberately. Period.getDays is a component, not a general total-day distance; use the operation matching the question rather than converting everything to guessed milliseconds.
Frequently asked questions
Which type should I use for a birthday or due date?
Usually LocalDate when the rule is about a calendar date without a precise time. Do not invent a UTC midnight timestamp unless the product actually defines that conversion and its zone.
Does LocalDateTime include my computer's zone?
No. It contains date and time fields without an offset or region. Applying a ZoneId is an explicit operation and may need a policy for missing or repeated local times.
Why use uuuu and strict parsing?
uuuu expresses the proleptic year needed by this strict calendar pattern. ResolverStyle.STRICT rejects invalid combinations such as 29 February in a non-leap year instead of adjusting the date.
Are Period and Duration interchangeable?
No. Period models calendar amounts; Duration models seconds and nanoseconds. Across a clock change, advancing one local date and advancing 24 elapsed hours can produce different local times.
Can two places have different dates at the same instant?
Yes. The fixed UTC and Tokyo example demonstrates this near midnight. Choose the business zone explicitly when interpreting a date-only overdue policy.
Does the loan example send reminders or enforce a real deadline?
No. It only calculates in-memory results for supplied values and clocks. Scheduling, persistence, authoritative zone selection, notifications, and production deadline enforcement remain separate responsibilities.
Calendar facts and global moments are different values
Choose a type that matches the information available. Keep immutable results, parse real dates strictly, format with explicit locale, and make zone assumptions visible. Period and Duration serve different arithmetic promises. A supplied Clock turns shifting real-world time into repeatable tests.
Explain why a LocalDate is not midnight UTC, why adding a month differs from adding thirty days, and why the same instant can have different local dates. Next: errors and debugging, where you will distinguish compiler failures, runtime exceptions, and incorrect results.
Can you choose the right date, moment, and clock?
Answer six questions and read the explanations. Quiz answers are temporary and do not save course progress.
References and related learning
The loan examples, explanations, and exercises are original SovranCode material. These official Java 21 API references document the relevant calendar, formatting, and zone behavior.
- Java 21 LocalDate: calendar dates and arithmetic
- Java 21 LocalDateTime: local fields without a zone
- Java 21 ZonedDateTime: offsets, gaps, and overlaps
- Java 21 DateTimeFormatter: patterns and resolution
- Java 21 Clock: explicit time dependencies
- Java 21 Period: calendar amounts
- Java 21 Duration: elapsed amounts
Review API documentation · Review encapsulation · Java course roadmap.
Last updated: