Date & Time ·
‹ Previous Next ›
⏱ 5 min read Modified: 2026-10-19

Java LocalDateTime Class: Creation, Formatting and Examples

The call LocalDateTime.parse("2024-03-15 10:30") looks harmless, yet it fails with DateTimeParseException: Text '2024-03-15 10:30' could not be parsed at index 10. At position 10 the default ISO-8601 format expects the letter T, not a space: LocalDateTime.parse("2024-03-15T10:30") works, while a string with a space needs its own DateTimeFormatter.

LocalDateTime in Java (java.time.LocalDateTime) is an immutable, thread-safe class that stores a date and time without a time zone: year, month, day, hours, minutes, seconds and nanoseconds, for example 2024-03-15T14:30:45.123. Think of it as a LocalDate and a LocalTime in one object. The class was added in Java 8 in the java.time package; LocalDateTime.now() returns the current date and time, and LocalDateTime.of() creates a specific one.

How to Create a LocalDateTime: of(), atTime(), parse()

LocalDateTime has no public constructor — objects are created with factory methods. LocalDateTime.of(year, month, day, hour, minute[, second[, nanoOfSecond]]) builds a value from its parts, LocalDateTime.of(LocalDate, LocalTime) joins an existing date and time, date.atTime() and time.atDate() do the same from the LocalDate and LocalTime side, and parse() reads an ISO-8601 string.

import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.LocalTime;
import java.time.Month;

public class LocalDateTimeExample1 {
    public static void main(String[] args) {
        // Seconds and nanoseconds are zero
        LocalDateTime date1 = LocalDateTime.of(2014, 9, 19, 14, 5);
        // Nanoseconds are zero
        LocalDateTime date2 = LocalDateTime.of(2014, 9, 19, 14, 5, 20);
        LocalDateTime date3 = LocalDateTime.of(2014, 9, 19, 14, 5, 20, 9);
        LocalDateTime date4 = LocalDateTime.of(2024, Month.MARCH, 15, 14, 30);

        LocalDate date = LocalDate.of(2024, 3, 15);
        LocalTime time = LocalTime.of(9, 15);

        LocalDateTime date5 = date.atTime(14, 30, 59);
        LocalDateTime date6 = time.atDate(date);
        LocalDateTime date7 = LocalDateTime.of(date, time);
        LocalDateTime date8 = date.atStartOfDay();
        LocalDateTime date9 = LocalDateTime.parse("2024-03-15T14:30:45");

        System.out.println(date1);
        System.out.println(date2);
        System.out.println(date3);
        System.out.println(date4);
        System.out.println(date5);
        System.out.println(date6);
        System.out.println(date7);
        System.out.println(date8);
        System.out.println(date9);
    }
}
2014-09-19T14:05
2014-09-19T14:05:20
2014-09-19T14:05:20.000000009
2024-03-15T14:30
2024-03-15T14:30:59
2024-03-15T09:15
2024-03-15T09:15
2024-03-15T00:00
2024-03-15T14:30:45

Note the output format: the date and the time are separated by the letter T, and toString() omits zero seconds and nanoseconds — that is why date1 prints as 2014-09-19T14:05. The nine nanoseconds in date3 are printed in full, as nine digits.

Every value is validated immediately, when the object is created. A non-existent date or time throws an exception, and a string without the T or without a time part cannot be parsed:

LocalDateTime.of(2024, 2, 30, 10, 0);    // DateTimeException: Invalid date 'FEBRUARY 30'
LocalDateTime.of(2024, 3, 15, 24, 0);    // DateTimeException: Invalid value for HourOfDay (valid values 0 - 23): 24
LocalDateTime.parse("2024-03-15 10:30"); // DateTimeParseException: Text '2024-03-15 10:30' could not be parsed at index 10
LocalDateTime.parse("2024-03-15");       // DateTimeParseException: Text '2024-03-15' could not be parsed at index 10

