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

Java Date and Time API: java.time and the Current Date

A nightly report runs at 9 PM in New York and names its file with LocalDate.now(). On the developer's laptop the file is report-2026-10-17; in production it is report-2026-10-18. Nothing is wrong with the code itself: the server runs in UTC, where it is already 1 AM the next day, and now() without arguments silently uses the JVM's default time zone. The fix is one argument, shown in the now(ZoneId) and now(Clock) section.

The Java Date and Time API (the java.time package, JSR-310) is the standard library for dates and times, added in Java 8 to replace java.util.Date and java.util.Calendar. Its main classes — LocalDate, LocalTime, LocalDateTime, ZonedDateTime, Instant, Period and Duration — are immutable and thread-safe. You get the current date with LocalDate.now() and the current date and time with LocalDateTime.now().

How to get the current date and time in Java

In Java you get the current date and time with the static now() method of a java.time class: LocalDate.now() returns only the date, LocalTime.now() only the time, LocalDateTime.now() the date and time, ZonedDateTime.now() the date and time with a time zone, and Instant.now() the current moment on the UTC time-line.

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

public class CurrentDateTimeExample {
    public static void main(String[] args) {
        LocalDate date = LocalDate.now();               // current date
        LocalTime time = LocalTime.now();               // current time
        LocalDateTime dateTime = LocalDateTime.now();   // current date and time
        ZonedDateTime zoned = ZonedDateTime.now();      // + JVM default time zone
        Instant instant = Instant.now();                // moment, printed in UTC
        LocalDateTime tokyo = LocalDateTime.now(ZoneId.of("Asia/Tokyo"));

        System.out.println("Date:      " + date);
        System.out.println("Time:      " + time);
        System.out.println("Date-time: " + dateTime);
        System.out.println("Zoned:     " + zoned);
        System.out.println("Instant:   " + instant);
        System.out.println("Tokyo:     " + tokyo);
    }
}

Sample output when run on October 17, 2026 at 14:35 New York time (Java 17, JVM default zone America/New_York, daylight saving time, UTC−4):

Date:      2026-10-17
Time:      14:35:07.123456
Date-time: 2026-10-17T14:35:07.123456
Zoned:     2026-10-17T14:35:07.123456-04:00[America/New_York]
Instant:   2026-10-17T18:35:07.123456Z
Tokyo:     2026-10-18T03:35:07.123456

Two details are worth noticing. Instant is always printed in UTC (the Z suffix), so it is 4 hours ahead of New York. And in Tokyo it is already October 18: the «current date» depends on where you ask. The number of fraction digits depends on the Java version and OS: in Java 8 the system clock gives milliseconds (14:35:07.123), since Java 9 usually microseconds.

To print the current date in a fixed format such as MM/dd/yyyy, use DateTimeFormatter:

String today = LocalDate.now().format(DateTimeFormatter.ofPattern("MM/dd/yyyy"));
System.out.println(today); // Output: 10/17/2026

Pattern letters and parsing a string into a date are covered in the separate lesson on DateTimeFormatter; getting the year, month or day of week from the current date is covered in the lesson on the LocalDate class.

now(ZoneId) and now(Clock): time zones and testable code

Every now() method has two overloads. now(ZoneId zone) returns the current date or time in the given time zone instead of the JVM default. now(Clock clock) takes the time from a Clock object, which lets you fix «now» in unit tests. The no-argument now() is simply now(Clock.systemDefaultZone()).

The example below reproduces the bug from the introduction without waiting for 9 PM: the same moment is read through a UTC clock and a New York clock.

import java.time.Clock;
import java.time.Instant;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.ZoneId;
import java.time.ZoneOffset;

public class ClockExample {
    public static void main(String[] args) {
        // 9 PM on October 17 in New York = 1 AM on October 18 in UTC
        Instant moment = Instant.parse("2026-10-18T01:00:00Z");

        Clock utcServer = Clock.fixed(moment, ZoneOffset.UTC);
        Clock newYork = Clock.fixed(moment, ZoneId.of("America/New_York"));

        System.out.println(LocalDate.now(utcServer));     // Output: 2026-10-18
        System.out.println(LocalDate.now(newYork));       // Output: 2026-10-17
        System.out.println(LocalDateTime.now(newYork));   // Output: 2026-10-17T21:00
        System.out.println(Instant.now(newYork));         // Output: 2026-10-18T01:00:00Z

        LocalDate.now(ZoneId.of("America/NewYork"));
        // Exception in thread "main" java.time.zone.ZoneRulesException:
        // Unknown time-zone ID: America/NewYork
    }
}

