Skip to content
On this page

LocalDateTime

Since Java 8, the java.time package provides a new date and time API, primarily involving the following types:

  • Local date and time: LocalDateTime, LocalDate, LocalTime
  • Date and time with time zone: ZonedDateTime
  • Instant: Instant
  • Time zone: ZoneId, ZoneOffset
  • Time interval: Duration
  • A new set of formatting types, DateTimeFormatter, to replace SimpleDateFormat.

Compared to the old API, the new API strictly distinguishes between instant, local date, local time, and date-time with time zone, making date and time calculations much more convenient.

Additionally, the new API corrects unreasonable constant designs in the old API:

  • The range of Month is represented as 1 to 12 for January to December.
  • The range of Week is represented as 1 to 7 for Monday to Sunday.

Finally, almost all types in the new API are immutable (similar to String), so they can be used safely without worrying about modifications.

Let's first look at the most commonly used LocalDateTime, which represents a local date and time:

java
import java.time.*;

public class Main {
    public static void main(String[] args) {
        LocalDate d = LocalDate.now(); // Current date
        LocalTime t = LocalTime.now(); // Current time
        LocalDateTime dt = LocalDateTime.now(); // Current date and time
        System.out.println(d); // Printed strictly according to ISO 8601 format
        System.out.println(t); // Printed strictly according to ISO 8601 format
        System.out.println(dt); // Printed strictly according to ISO 8601 format
    }
}

The local date and time obtained through now() is always returned in the current default time zone. Unlike the old API, LocalDateTime, LocalDate, and LocalTime are printed strictly according to the date and time format specified by ISO 8601 by default.

There is a small issue with the above code: when obtaining the three types, a slight delay occurs while executing each line of code. Therefore, the dates and times of the three types may not match (the milliseconds will likely differ). To ensure that the same instant's date and time are obtained, you can rewrite it as follows:

java
LocalDateTime dt = LocalDateTime.now(); // Current date and time
LocalDate d = dt.toLocalDate(); // Convert to current date
LocalTime t = dt.toLocalTime(); // Convert to current time

Conversely, you can create LocalDateTime by specifying a date and time using the of() method:

java
// Specify date and time:
LocalDate d2 = LocalDate.of(2019, 11, 30); // 2019-11-30, note that 11=November
LocalTime t2 = LocalTime.of(15, 16, 17); // 15:16:17
LocalDateTime dt2 = LocalDateTime.of(2019, 11, 30, 15, 16, 17);
LocalDateTime dt3 = LocalDateTime.of(d2, t2);

Since it follows the strict ISO 8601 format, you can convert a string to LocalDateTime by passing a standard format:

java
LocalDateTime dt = LocalDateTime.parse("2019-11-19T15:16:17");
LocalDate d = LocalDate.parse("2019-11-19");
LocalTime t = LocalTime.parse("15:16:17");

Note that the separator for date and time in ISO 8601 is T. The standard formats are as follows:

  • Date: yyyy-MM-dd
  • Time: HH:mm:ss
  • Time with milliseconds: HH:mm:ss.SSS
  • Date and time: yyyy-MM-dd'T'HH:mm:ss
  • Date and time with milliseconds: yyyy-MM-dd'T'HH:mm:ss.SSS

DateTimeFormatter

If you need to customize the output format or parse a non-ISO 8601 format string into LocalDateTime, you can use the new DateTimeFormatter:

java
import java.time.*;
import java.time.format.*;

public class Main {
    public static void main(String[] args) {
        // Custom formatting:
        DateTimeFormatter dtf = DateTimeFormatter.ofPattern("yyyy/MM/dd HH:mm:ss");
        System.out.println(dtf.format(LocalDateTime.now()));

        // Parse using custom format:
        LocalDateTime dt2 = LocalDateTime.parse("2019/11/30 15:16:17", dtf);
        System.out.println(dt2);
    }
}

LocalDateTime provides a very simple method for adding or subtracting dates and times using method chaining:

java
import java.time.*;

public class Main {
    public static void main(String[] args) {
        LocalDateTime dt = LocalDateTime.of(2019, 10, 26, 20, 30, 59);
        System.out.println(dt);
        // Add 5 days and subtract 3 hours:
        LocalDateTime dt2 = dt.plusDays(5).minusHours(3);
        System.out.println(dt2); // 2019-10-31T17:30:59
        // Subtract 1 month:
        LocalDateTime dt3 = dt2.minusMonths(1);
        System.out.println(dt3); // 2019-09-30T17:30:59
    }
}

Notice that adjusting the month automatically adjusts the date. For example, subtracting 1 month from 2019-10-31 results in 2019-09-30, as September does not have 31 days.