A date-only string becomes a LocalDateTime in two steps: LocalDate.parse("2024-03-15").atStartOfDay(). How to parse other formats is shown in Format LocalDateTime.

LocalDateTime.now(): the Current Date and Time

LocalDateTime.now() returns the current date and time from the system clock in the JVM's default time zone. LocalDateTime.now(ZoneId) returns the date and time in the given zone, and now(Clock) reads them from a Clock object, which is handy in tests.

import java.time.LocalDateTime;
import java.time.ZoneOffset;

public class LocalDateTimeNowExample {
    public static void main(String[] args) {
        LocalDateTime now = LocalDateTime.now();
        LocalDateTime utc = LocalDateTime.now(ZoneOffset.UTC);

        System.out.println("Now:    " + now);
        System.out.println("In UTC: " + utc);
    }
}
Now:    2026-10-19T14:35:07.123456   (depends on when you run it and on the JVM zone, here Europe/Moscow)
In UTC: 2026-10-19T11:35:07.123512

The number of digits after the seconds depends on the Java version and the OS: Java 8 usually gives milliseconds, Java 9 and later typically microseconds. The result of now() does not remember which zone it came from — it is just "what the wall clock shows". If now() on a server differs from your laptop, check the JVM zone with ZoneId.systemDefault(); a fixed Clock for tests works exactly as in the LocalTime lesson of this module (LocalDateTime.now(clock)).

Getting the Date, the Time and Individual Fields

The fields of a LocalDateTime are read with getters: getYear(), getMonthValue(), getMonth(), getDayOfMonth(), getDayOfYear(), getDayOfWeek(), getHour(), getMinute(), getSecond(), getNano(). toLocalDate() returns the whole date and toLocalTime() the whole time.

import java.time.LocalDateTime;

public class LocalDateTimeExample2 {
    public static void main(String[] args) {
        LocalDateTime dateTime = LocalDateTime.of(2024, 3, 15, 14, 30, 45);
        int year = dateTime.getYear();
        int dayYear = dateTime.getDayOfYear();
        int hour = dateTime.getHour();
        int minute = dateTime.getMinute();

        System.out.println("Year: " + year);
        System.out.println("Month: " + dateTime.getMonth());
        System.out.println("Day of year: " + dayYear);
        System.out.println("Day of week: " + dateTime.getDayOfWeek());
        System.out.println("Hour: " + hour);
        System.out.println("Minute: " + minute);
        System.out.println("Date: " + dateTime.toLocalDate());
        System.out.println("Time: " + dateTime.toLocalTime());
    }
}
Year: 2024
Month: MARCH
Day of year: 75
Day of week: FRIDAY
Hour: 14
Minute: 30
Date: 2024-03-15
Time: 14:30:45

The generic get(ChronoField) works too: dateTime.get(ChronoField.MINUTE_OF_DAY) returns 870. A LocalDateTime has every date and time field but no zone fields: dateTime.get(ChronoField.OFFSET_SECONDS) throws UnsupportedTemporalTypeException: Unsupported field: OffsetSeconds. ChronoField constants are covered in detail in the LocalDate and LocalTime lessons of this module.

Changing, Adding and Comparing: with(), plus(), isAfter()

with...() methods replace one field, plus...() and minus...() shift the value by years, months, weeks, days, hours, minutes, seconds or nanoseconds, and isAfter(), isBefore(), isEqual() compare two values. The object is immutable: every method returns a new LocalDateTime, and the original stays the same.

import java.time.LocalDateTime;
import java.time.temporal.ChronoField;
import java.time.temporal.ChronoUnit;

