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*()andwith*()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, soLocalTime.of(14, 0)is shown as14:00. For a stable format (reports, string comparison in tests) useDateTimeFormatter. 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?» useisEqual()or comparetoInstant().LocalDateTimefor event timestamps. Without a zone you cannot tell when an event really happened if servers run in different zones. StoreInstantin 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