To adjust the date and time, use the withXxx() methods, such as withHour(15) to change 10:11:12 to 15:11:12:

  • Adjust year: withYear()
  • Adjust month: withMonth()
  • Adjust day: withDayOfMonth()
  • Adjust hour: withHour()
  • Adjust minute: withMinute()
  • Adjust second: withSecond()

Example code is as follows:

java
import java.time.*;

public class Main {
    public static void main(String[] args) {
        LocalDateTime dt = LocalDateTime.of(2019, 10, 26, 20, 30, 59);
        System.out.println(dt);
        // Change day to the 31st:
        LocalDateTime dt2 = dt.withDayOfMonth(31);
        System.out.println(dt2); // 2019-10-31T20:30:59
        // Change month to September:
        LocalDateTime dt3 = dt2.withMonth(9);
        System.out.println(dt3); // 2019-09-30T20:30:59
    }
}

It is also important to note that adjusting the month will correspondingly adjust the date, meaning that when adjusting the month of 2019-10-31 to September, the date automatically changes to the 30th.

In fact, LocalDateTime also has a generic with() method that allows for more complex calculations. For example:

java
import java.time.*;
import java.time.temporal.*;

public class Main {
    public static void main(String[] args) {
        // The first day of this month at 00:00:
        LocalDateTime firstDay = LocalDate.now().withDayOfMonth(1).atStartOfDay();
        System.out.println(firstDay);

        // The last day of this month:
        LocalDate lastDay = LocalDate.now().with(TemporalAdjusters.lastDayOfMonth());
        System.out.println(lastDay);

        // The first day of next month:
        LocalDate nextMonthFirstDay = LocalDate.now().with(TemporalAdjusters.firstDayOfNextMonth());
        System.out.println(nextMonthFirstDay);

        // The first Monday of this month:
        LocalDate firstWeekday = LocalDate.now().with(TemporalAdjusters.firstInMonth(DayOfWeek.MONDAY));
        System.out.println(firstWeekday);
    }
}

The new API can easily handle calculations such as finding the first Sunday of a month.

To determine the chronological order of two LocalDateTime instances, you can use the isBefore() and isAfter() methods, which are similar for LocalDate and LocalTime:

java
import java.time.*;

public class Main {
    public static void main(String[] args) {
        LocalDateTime now = LocalDateTime.now();
        LocalDateTime target = LocalDateTime.of(2019, 11, 19, 8, 15, 0);
        System.out.println(now.isBefore(target));
        System.out.println(LocalDate.now().isBefore(LocalDate.of(2019, 11, 19)));
        System.out.println(LocalTime.now().isAfter(LocalTime.parse("08:15:00")));
    }
}

Note that LocalDateTime cannot be converted to a timestamp because it has no time zone, which makes it impossible to determine a specific moment. The ZonedDateTime we will introduce later combines LocalDateTime with a time zone, allowing it to be converted to a timestamp represented as long.

Duration and Period

Duration represents the time interval between two moments, while Period represents the number of days between two dates:

java
import java.time.*;

public class Main {
    public static void main(String[] args) {
        LocalDateTime start = LocalDateTime.of(2019, 11, 19, 8, 15, 0);
        LocalDateTime end = LocalDateTime.of(2020, 1, 9, 19, 25, 30);
        Duration d = Duration.between(start, end);
        System.out.println(d); // PT1235H10M30S

        Period p = LocalDate.of(2019, 11, 19).until(LocalDate.of(2020, 1, 9));
        System.out.println(p); // P1M21D
    }
}

Note that the difference between two LocalDateTime instances is represented using Duration, similar to PT1235H10M30S, which indicates 1235 hours, 10 minutes, and 30 seconds. The difference between two LocalDate instances is represented using Period, such as P1M21D, indicating 1 month and 21 days.

Both Duration and Period formats conform to ISO 8601, using the structure P...T..., where P... indicates the date interval and T... indicates the time interval. If it is in the format PT..., it signifies only a time interval. You can also create Duration directly using ofXxx() or parse() methods:

java
Duration d1 = Duration.ofHours(10); // 10 hours
Duration d2 = Duration.parse("P1DT2H3M"); // 1 day, 2 hours, 3 minutes

You may notice that the Java 8 java.time API closely resembles the open-source Joda Time. This similarity is due to the excellent design of Joda Time, which led the JDK team to invite Joda Time's author, Stephen Colebourne, to help design the java.time API.

Summary

Java 8 introduced a new date and time API that consists of immutable classes, which are formatted and parsed by default according to the ISO 8601 standard.

Using LocalDateTime, you can easily add or subtract dates and times, or adjust them, and it always returns a new object.

Methods like isBefore() and isAfter() help determine the chronological order of dates and times.

Duration and Period can represent the "interval differences" between two dates and times.

LocalDateTime has loaded