public class LocalDateTimeExample3 {
    public static void main(String[] args) {
        LocalDateTime base = LocalDateTime.of(2024, 3, 15, 14, 30);

        LocalDateTime date1 = base.with(ChronoField.HOUR_OF_DAY, 10);
        LocalDateTime date2 = base.withMonth(8);
        LocalDateTime date3 = base.withYear(2013).withMinute(0);

        // Comparing
        boolean after = base.isAfter(date1);
        boolean before = base.isBefore(date1);
        boolean equal = base.equals(date1);

        // Adding
        LocalDateTime date4 = base.plusYears(4);
        LocalDateTime date5 = base.plusWeeks(3);
        LocalDateTime date6 = date1.plus(2, ChronoUnit.HOURS);

        // Subtracting
        LocalDateTime date7 = base.minusMonths(2);
        LocalDateTime date8 = base.minusNanos(1);
        LocalDateTime date9 = base.minus(10, ChronoUnit.SECONDS);

        System.out.println("date1: " + date1);
        System.out.println("date2: " + date2);
        System.out.println("date3: " + date3);
        System.out.println("after: " + after + ", before: " + before + ", equal: " + equal);
        System.out.println("date4: " + date4);
        System.out.println("date5: " + date5);
        System.out.println("date6: " + date6);
        System.out.println("date7: " + date7);
        System.out.println("date8: " + date8);
        System.out.println("date9: " + date9);
        System.out.println("base is unchanged: " + base);
    }
}
date1: 2024-03-15T10:30
date2: 2024-08-15T14:30
date3: 2013-03-15T14:00
after: true, before: false, equal: false
date4: 2028-03-15T14:30
date5: 2024-04-05T14:30
date6: 2024-03-15T12:30
date7: 2024-01-15T14:30
date8: 2024-03-15T14:29:59.999999999
date9: 2024-03-15T14:29:50
base is unchanged: 2024-03-15T14:30

Unlike LocalTime, the time does not "wrap around" within a day here: LocalDateTime.of(2024, 3, 15, 23, 30).plusMinutes(45) gives 2024-03-16T00:15 — the date moves to the next day. For sorting, use compareTo() (the class implements Comparable) and compare the result with zero, not with -1: dates are compared first, and the difference can be any number.

To drop extra precision, use truncatedTo(): dateTime.truncatedTo(ChronoUnit.DAYS) gives midnight of the same day, truncatedTo(ChronoUnit.MINUTES) the value without seconds. Units longer than a day are not supported: truncatedTo(ChronoUnit.MONTHS) throws UnsupportedTemporalTypeException: Unit is too large to be used for truncation.

Format LocalDateTime: format() and parse()

To print a LocalDateTime in a custom form such as 15.03.2024 14:30, call dateTime.format(DateTimeFormatter.ofPattern("dd.MM.yyyy HH:mm")). The reverse is LocalDateTime.parse(text, formatter). Without a formatter, toString() and parse() only work with ISO-8601: 2024-03-15T14:30.

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

public class FormatLocalDateTimeExample {
    public static void main(String[] args) {
        LocalDateTime dateTime = LocalDateTime.of(2024, 3, 15, 14, 30);

        DateTimeFormatter dmy = DateTimeFormatter.ofPattern("dd.MM.yyyy HH:mm");
        DateTimeFormatter sql = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
        DateTimeFormatter us = DateTimeFormatter.ofPattern("MM/dd/yyyy hh:mm a", Locale.ENGLISH);

        System.out.println(dateTime);              // toString(), ISO-8601
        System.out.println(dateTime.format(dmy));
        System.out.println(dateTime.format(sql));
        System.out.println(dateTime.format(us));

        // String -> LocalDateTime
        LocalDateTime parsed = LocalDateTime.parse("15.03.2024 14:30", dmy);
        LocalDateTime withSpace = LocalDateTime.parse("2024-03-15 10:30:00", sql);
        System.out.println(parsed);
        System.out.println(withSpace);
    }
}
2024-03-15T14:30
15.03.2024 14:30
2024-03-15 14:30:00
03/15/2024 02:30 PM
2024-03-15T14:30
2024-03-15T10:30

