Java Date and Time

โšก Smart Summary

Java Date and Time handling combines the legacy java.util.Date and SimpleDateFormat classes with the modern java.time API introduced in Java 8, giving developers immutable, thread-safe types for accurate calendar work.

  • ๐Ÿ•’ Legacy Date: The java.util.Date class stores an instant in milliseconds and prints a default string through toString.
  • ๐Ÿ”ค Pattern Letters: SimpleDateFormat uses letters such as yyyy, MM, dd, HH, mm, ss, and zzz to define custom output formats.
  • โš–๏ธ Comparison: The compareTo method returns a negative, zero, or positive integer to order two Date instances chronologically.
  • ๐Ÿ†• Modern API: LocalDate, LocalDateTime, ZonedDateTime, and Instant in java.time replace Date and Calendar with immutable objects.
  • ๐Ÿงต Thread Safety: DateTimeFormatter is immutable and safe to share, while SimpleDateFormat needs synchronization or a ThreadLocal wrapper.
  • ๐Ÿ” Interoperability: Convert between the old and new APIs using Date.toInstant, Date.from, and Calendar.toInstant helpers.

Java Date and Time

Handling dates and times in Java is a routine part of building scheduling logic, log timestamps, reports, and audit trails. Before writing any code, it helps to know the parameters that make up a date:

  • The year (in two or four digits).
  • The month (as two digits, as the first three letters of the month, or as the full month name).
  • The date (the actual day-of-month number).
  • The day (the day name at the given date, such as Sun, Mon, Tue).

Java exposes many other parameters that describe a moment on the timeline, including era, week-in-year, time zone, and offset. The sections below cover each of them with runnable examples.

How to use Date in Java

Display Date in Java

Java provides a Date class in the java.util package. The class stores a specific instant in time to millisecond precision and offers helper methods to print, compare, and format that instant. To get the current date and time, instantiate the class with its default constructor:

import java.util.Date;

class Date_Ex1 {
    public static void main(String args[]) {
        // Instantiate a Date object by invoking its constructor
        Date objDate = new Date();
        // Display the Date and Time using toString()
        System.out.println(objDate.toString());
    }
}

Output:

Wed Nov 29 06:36:22 UTC 2017

The example prints the date in the default toString format. To display the same instant in a different pattern, you first need to understand the formatting letters that Java uses.

SimpleDateFormat: Parse and Format Dates

The SimpleDateFormat class in java.text converts a Date object into a formatted string and parses a formatted string back into a Date. It uses pattern letters that map to a date or time component. The table below lists every supported letter:

LetterDate or Time ComponentExample
GEra designatorAD
yYear2026
MMonth in yearJuly or Jul or 07
wWeek in year27
WWeek in month2
DDay in year189
dDay in month10
FDay of week in month2
EDay name in weekTuesday or Tue
uDay number of week (1 = Monday, 7 = Sunday)1
aAM or PM markerPM
HHour in day (0-23)0
kHour in day (1-24)24
KHour in AM or PM (0-11)0
hHour in AM or PM (1-12)12
mMinute in hour30
sSecond in minute55
SMillisecond978
zTime zone (name)Pacific Standard Time; PST; GMT-08:00
ZTime zone (RFC 822 offset)-0800
XTime zone (ISO 8601 offset)-08 or -0800 or -08:00

You do not need to remember the whole table. Refer to it whenever you build a new pattern.

How to Use SimpleDateFormat

SimpleDateFormat is a subclass of DateFormat that lets you format and parse dates using the pattern letters above. Combine the letters to describe the exact string you want to produce or read.

For example:

  1. Date format required: 2012.10.23 20:20:45 PST. The appropriate format pattern is yyyy.MM.dd HH:mm:ss zzz.
  2. Date format required: 09:30:00 AM 23-May-2012. The appropriate format pattern is hh:mm:ss a dd-MMM-yyyy.

