Date Basics

Su Suriya Ravichandran Updated 14 Nov 2026
7 min read ·Lesson 113 of 133

Dates and times appear in almost every application: order timestamps, due dates, subscription renewals, meeting schedules, log entries, ages, and deadlines. Java's modern date and time API, the java.time package introduced in Java 8, provides clear, immutable, thread-safe classes for each kind of temporal value. This lesson explains how the API is organized, which class to use for which job, and the basic operations they all share. The following lessons cover the individual classes in detail.

The Old API and Why It Was Replaced

Before Java 8, programs used java.util.Date, java.util.Calendar, and java.text.SimpleDateFormat. They are still found in older code, but they have well-known problems:

ProblemExample
Mutable objectsA Date passed to a method could be changed by that method
Confusing designDate represents an instant, yet its toString() shows a local date and time
Zero-based monthsIn Calendar, January is month 0
Not thread-safeA shared SimpleDateFormat produces corrupted results under concurrent use
No separate typesNo way to represent "a date without a time" or "a time without a date"

java.time fixes all of these. New code should use it exclusively, converting to legacy types only when an older library requires them.

The Core Classes

Each class represents a different concept. Choosing the right one is the most important decision:

ClassRepresentsExample valueTypical use
LocalDateA date without time or zone2026-11-12Birthdays, due dates, holidays
LocalTimeA time of day without date or zone10:00Opening hours, alarm times
LocalDateTimeDate and time without zone2026-11-12T10:00A form's "appointment" field before a zone is known
ZonedDateTimeDate and time in a specific time zone2026-11-12T10:00+05:30[Asia/Kolkata]Meetings, flights, anything scheduled for a place
OffsetDateTimeDate and time with a fixed UTC offset2026-11-12T10:00+05:30Timestamps in APIs and databases
InstantA point on the global timeline (UTC)2026-11-12T04:30:00ZLogging, event timestamps, storing "when it happened"
DurationAn amount of time in seconds and nanosecondsPT8H30MElapsed time, timeouts
PeriodAn amount of time in years, months, and daysP1Y2M3DAges, subscription lengths
ZoneIdA time zone with its rulesAsia/KolkataConverting between local times

"Local" means no time zone: a LocalDate of 2026-11-12 is "the 12th of November" wherever you are, not a specific moment. An Instant is the opposite: one exact moment, the same everywhere in the world, but meaningless to a person until it is shown in some time zone.

Getting the Current Date and Time

LocalDate today = LocalDate.now();
LocalTime time = LocalTime.now();
LocalDateTime dt = LocalDateTime.now();
ZonedDateTime zdt = ZonedDateTime.now();               // in the JVM's default zone
ZonedDateTime tokyo = ZonedDateTime.now(ZoneId.of("Asia/Tokyo"));
Instant now = Instant.now();

With a fixed moment (04:30 UTC on 12 November 2026) and the Asia/Kolkata zone, these produce:

2026-11-12 | 10:00 | 2026-11-12T10:00
2026-11-12T10:00+05:30[Asia/Kolkata] | 2026-11-12T04:30:00Z

All java.time types print in ISO-8601 format by default (yyyy-MM-dd, HH:mm:ss, T between date and time, Z for UTC), which is also the standard format for JSON and APIs.

Testable time with Clock

Code that calls now() directly is hard to test, because the result changes every run. Every now() method also accepts a java.time.Clock. Production code passes Clock.systemDefaultZone(), and tests pass a fixed clock:

Clock fixed = Clock.fixed(Instant.parse("2026-11-12T04:30:00Z"), ZoneId.of("Asia/Kolkata"));
LocalDate.now(fixed);     // always 2026-11-12

Creating Specific Dates and Times

LocalDate d = LocalDate.of(2026, 11, 12);                // months are 1-12
LocalDate d2 = LocalDate.of(2026, Month.NOVEMBER, 12);
LocalTime t = LocalTime.of(9, 30);
LocalDateTime dt = LocalDateTime.of(d, t);
LocalDate parsed = LocalDate.parse("2026-11-12");        // ISO text
Instant i = Instant.parse("2026-11-12T04:30:00Z");