Two failures come up again and again. If the pattern has no time but you parse into a LocalDateTime, for example LocalDateTime.parse("15.03.2024", DateTimeFormatter.ofPattern("dd.MM.yyyy")), you get DateTimeParseException: ... Unable to obtain LocalDateTime from TemporalAccessor — parse into a LocalDate and call atStartOfDay(). And a pattern with a zone offset (XXX, Z) applied to a LocalDateTime throws UnsupportedTemporalTypeException: Unsupported field: OffsetSeconds: the object has no zone, so call atZone() first. The a marker (AM/PM) depends on the locale, so pass a Locale explicitly; the full list of pattern letters is in the DateTimeFormatter lesson of this module.

Difference Between Two LocalDateTime Values

The difference between two LocalDateTime values is calculated with Duration.between(start, end) — the result is in hours, minutes and seconds — or with ChronoUnit.X.between(start, end), which returns a long in the chosen unit. Only whole units count: an incomplete day or hour is dropped.

import java.time.Duration;
import java.time.LocalDateTime;
import java.time.Period;
import java.time.temporal.ChronoUnit;

public class BetweenLocalDateTimeExample {
    public static void main(String[] args) {
        LocalDateTime start = LocalDateTime.of(2024, 3, 15, 22, 0);
        LocalDateTime end = LocalDateTime.of(2024, 3, 16, 6, 30);

        System.out.println(Duration.between(start, end));
        System.out.println(ChronoUnit.MINUTES.between(start, end));
        System.out.println(ChronoUnit.DAYS.between(start, end));
        System.out.println(Period.between(start.toLocalDate(), end.toLocalDate()));
    }
}
PT8H30M
510
0
P1D

The last two lines answer different questions. ChronoUnit.DAYS.between counts full 24-hour days (8.5 hours passed, so 0 days), while Period.between looks at the dates and sees that the calendar day changed. Period.between(start, end) with LocalDateTime arguments does not compile: it accepts only LocalDate. Duration and Period each have their own lesson in this module.

LocalDateTime vs Instant: Which One to Use

LocalDateTime is a "wall clock" date and time with no zone, so the same value in Moscow and in Berlin refers to different moments. Instant is an exact point on the UTC timeline, the same for everyone. Use Instant for event timestamps (order created, log entries) and LocalDateTime for local values such as "doctor's appointment on March 15 at 10:30".

Feature LocalDateTime Instant
What it stores Year, month, day, hours, minutes, seconds, nanoseconds Seconds and nanoseconds since 1970-01-01T00:00:00Z
Time zone None, not even implied None, but the value is always in UTC
Example toString() 2024-03-15T10:30 2024-03-15T07:30:00Z
Identifies a moment in time? No, not until a zone is supplied Yes
Daylight saving time Ignored Not relevant: the UTC timeline is continuous
Year and month fields, plusMonths() Yes No: only seconds, milliseconds, nanoseconds (and days as 86,400 seconds)
When to choose it Schedules, date and time on a receipt, a "date and time" form field Timestamps in logs and databases, data exchange between services

What does this code print? The missing zone in LocalDateTime becomes obvious around clock changes:

import java.time.LocalDateTime;
import java.time.ZoneId;
import java.time.temporal.ChronoUnit;

public class DstExample {
    public static void main(String[] args) {
        ZoneId berlin = ZoneId.of("Europe/Berlin");

        // March 31, 2024: in Berlin clocks jump from 02:00 straight to 03:00
        LocalDateTime gap = LocalDateTime.of(2024, 3, 31, 2, 30);
        System.out.println(gap.atZone(berlin));

        // October 27, 2024: from 03:00 back to 02:00, the night is one hour longer
        LocalDateTime start = LocalDateTime.of(2024, 10, 27, 0, 0);
        LocalDateTime end = LocalDateTime.of(2024, 10, 27, 6, 0);
        System.out.println(ChronoUnit.HOURS.between(start, end));
        System.out.println(ChronoUnit.HOURS.between(start.atZone(berlin), end.atZone(berlin)));
    }
}
2024-03-31T03:30+02:00[Europe/Berlin]
6
7