Tip: Watch letter case. Uppercase M is month, lowercase m is minute. Uppercase H is a 24-hour clock, lowercase h is a 12-hour clock. Mixing them up produces silently wrong output.

The program below prints the current date twice, once in default format and once through a SimpleDateFormat pattern:

import java.text.SimpleDateFormat;
import java.util.Date;

class TestDates_Format {
    public static void main(String args[]) {
        // Current system date and time is assigned to objDate
        Date objDate = new Date();
        System.out.println(objDate);

        // Build the target pattern and format the date
        String strDateFormat = "hh:mm:ss a dd-MMM-yyyy";
        SimpleDateFormat objSDF = new SimpleDateFormat(strDateFormat);
        System.out.println(objSDF.format(objDate));
    }
}

Output:

Wed Nov 29 06:31:41 UTC 2017
06:31:41 AM 29-Nov-2017

Compare Dates Example

Compare Dates Example

The most reliable way to order two Date objects is the compareTo() method. It returns a negative integer when the caller is earlier, zero when both instants are equal, and a positive integer when the caller is later. The snippet below parses two date strings and prints their relative order:

import java.text.SimpleDateFormat;
import java.text.ParseException;
import java.util.Date;

class TestDates_Compare {
    public static void main(String args[]) throws ParseException {

        SimpleDateFormat objSDF = new SimpleDateFormat("dd-MM-yyyy");
        Date dt_1 = objSDF.parse("20-08-1981");
        Date dt_2 = objSDF.parse("12-10-2012");

        System.out.println("Date1 : " + objSDF.format(dt_1));
        System.out.println("Date2 : " + objSDF.format(dt_2));

        // compareTo returns > 0 when dt_1 is after dt_2
        if (dt_1.compareTo(dt_2) > 0) {
            System.out.println("Date 1 occurs after Date 2");
        }
        // compareTo returns < 0 when dt_1 is before dt_2
        else if (dt_1.compareTo(dt_2) < 0) {
            System.out.println("Date 1 occurs before Date 2");
        }
        // compareTo returns 0 when both dates are equal
        else {
            System.out.println("Both are the same date");
        }
    }
}

Output:

Date1 : 20-08-1981
Date2 : 12-10-2012
Date 1 occurs before Date 2

Note: The original code used the pattern dd-mm-yyyy. The lowercase mm letter is minute, not month, so the parser silently produced the wrong month value. The fix above uses uppercase MM for month, which is the correct pattern letter.

Java 8 Modern Date and Time API (java.time)

Java 8 introduced the java.time package, a redesigned API inspired by the Joda-Time library. The new classes are immutable, thread-safe, and separate the human calendar from the machine timeline. The most common types are:

  • LocalDate for a date without a time or zone (for example, a birthday).
  • LocalTime for a wall-clock time without a date or zone.
  • LocalDateTime for a date and time without a zone.
  • ZonedDateTime for a date and time with a specific time zone.
  • OffsetDateTime for a date and time with a UTC offset but no named zone.
  • Instant for a machine timestamp measured from the UNIX epoch.
  • Duration and Period for time-based and date-based amounts.

The example below prints the current date, time, timestamp, and time zone using the modern API:

import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.ZonedDateTime;
import java.time.Instant;
import java.time.ZoneId;

class ModernDateDemo {
    public static void main(String args[]) {
        LocalDate today = LocalDate.now();
        LocalDateTime now = LocalDateTime.now();
        ZonedDateTime zoned = ZonedDateTime.now(ZoneId.of("Asia/Kolkata"));
        Instant epoch = Instant.now();

        System.out.println("LocalDate     : " + today);
        System.out.println("LocalDateTime : " + now);
        System.out.println("ZonedDateTime : " + zoned);
        System.out.println("Instant       : " + epoch);
    }
}

Each type has factory methods (now, of, parse) and immutable operations (plusDays, minusHours, withYear) that return a fresh value instead of mutating the original object.