Invalid values are rejected immediately:

LocalDate.of(2026, 2, 30);    // DateTimeException: Invalid date 'FEBRUARY 30'

Immutability

Every java.time object is immutable. Methods that "change" a value return a new object:

LocalDate d = LocalDate.of(2026, 1, 31);
d.plusDays(1);                      // result discarded
System.out.println(d);              // 2026-01-31: unchanged

LocalDate next = d.plusDays(1);     // 2026-02-01

Immutability makes these objects safe to share between threads and to use as map keys. As with String, always keep the result.

Common Operations

All the main classes share a consistent method vocabulary:

KindMethodsExample
Createof, parse, now, fromLocalDate.of(2026, 11, 12)
Read a fieldgetYear, getMonth, getDayOfWeek, getHourd.getDayOfWeek() → SATURDAY
Add or subtractplusDays, minusMonths, plus(Duration)d.plusMonths(1)
Replace a fieldwithYear, withDayOfMonth, with(adjuster)d.withDayOfMonth(1)
CompareisBefore, isAfter, isEqual, compareTodue.isBefore(today)
ConvertatTime, atZone, toLocalDate, toInstantd.atStartOfDay(zone)
Formatformat(DateTimeFormatter)d.format(...)

Date arithmetic follows calendar rules:

LocalDate.of(2026, 1, 31).plusMonths(1);    // 2026-02-28: clamped to the last valid day
LocalDate.of(2028, 1, 1).isLeapYear();      // true

Measuring Time Between Values

LocalDate birthday = LocalDate.of(1998, Month.FEBRUARY, 14);
LocalDate today = LocalDate.of(2026, 11, 12);

Period.between(birthday, today).getYears();                         // 28 (age)
ChronoUnit.DAYS.between(today, LocalDate.of(2027, 2, 14));          // 94
Duration.between(LocalTime.of(9, 15), LocalTime.of(17, 45));        // PT8H30M

Period measures calendar-based amounts (years, months, days); Duration measures exact time (hours, minutes, seconds). ChronoUnit.X.between returns a single count in one unit.

Converting to and from Legacy Types

Date legacy = Date.from(Instant.now());                  // java.time → java.util.Date
Instant instant = legacy.toInstant();                    // java.util.Date → java.time
LocalDate fromSql = sqlDate.toLocalDate();               // java.sql.Date → LocalDate

Modern JDBC drivers also read and write LocalDate, LocalDateTime, and OffsetDateTime directly, so conversions are needed mainly at the boundary with older libraries.

Choosing the Right Type

  • Something that happened at an exact moment (a payment, a login, a log entry): store an Instant (or OffsetDateTime in UTC).
  • Something that happens at a local time in a place (a class at 7:30 pm in Chennai): ZonedDateTime, or a LocalDateTime plus a ZoneId stored separately.
  • A calendar date with no time (a birthday, an invoice due date): LocalDate.
  • A recurring time of day (shop opens at 9:00): LocalTime.
  • How long something takes: Duration. How many calendar units: Period.

A frequent bug is storing a moment as a LocalDateTime: the value no longer says which time zone it belongs to, and conversions later become guesswork.

Practical Example: A Webinar Across Time Zones

A webinar is scheduled at 7:30 pm Indian time. Registrants around the world need to see the start time in their own zones, and the system needs to know how long remains and when to send a reminder.

import java.time.*;
import java.time.format.DateTimeFormatter;
import java.time.temporal.ChronoUnit;
import java.util.List;