02:30 on March 31 does not exist in Berlin, but LocalDateTime has no way of knowing that — when you attach the zone, the time is silently shifted to 03:30. And on the night the clocks go back, the wall clock shows 6 hours between 00:00 and 06:00, while 7 hours actually passed. If you calculate shift lengths, bill phone calls or store when events happened, work with ZonedDateTime or Instant.

Converting to Instant, ZonedDateTime and Milliseconds

To turn a LocalDateTime into an Instant or epoch milliseconds, you must supply a zone, ldt.atZone(zone).toInstant(), or an offset, ldt.toInstant(ZoneOffset.UTC). The reverse is LocalDateTime.ofInstant(instant, zone). Without a zone the conversion is impossible, which is why the class has no no-argument toInstant().

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

public class ConvertLocalDateTimeExample {
    public static void main(String[] args) {
        LocalDateTime meeting = LocalDateTime.of(2024, 3, 15, 10, 30);
        ZoneId moscow = ZoneId.of("Europe/Moscow");

        ZonedDateTime zoned = meeting.atZone(moscow);
        Instant inMoscow = zoned.toInstant();
        Instant inBerlin = meeting.atZone(ZoneId.of("Europe/Berlin")).toInstant();
        Instant asUtc = meeting.toInstant(ZoneOffset.UTC);
        long millis = inMoscow.toEpochMilli();

        LocalDateTime inTokyo = LocalDateTime.ofInstant(inMoscow, ZoneId.of("Asia/Tokyo"));

        System.out.println(zoned);
        System.out.println(inMoscow);
        System.out.println(inBerlin);
        System.out.println(asUtc);
        System.out.println(millis);
        System.out.println(inTokyo);
        System.out.println(zoned.toLocalDateTime());
    }
}
2024-03-15T10:30+03:00[Europe/Moscow]
2024-03-15T07:30:00Z
2024-03-15T09:30:00Z
2024-03-15T10:30:00Z
1710487800000
2024-03-15T16:30
2024-03-15T10:30

Lines 2–4 show the key point: the same "March 15, 10:30" yields three different moments depending on the zone. If you need epoch seconds without an Instant object, there is a shortcut: meeting.toEpochSecond(ZoneOffset.ofHours(3)). Conversion to the legacy java.util.Date goes through the same Instant: Date.from(ldt.atZone(zone).toInstant()); the full table of conversions from the old API is in the java.time overview lesson of this module.

ZoneId.systemDefault() is a hidden dependency

The code ldt.atZone(ZoneId.systemDefault()) gives moments 3 hours apart on a developer laptop in Moscow and on a server running in UTC. If the value came from a user, use their zone (from the profile or the browser), not the JVM zone. If the zone is unknown, store the LocalDateTime as is instead of guessing an Instant.

LocalDateTime Methods Cheat Sheet

LocalDateTime methods fall into groups: creating, reading fields, getting a modified copy, comparing, formatting and attaching a zone. Every method that seems to "change" the value returns a new object.

Method What it does Edge case / error
of(y, m, d, h, min[, s[, n]]), of(LocalDate, LocalTime) Date and time from parts of(2024, 2, 30, 10, 0) — DateTimeException
now(), now(ZoneId), now(Clock) Current date and time Without an argument, the JVM default zone is used
parse(text), parse(text, formatter) Parses a string A space instead of T — DateTimeParseException at index 10
getYear() … getNano(), toLocalDate(), toLocalTime() Fields, the date or the time alone get(ChronoField.OFFSET_SECONDS) — UnsupportedTemporalTypeException
withYear() … withNano(), with(ChronoField, value) Copy with one field replaced withHour(24) — DateTimeException
plusYears() … plusNanos(), minus...(), plus(n, ChronoUnit) Shifts by N units Crossing midnight changes the date
isBefore(), isAfter(), isEqual(), compareTo() Comparison Compare compareTo() with zero, not with -1
truncatedTo(unit) Zeroes fields below the unit MONTHS and larger — UnsupportedTemporalTypeException
format(formatter) String by pattern Pattern with XXX — Unsupported field: OffsetSeconds
atZone(ZoneId), atOffset(ZoneOffset) Attaches a zone: ZonedDateTime / OffsetDateTime A time inside a DST gap is moved forward
toInstant(ZoneOffset), toEpochSecond(ZoneOffset), ofInstant(Instant, ZoneId) To a moment in time and back Cannot be called without a zone or offset

Easy-to-Miss Mistakes

Most LocalDateTime bugs come from the string format (the letter T), a forgotten assignment of the result, and the assumption that the value carries a time zone. Here are the typical cases and their fixes.

Mistake What happens How to fix it
dt.plusHours(1); without assignment The result is lost, dt does not change dt = dt.plusHours(1);
LocalDateTime.parse("2024-03-15 10:30") DateTimeParseException at index 10 "2024-03-15T10:30" or ofPattern("yyyy-MM-dd HH:mm")
createdAt = LocalDateTime.now() on servers in different zones Event times cannot be compared or ordered Instant.now() or OffsetDateTime
Period.between(ldt1, ldt2) Does not compile: a LocalDate is required Duration.between(ldt1, ldt2) or Period.between(ldt1.toLocalDate(), ldt2.toLocalDate())
ChronoUnit.DAYS.between to check "did the date change" 22:00 → 06:30 the next day gives 0 Compare toLocalDate()
dt1 == dt2 Compares references, not values dt1.isEqual(dt2) or dt1.equals(dt2)
date.atTime(14, 30, 59, 999999) Gives 14:30:59.000999999: the fourth argument is nanoseconds 999_999_999; details in the LocalTime lesson

Frequently Asked Questions

How do I save a LocalDateTime to a database?

Since JDBC 4.2 you pass the object directly: ps.setObject(1, dateTime) and rs.getObject("created", LocalDateTime.class); the matching column type is TIMESTAMP (without time zone). Hibernate 5.2+ and JPA 2.2 map LocalDateTime out of the box. Older code uses Timestamp.valueOf(dateTime) and timestamp.toLocalDateTime(). If the column must hold the moment an event happened, choose TIMESTAMP WITH TIME ZONE together with OffsetDateTime or Instant.

How do I get the start and end of the day as a LocalDateTime?

Start of day is date.atStartOfDay() (00:00), end of day is date.atTime(LocalTime.MAX) (23:59:59.999999999). For "everything on this day" queries a half-open range is more reliable: created >= date.atStartOfDay() and created < date.plusDays(1).atStartOfDay() — this way you do not lose rows with fractions of a second when the database stores a different precision.

Why doesn't Jackson serialize LocalDateTime to JSON?

Support for java.time lives in the separate jackson-datatype-jsr310 module. Without it, Jackson 2.12+ throws InvalidDefinitionException: Java 8 date/time type `java.time.LocalDateTime` not supported by default. Register JavaTimeModule and disable SerializationFeature.WRITE_DATES_AS_TIMESTAMPS, otherwise the value is written as an array of numbers like [2024,3,15,14,30] instead of an ISO-8601 string. Spring Boot applies both settings automatically; a custom format is set with @JsonFormat(pattern = "dd.MM.yyyy HH:mm").

How do I sort a list of objects by a LocalDateTime field?

LocalDateTime implements Comparable, so orders.sort(Comparator.comparing(Order::getCreatedAt)) is enough; add .reversed() for newest first. If the field can be null, the plain comparator fails with NullPointerException — use Comparator.comparing(Order::getCreatedAt, Comparator.nullsLast(Comparator.naturalOrder())).

Comments

Please log in or register to have a possibility to add comment.