To convert a LocalDate to a LocalDateTime, we add a time of day with atStartOfDay() or one of the atTime() methods, and to convert a LocalDateTime to a LocalDate, we call toLocalDate(), which drops the time. Neither direction involves a time zone, because both classes describe a local calendar date and clock time.
The conversion comes up when a date meets an API that works with timestamps, for example a search filter that receives a day but queries a TIMESTAMP column, or a booking that is stored with its time but shown on a calendar by day.
The following example turns a delivery date into date-times and back, with the result of each call as a comment.
LocalDate deliveryDay = LocalDate.of(2026, 10, 10);
LocalDateTime midnight = deliveryDay.atStartOfDay(); // 2026-10-10T00:00
LocalDateTime slot = deliveryDay.atTime(12, 30); // 2026-10-10T12:30
LocalDateTime exact = deliveryDay.atTime(LocalTime.of(18, 45, 10)); // 2026-10-10T18:45:10
LocalDate dayAgain = exact.toLocalDate(); // 2026-10-10
LocalTime timeOnly = exact.toLocalTime(); // 18:45:10
Notice that toString() leaves out zero seconds, so midnight prints as T00:00. The sections go through every way to add a time, the way back, the start of a day in a real time zone, and a query that selects all orders of one day.
1. What Each Conversion Adds or Drops
A LocalDateTime is a LocalDate and a LocalTime in one object. Going from a date to a date-time, we must supply the missing time, and going back, the time is thrown away and cannot be recovered from the LocalDate.