public class Webinar {
    public static void main(String[] args) {
        Clock clock = Clock.fixed(Instant.parse("2026-11-12T04:30:00Z"), ZoneId.of("Asia/Kolkata"));

        ZonedDateTime start = ZonedDateTime.of(LocalDate.of(2026, 11, 20), LocalTime.of(19, 30), ZoneId.of("Asia/Kolkata"));
        Duration length = Duration.ofMinutes(90);
        Instant startInstant = start.toInstant();

        DateTimeFormatter fmt = DateTimeFormatter.ofPattern("EEE dd MMM, hh:mm a (zzz)");
        for (String zone : List.of("Asia/Kolkata", "Europe/London", "America/New_York", "Asia/Tokyo")) {
            ZonedDateTime local = startInstant.atZone(ZoneId.of(zone));
            System.out.printf("%-17s %s%n", zone, local.format(fmt));
        }

        LocalDate today = LocalDate.now(clock);
        long daysLeft = ChronoUnit.DAYS.between(today, start.toLocalDate());
        Duration untilStart = Duration.between(Instant.now(clock), startInstant);
        System.out.println("Today: " + today + " (" + today.getDayOfWeek() + ")");
        System.out.println("Days left: " + daysLeft + ", exactly " + untilStart.toDays() + "d " + untilStart.toHoursPart() + "h");
        System.out.println("Ends at: " + start.plus(length).toLocalTime());
        System.out.println("Reminder date: " + start.toLocalDate().minusDays(1));
    }
}

Output (with an English default locale):

Asia/Kolkata      Fri 20 Nov, 07:30 PM (IST)
Europe/London     Fri 20 Nov, 02:00 PM (GMT)
America/New_York  Fri 20 Nov, 09:00 AM (EST)
Asia/Tokyo        Fri 20 Nov, 11:00 PM (JST)
Today: 2026-11-12 (THURSDAY)
Days left: 8, exactly 8d 9h
Ends at: 21:00
Reminder date: 2026-11-19

Each class plays its own role:

  • ZonedDateTime holds the scheduled start, because the event happens at a local time in a specific zone.
  • Instant converts that start into one global moment. atZone then shows that moment in each attendee's time zone, applying every zone's current offset rules: London is five and a half hours behind India in November, and New York ten and a half.
  • LocalDate and ChronoUnit.DAYS count calendar days remaining.
  • Duration gives the exact time remaining.
  • plus(length) and minusDays compute the end time and the reminder date.

Using a fixed Clock makes the program's output the same on every run, which is exactly how such logic is unit-tested.

0 Comments

Reviewed before they appear

No comments yet.

Java
Java Introduction Java Features Java Syntax Java Comments Java Statements Java Output Java Variables Java Data Types Java Type Casting Java Operators Java Strings Java Numbers Java Math Java Booleans Java User Input Java Conditions Java If Else Java Switch Java While Loop Java For Loop Java Break Java Continue Java Arrays Java Methods Java Parameters Java Return Values Java Scope Java Recursion Install Java Install JDK Java IDEs Java JDK Java JRE Java JVM Java Compiler Java File Compile Java Run Java JShell Basics Java Packages Java OOP Java Classes Java Objects Class Methods Constructors Constructor Overload this Keyword static Keyword final Keyword Encapsulation Inheritance Polymorphism Abstraction Access Modifiers super Keyword Method Overload Method Override Instanceof Operator Nested Classes Inner Classes Anonymous Classes Enums Records Array Basics Array Length Loop Arrays Multidimensional Arrays Array Copy Array Sorting Array Search ArrayList LinkedList HashSet TreeSet HashMap TreeMap Queue Interface Deque Interface Stack Class Iterator List Interface Set Interface Map Interface Collections Class Comparable Comparator Generics Basics Generic Classes Generic Methods Wildcards String Basics String Length String Methods String Concatenation String Compare String Search String Replace String Split String Format StringBuilder StringBuffer Text Blocks Regular Expressions Number Types Integer Class Float Class Double Class BigInteger BigDecimal Math Class Random Numbers Number Format Date Basics LocalDate LocalTime LocalDateTime ZonedDateTime Date Formatting Date Parsing Period Class Duration Class Java Instant Class Java Exception Basics Java try...catch Java Multiple Catch Blocks Java finally Block Java throw Keyword Java throws Keyword Java Checked Exceptions Java Unchecked Exceptions Java Custom Exceptions Java try-with-resources Java Exception Chaining
Ask about this post
AI Ask about this post

Ask questions about Date Basics and get answers drawn from it.

Signed-in readers only.