The Instant is the same for both clocks; only the local date differs. A typo in the zone ID is not ignored: ZoneId.of() throws ZoneRulesException (a subclass of DateTimeException), so a wrong ID fails at the first call. The old TimeZone.getTimeZone("America/NewYork") silently returns GMT instead.

Call Where the time comes from When to use it
LocalDate.now() System clock + JVM default zone (ZoneId.systemDefault()) Quick scripts, desktop apps where the user's zone is the JVM zone
LocalDate.now(ZoneId.of("America/New_York")) System clock + the given zone Servers, business rules tied to a region («today in the store's city»)
LocalDate.now(Clock.systemUTC()) System clock + UTC Services that keep all dates in UTC
LocalDate.now(clock) with an injected Clock Whatever clock was passed in: system in production, Clock.fixed(...) in tests Any code that must be unit-tested («is the subscription expired today?»)

LocalDate vs LocalDateTime vs ZonedDateTime vs Instant

The difference is in what each class stores: LocalDate stores only a date, LocalTime only a time of day, LocalDateTime a date and time without a zone, ZonedDateTime a date and time in a specific zone, and Instant an exact moment on the UTC time-line. Period and Duration are amounts of time, not points in time.

Class Date Time Zone / offset Example of toString() Typical use
LocalDate Yes No No 2026-10-17 Birthday, due date, holiday
LocalTime No Yes (to nanoseconds) No 14:35:07 Opening hours, alarm time
LocalDateTime Yes Yes No 2026-10-17T14:35:07 Meeting in one city, time on a receipt
ZonedDateTime Yes Yes Zone (ZoneId) + offset 2026-10-17T14:35:07-04:00[America/New_York] Flights, calls across countries, daylight saving time
Instant Seconds and nanoseconds since 1970-01-01T00:00:00Z No (always printed in UTC) 2026-10-17T18:35:07Z Timestamps in logs, databases, between services
Period Amount in years, months, days — P1Y2M3D Age, contract term, «in 2 months»
Duration Amount in seconds and nanoseconds — PT1H30M Timeouts, how long an operation took

The point-in-time classes (LocalDate, LocalTime, LocalDateTime, ZonedDateTime, Instant) implement java.time.temporal.Temporal. The amount classes Period and Duration implement java.time.temporal.TemporalAmount, so any amount can be added to a date with the common plus(TemporalAmount) method. The methods of each class (of(), getters, with*(), plus*()/minus*(), comparisons) are covered in the separate lessons on LocalDate, LocalTime, LocalDateTime, Instant, Period and Duration.

Instant has no time zone either

The Local* classes don't store a time zone, but neither does Instant: it is just a point on the UTC time-line, and toString() prints it in UTC. A time zone exists only in ZonedDateTime (a region such as America/New_York) and OffsetDateTime (a fixed offset such as -04:00). To see the local time of an Instant, you must name a zone: instant.atZone(ZoneId.of("America/New_York")).

Which java.time class to choose

Choose the class by the question your data answers. If it is «which day?» — LocalDate. If it is «what does the wall clock show in one place?» — LocalDateTime. If people in different zones must agree on the moment — ZonedDateTime for display and calculations, Instant for storage. For «how long?» use Period or Duration.

You need to store… Use Don't use
A date of birth, an invoice due date LocalDate LocalDateTime with 00:00: a fake time creates off-by-one-day bugs after zone conversion
A daily schedule: «the store opens at 09:00» LocalTime Instant: the moment shifts twice a year with daylight saving time
A local event in one known place LocalDateTime (plus the place's ZoneId if you need the exact moment) Instant alone: you lose what the clock on the wall showed
A meeting between New York and Tokyo, a flight departure ZonedDateTime LocalDateTime: nobody knows which zone it is in
When an event happened: log entries, created_at, messages between services Instant LocalDateTime.now(): servers in different zones write incomparable values
«3 months», a person's age Period (calendar-based) Duration.ofDays(90): months have different lengths
A timeout, elapsed time of a task Duration (time-based) Period: it has no hours or seconds

java.time vs Date and Calendar

Before Java 8, dates were handled by java.util.Date and java.util.Calendar. These classes are mutable and not thread-safe, months are numbered from zero, and invalid values silently roll over into the next month. java.time fixes all of this: objects are immutable, months start at 1, and an invalid date throws an exception right away.

A short demonstration of the old API: new Date(2024, 1, 15) compiles (with a deprecation warning) but creates February 15, 3924. The constructor adds 1900 to the year and counts months from zero, so 1 is February. In java.time the same date is LocalDate.of(2024, 2, 15) and means exactly what it says.

Feature Date / Calendar java.time
Mutability Mutable: setTime(), set(), add() change the object Immutable: plusDays() and others return a new object
Thread safety No; SimpleDateFormat is not thread-safe either Yes, including DateTimeFormatter
Month numbering From 0: January = 0, December = 11 From 1, plus the Month enum
Year in new Date(y, m, d) Offset from 1900 (constructor deprecated) The real year: LocalDate.of(2024, 2, 15)
Invalid date Silently rolls over (Feb 29, 2023 → Mar 1, 2023) DateTimeException
Time zone Date stores milliseconds since the epoch, but toString() prints them in the JVM default zone Separate classes with and without a zone: LocalDateTime, ZonedDateTime, Instant
Separation of concepts Date is a date, a time and a moment at once One class per task (see the comparison table)

What does this code print? Many people expect an exception, but Calendar works in lenient mode by default:

import java.time.LocalDate;
import java.util.Calendar;

public class LenientExample {
    public static void main(String[] args) {
        Calendar c = Calendar.getInstance();
        c.clear();
        c.set(2023, Calendar.FEBRUARY, 29);   // 2023 is not a leap year
        System.out.println(c.getTime());
        // Output (JVM zone America/New_York): Wed Mar 01 00:00:00 EST 2023

        LocalDate date = LocalDate.of(2023, 2, 29);
        // Exception in thread "main" java.time.DateTimeException:
        // Invalid date 'February 29' as '2023' is not a leap year
    }
}

The old API «fixed» the mistake and returned March 1 — a bug that easily slips into production. LocalDate.of() reports the problem immediately, on the line where it happened.

Date vs LocalDate: converting between the old and new API

java.util.Date is a moment in time (milliseconds since 1970-01-01T00:00:00Z), while LocalDate is a calendar date with no time and no zone. That is why converting a Date to a LocalDate always needs a time zone: date.toInstant().atZone(zone).toLocalDate(), or LocalDate.ofInstant(date.toInstant(), zone) in Java 9+. The way back is Date.from(instant).

From To How
Date Instant date.toInstant()
Instant Date Date.from(instant)
Date LocalDate LocalDate.ofInstant(date.toInstant(), zone) (Java 9+) or date.toInstant().atZone(zone).toLocalDate()
LocalDate Date Date.from(localDate.atStartOfDay(zone).toInstant())
Date LocalDateTime LocalDateTime.ofInstant(date.toInstant(), zone)
LocalDateTime Date Date.from(ldt.atZone(zone).toInstant())
GregorianCalendar ZonedDateTime calendar.toZonedDateTime() / back: GregorianCalendar.from(zdt)
java.sql.Date LocalDate sqlDate.toLocalDate() / back: java.sql.Date.valueOf(localDate)
import java.time.Instant;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.ZoneId;
import java.util.Date;

public class ConvertExample {
    public static void main(String[] args) {
        ZoneId newYork = ZoneId.of("America/New_York");
        Date legacy = new Date(0L);                       // 1970-01-01T00:00:00Z

        Instant instant = legacy.toInstant();
        LocalDateTime local = LocalDateTime.ofInstant(instant, newYork);
        LocalDate day = LocalDate.ofInstant(instant, newYork);     // Java 9+
        Date back = Date.from(instant);

        System.out.println(instant);          // Output: 1970-01-01T00:00:00Z
        System.out.println(local);            // Output: 1969-12-31T19:00
        System.out.println(day);              // Output: 1969-12-31
        System.out.println(back.getTime());   // Output: 0

        java.sql.Date sqlDate = java.sql.Date.valueOf(LocalDate.of(2024, 2, 15));
        System.out.println(sqlDate.toLocalDate());   // Output: 2024-02-15
        sqlDate.toInstant();
        // Exception in thread "main" java.lang.UnsupportedOperationException
    }
}

The same Date is January 1, 1970 in UTC and December 31, 1969 in New York — this is exactly why there is no zone-free conversion from Date to LocalDate. Note the last line: java.sql.Date extends java.util.Date but has no time part, so its toInstant() throws UnsupportedOperationException. Use toLocalDate() for it.

You often don't need Date at all

JDBC 4.2 drivers read and write LocalDate, LocalDateTime and OffsetDateTime directly via rs.getObject(column, LocalDate.class) and ps.setObject(index, localDate), and Hibernate 5+ maps java.time fields without converters. Convert to Date only at the boundary with a library that still requires it.

Where developers get tripped up

The most common mistake with java.time is calling plusDays() or withYear() and not saving the result: the object is immutable, so the original date stays the same. Other frequent surprises are adding months at the end of a month, the shortened toString() output and comparing ZonedDateTime values with equals().

import java.time.LocalDate;
import java.time.LocalTime;
import java.time.ZoneId;
import java.time.ZonedDateTime;

public class MistakesExample {
    public static void main(String[] args) {
        LocalDate date = LocalDate.of(2024, 1, 31);
        date.plusDays(1);                           // result is lost
        System.out.println(date);                   // Output: 2024-01-31

        date = date.plusDays(1);                    // correct
        System.out.println(date);                   // Output: 2024-02-01

        LocalDate endOfJan = LocalDate.of(2024, 1, 31);
        System.out.println(endOfJan.plusMonths(1)); // Output: 2024-02-29

        System.out.println(LocalTime.of(14, 0));    // Output: 14:00
        System.out.println(LocalTime.of(14, 0, 5)); // Output: 14:00:05

        ZonedDateTime utc = ZonedDateTime.of(2024, 1, 1, 12, 0, 0, 0, ZoneId.of("UTC"));
        ZonedDateTime paris = utc.withZoneSameInstant(ZoneId.of("Europe/Paris"));
        System.out.println(utc.equals(paris));      // Output: false
        System.out.println(utc.isEqual(paris));     // Output: true
    }
}
  • Lost result. The plus*(), minus*() and with*() methods return a new object — assign it to a variable.
  • End of month. plusMonths(1) from January 31 gives the last existing day of February (the 29th in the leap year 2024), not March 2 or 3.
  • Short toString(). Zero seconds and nanoseconds are not printed, so LocalTime.of(14, 0) is shown as 14:00. For a stable format (reports, string comparison in tests) use DateTimeFormatter.
  • equals() on zoned values. equals() compares the local date-time and the zone, so the same moment in UTC and Paris is «not equal». To ask «is it the same instant?» use isEqual() or compare toInstant().
  • LocalDateTime for event timestamps. Without a zone you cannot tell when an event really happened if servers run in different zones. Store Instant in logs and databases.
  • now() without a zone on a server. The result depends on the server's OS settings — see the UTC server example.

Frequently Asked Questions

How do I get the current timestamp in milliseconds in Java?

Call Instant.now().toEpochMilli() or System.currentTimeMillis(): both return the number of milliseconds since 1970-01-01T00:00:00Z as a long. Keep the Instant itself if you need to compare, add a Duration or convert to a time zone. To measure how long code runs, use System.nanoTime(), which does not jump when the system clock is adjusted.

What is the difference between ZonedDateTime and OffsetDateTime?

OffsetDateTime stores a fixed offset such as -04:00, while ZonedDateTime stores a region such as America/New_York together with its daylight saving rules. Adding 6 months to a New York ZonedDateTime in July gives a January value with offset -05:00, while an OffsetDateTime keeps -04:00 and no longer matches New York clocks. Use OffsetDateTime for database columns and APIs, ZonedDateTime for calendar calculations in a region.

How do I change the default time zone of the JVM?

Start the JVM with -Duser.timezone=America/New_York or set the TZ environment variable of the process. TimeZone.setDefault() also works but changes the zone for the whole application at runtime, which is risky on shared servers. A safer approach is to leave the default alone and pass ZoneId or Clock to now() explicitly, as shown in the section now(ZoneId) and now(Clock).

Can I use java.time on Java 7 or older Android versions?

The java.time package exists only in Java 8 and later. For Java 6 and 7 there is the ThreeTen-Backport library with the same API in the org.threeten.bp package. On Android, java.time is available from API level 26; for older devices enable core library desugaring in the Android Gradle plugin.

Comments

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