All of these methods return new objects, so the original value never changes. The LocalDateTime class itself is covered in the Java LocalDateTime tutorial, and the date class in the LocalDate guide.
2. LocalDate to LocalDateTime
The LocalDate class has five methods that return a LocalDateTime. They differ only in how we pass the time.
| Method | Time added | Result for 2026-10-10 |
|---|---|---|
| atStartOfDay() | Midnight, 00:00 | 2026-10-10T00:00 |
| atTime(int hour, int minute) | Hour and minute | atTime(9, 15) gives 2026-10-10T09:15 |
| atTime(int hour, int minute, int second) | Plus seconds | atTime(9, 15, 30) gives 2026-10-10T09:15:30 |
| atTime(int hour, int minute, int second, int nanoOfSecond) | Plus nanoseconds | atTime(9, 15, 30, 500_000_000) gives 2026-10-10T09:15:30.500 |
| atTime(LocalTime time) | Any LocalTime | atTime(LocalTime.NOON) gives 2026-10-10T12:00 |
2.1. atStartOfDay() for Midnight
The method atStartOfDay() returns the date at 00:00, which is the start of the day in the local calendar. We use it for the lower bound of a day range and for values that mean “from this day on”, such as the start of a subscription.
LocalDate trialStart = LocalDate.of(2026, 10, 10);
LocalDateTime startsAt = trialStart.atStartOfDay(); // 2026-10-10T00:00
LocalDateTime sameValue = trialStart.atTime(LocalTime.MIDNIGHT); // 2026-10-10T00:00
The overload atStartOfDay(ZoneId) returns a ZonedDateTime and handles days that do not start at midnight, as section 4 shows.
2.2. atTime() With Hours, Minutes, Seconds and Nanoseconds
The atTime() overloads take the time fields as numbers. The hour goes from 0 to 23, the minute and second from 0 to 59, and the nanosecond from 0 to 999,999,999. A value outside its range throws a DateTimeException that names the field.
LocalDate pickupDay = LocalDate.of(2026, 10, 10);
LocalDateTime pickup = pickupDay.atTime(17, 5); // 2026-10-10T17:05
LocalDateTime precise = pickupDay.atTime(17, 5, 30, 250_000_000); // 2026-10-10T17:05:30.250
LocalDateTime invalid = pickupDay.atTime(24, 0); // DateTimeException: Invalid value for HourOfDay (valid values 0 - 23): 24
There is no 24:00 in java.time. For the end of a day, we use the start of the next day as an exclusive bound, which section 5 uses.
2.3. LocalTime Constants and the Current Time
The atTime(LocalTime) overload accepts any LocalTime, including the constants MIDNIGHT, NOON and MAX. The static factory LocalDateTime.of(LocalDate, LocalTime) gives the same result, and some teams find it easier to read.
LocalDate day = LocalDate.of(2026, 10, 10);
LocalDateTime noon = day.atTime(LocalTime.NOON); // 2026-10-10T12:00
LocalDateTime lastNano = day.atTime(LocalTime.MAX); // 2026-10-10T23:59:59.999999999
LocalDateTime combined = LocalDateTime.of(day, LocalTime.of(8, 0)); // 2026-10-10T08:00
To combine a stored date with the current time, we pass LocalTime.now(clock). A fixed Clock in tests makes the result repeatable, and in production we pass a clock for the business zone.
Clock clock = Clock.fixed(Instant.parse("2026-10-10T06:30:00Z"), ZoneId.of("Europe/Madrid"));
LocalDateTime withNow = LocalDate.of(2026, 12, 24).atTime(LocalTime.now(clock)); // 2026-12-24T08:30
3. LocalDateTime to LocalDate
The method toLocalDate() returns a LocalDate with the same year, month and day, and toLocalTime() returns the time part. LocalDate.from(temporal) does the same for any type that has a date, which helps in generic code.
LocalDateTime placedAt = LocalDateTime.of(2026, 10, 10, 23, 58, 40);
LocalDate orderDay = placedAt.toLocalDate(); // 2026-10-10
LocalTime orderTime = placedAt.toLocalTime(); // 23:58:40
LocalDate fromTemporal = LocalDate.from(placedAt); // 2026-10-10
LocalDateTime cutToDay = placedAt.truncatedTo(ChronoUnit.DAYS); // 2026-10-10T00:00
The last line keeps the type. The method truncatedTo(ChronoUnit.DAYS) sets the time to midnight but returns a LocalDateTime, which suits code that compares or groups timestamps by day without a type change.
If the LocalDateTime was recorded in one zone and the day must be shown in another, dropping the time is not enough. We attach the source zone, move the value with withZoneSameInstant() and take the date there.
LocalDateTime recordedInLondon = LocalDateTime.of(2026, 10, 10, 23, 30);
LocalDate dayInTokyo = recordedInLondon.atZone(ZoneId.of("Europe/London")).withZoneSameInstant(ZoneId.of("Asia/Tokyo")).toLocalDate(); // 2026-10-11
4. Start of a Day in a Time Zone
Not every day starts at 00:00, because some zones move the clock forward at midnight when daylight saving time starts. The method atStartOfDay() without a zone still returns 00:00, which is a local time that never happened in that zone.
The overload atStartOfDay(ZoneId) checks the zone rules and returns the first valid time as a ZonedDateTime. In Cuba, clocks jump from 00:00 to 01:00 on March 8, 2026.
LocalDate dstStart = LocalDate.of(2026, 3, 8);
ZoneId havana = ZoneId.of("America/Havana");
LocalDateTime plainMidnight = dstStart.atStartOfDay(); // 2026-03-08T00:00
ZonedDateTime realStart = dstStart.atStartOfDay(havana); // 2026-03-08T01:00-04:00[America/Havana]
We use the zoned version whenever the start of the day becomes a moment, for example to compute an Instant for a database query in UTC. The start and end of day tutorial shows more variants, and LocalDate to ZonedDateTime covers the zoned conversions.
5. Selecting All Orders of One Day
Say a food delivery app shows a daily report, and the user picks October 10, 2026 in a date picker. The orders table has a TIMESTAMP column, so the LocalDate from the picker becomes two LocalDateTime bounds, the start of the day and the start of the next day.
record Order(int id, LocalDateTime placedAt) {}
static List<Order> placedOn(List<Order> orders, LocalDate day) {
LocalDateTime from = day.atStartOfDay();
LocalDateTime to = day.plusDays(1).atStartOfDay(); // exclusive upper bound
return orders.stream()
.filter(o -> !o.placedAt().isBefore(from) && o.placedAt().isBefore(to))
.toList();
}
List<Order> orders = List.of(new Order(1, LocalDateTime.of(2026, 10, 9, 23, 59, 59)), new Order(2, LocalDateTime.of(2026, 10, 10, 0, 0)), new Order(3, LocalDateTime.of(2026, 10, 10, 23, 59, 59, 900_000_000)), new Order(4, LocalDateTime.of(2026, 10, 11, 0, 0)));
List<Integer> ids = placedOn(orders, LocalDate.of(2026, 10, 10)).stream().map(Order::id).toList(); // [2, 3]
The same two bounds go into SQL as parameters. A half-open range with >= and < includes order 3 at 23:59:59.9 and works for any column precision.
SELECT id, placed_at
FROM orders
WHERE placed_at >= ? -- day.atStartOfDay()
AND placed_at < ? -- day.plusDays(1).atStartOfDay()
An upper bound of day.atTime(23, 59, 59) misses order 3. LocalTime.MAX is risky too, because MySQL rounds extra fractional digits by default, so 23:59:59.999999999 can become midnight of the next day and pull in order 4.
6. Comparing a LocalDate With a LocalDateTime
The two classes cannot be compared with each other. The method isBefore() of LocalDate takes a ChronoLocalDate, and the one of LocalDateTime takes a ChronoLocalDateTime, so mixing them is a compile error.
// does not compile: isBefore(ChronoLocalDate) cannot take a LocalDateTime
LocalDate due = LocalDate.of(2026, 10, 10);
boolean late = due.isBefore(LocalDateTime.of(2026, 10, 11, 9, 0));
We convert one side first. Which side depends on the rule, so for a “delivered by the due date” check we compare days, and for a “before the start of the due date” check we compare date-times.
LocalDate due = LocalDate.of(2026, 10, 10);
LocalDateTime delivered = LocalDateTime.of(2026, 10, 10, 21, 15);
boolean onTime = !delivered.toLocalDate().isAfter(due); // true
boolean beforeDueDay = delivered.isBefore(due.atStartOfDay()); // false
More comparison rules are in comparing LocalDate instances.
7. LocalDate and LocalDateTime Conversion FAQs
Search suggestions and code reviews of date range code keep raising the same questions.
7.1. How do we get the end of the day as a LocalDateTime?
We use date.atTime(LocalTime.MAX), which gives 23:59:59.999999999. For range checks, the start of the next day as an exclusive bound is safer, as section 5 explains.
7.2. Does atStartOfDay() use a time zone?
No. The method without arguments returns the local time 00:00 and knows nothing about zones. Only atStartOfDay(ZoneId) checks the zone rules, and it returns a ZonedDateTime instead of a LocalDateTime.
7.3. How do we convert a LocalDate to a LocalDateTime with the current time?
We call date.atTime(LocalTime.now(clock)). Passing a Clock or a ZoneId to now() makes the time come from the right zone, whereas LocalTime.now() without arguments uses the JVM default zone.
7.4. Can we convert a LocalDateTime to a LocalDate without losing data?
No. The time part is dropped, so the conversion cannot be reversed. We keep the original LocalDateTime when we need the time later, and use toLocalDate() only for display or grouping.
7.5. How do we convert a java.util.Date to a LocalDateTime or a LocalDate?
We go through an Instant and a zone, with LocalDateTime.ofInstant(date.toInstant(), zone) or LocalDate.ofInstant(date.toInstant(), zone). The details are in Date and LocalDateTime conversion and Date and LocalDate conversion.
8. Conclusion
A LocalDate becomes a LocalDateTime when we add a time, with atStartOfDay() for midnight or atTime() for any other time. The way back with toLocalDate() drops the time, and toLocalTime() keeps only the time.
Two details cause most bugs. A day in a real zone can start after midnight, so we use atStartOfDay(ZoneId) when the value becomes a moment, and a day range ends at the start of the next day, not at 23:59:59. We collect the other conversions between java.time types in the Java date and time guide.
9. References
- LocalDate.atStartOfDay() (Java SE 25 API)
- LocalDate.atTime() (Java SE 25 API)
- LocalDateTime.toLocalDate() (Java SE 25 API)
- LocalTime (Java SE 25 API)
Happy Learning !!
Please ignore my previous comment. I was debugging with desynchronized source code.
The java.time API is an improvement, but still has design issues compared to the best API currently being rolled out in the C++ standard for example. The documentation about the toLocalDate method of the LocalDateTime class is unfortunately too vague or missleading. Given a LocalDateTime which shows as 2019-10-25T00:00, and whose internal fields match those printed values, the toLocalDate method returns a LocalDate that’s 2019-10-24 on my system. I’ll have to resort to reading the actual source code implementation to have any clue what to do.