DateTimeFormatter: Format Dates in Modern Java

DateTimeFormatter in java.time.format replaces SimpleDateFormat. It uses the same style of pattern letters, is immutable, and is safe to share across threads without extra synchronization. The class also exposes ready-made constants such as ISO_LOCAL_DATE and ISO_ZONED_DATE_TIME for standard formats.

import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;

class FormatterDemo {
    public static void main(String args[]) {
        LocalDateTime now = LocalDateTime.now();

        DateTimeFormatter iso = DateTimeFormatter.ISO_LOCAL_DATE_TIME;
        DateTimeFormatter human = DateTimeFormatter.ofPattern("hh:mm:ss a dd-MMM-yyyy");

        System.out.println(now.format(iso));
        System.out.println(now.format(human));

        LocalDateTime parsed = LocalDateTime.parse("2026-07-04T09:30:00", iso);
        System.out.println("Parsed back : " + parsed);
    }
}

To compare two dates in the modern API, call isBefore, isAfter, or isEqual. These methods read more clearly than compareTo and work with every java.time class.

Thread Safety and Best Practices for Java Date and Time

Both java.util.Date and SimpleDateFormat predate modern concurrent Java, and each has known pitfalls. Follow the practices below to keep date handling correct in production code:

  • Prefer java.time over Date and Calendar. The new types are immutable and thread-safe, and they model the domain more accurately (dates without times, times without zones, and so on).
  • Do not share a SimpleDateFormat across threads. The class stores internal parse state and is not thread-safe. Wrap it in a ThreadLocal, synchronize on the instance, or switch to DateTimeFormatter.
  • Always specify a Locale. Month and day names change with the JVM default locale, which makes patterns like MMM and EEE non-portable. Pass a locale explicitly (for example, Locale.ENGLISH).
  • Be explicit about time zones. LocalDateTime has no zone, so convert to ZonedDateTime or Instant whenever the exact moment matters, especially for logs, database columns, and network payloads.
  • Convert between the two APIs carefully. Use date.toInstant(), Date.from(instant), and calendar.toInstant().atZone(zone) at the boundary between legacy code and modern code.
  • Store timestamps in UTC. Persist and transmit dates as UTC (an Instant or an ISO 8601 string) and convert to the user zone only when displaying.

FAQs

Call LocalDateTime.now() from the java.time package for a date and time in the system zone, or LocalDate.now() for a date only. On older Java, use new java.util.Date() to get the current instant.

java.util.Date represents an instant in milliseconds since the epoch and is mutable. LocalDate models a calendar date without a time or zone, is immutable and thread-safe, and offers clearer methods such as plusDays and isBefore.

No. SimpleDateFormat keeps mutable parse state and produces wrong results when shared across threads. Wrap it in a ThreadLocal, synchronize on the instance, or switch to DateTimeFormatter, which is immutable and safe.

Pass the exact string “yyyy-MM-dd”. Uppercase MM is month, lowercase mm is minute. For a full date and time, use “yyyy-MM-dd HH:mm:ss”. Always specify a Locale so month names stay reproducible.

With legacy Date, use compareTo which returns a negative, zero, or positive int. With java.time, call isBefore, isAfter, or isEqual on a LocalDate or Instant. Both approaches respect chronological order.

Call date.toInstant().atZone(ZoneId.systemDefault()).toLocalDateTime(). To go back, use Date.from(localDateTime.atZone(ZoneId.systemDefault()).toInstant()). Pick the zone explicitly whenever the exact moment matters.

AI models trained on log and document corpora infer the likely pattern from sample strings and return a matching DateTimeFormatter pattern. Machine learning classifiers also flag ambiguous fields such as month-day versus day-month order.

Yes. GitHub Copilot suggests DateTimeFormatter patterns, ZonedDateTime conversions, and JUnit tests for locale-sensitive output. Review each suggestion for the correct pattern letters, Locale, and time zone before committing.

Summarize this post with: