What does truncatedTo do on a temporal value, and how does it differ from a TemporalAdjuster?
answer
- truncatedTo = drop sub-units, floor only, no rounding
- Allowed units: time-based, <= DAYS; MONTHS/YEARS throw
- Lives on time-bearing types + Instant, not LocalDate
- Adjuster moves the date; truncatedTo lowers precision
- Truncate before DB persist/compare to avoid nanos-vs-millis mismatch
basics
~10 struncatedTo cuts off everything smaller than the unit you give it. For example, time.truncatedTo(ChronoUnit.MINUTES) zeroes out seconds and nanoseconds. It does not move the date around like an adjuster does; it just drops sub-units.
solid answer
~40 struncatedTo(TemporalUnit) returns a copy of a time-bearing temporal with all fields smaller than the given unit set to their floor (zero). For instance, dateTime.truncatedTo(ChronoUnit.HOURS) keeps the date and hour but zeroes minutes, seconds, and nanoseconds. It is available on LocalTime, LocalDateTime, OffsetDateTime, ZonedDateTime, and Instant. The unit must be a time-based unit no larger than a day (DAYS works because a standard day is exactly 24 hours; MONTHS/YEARS throw UnsupportedTemporalUnitException because their length varies). It always truncates toward the start of the unit (floor), never rounds. Conceptually it differs from a TemporalAdjuster: an adjuster derives a *related date* (next Friday, end of month), whereas truncatedTo only *drops precision* on the same instant. They compose well — e.g. midnight of the first of next month is date.with(firstDayOfNextMonth()).atStartOfDay() or dateTime.truncatedTo(ChronoUnit.DAYS).
code
java · 10 linesLocalDateTime dt = LocalDateTime.of(2026, 6, 20, 14, 37, 52, 123_456_789);
dt.truncatedTo(ChronoUnit.MINUTES); // 2026-06-20T14:37
dt.truncatedTo(ChronoUnit.DAYS); // 2026-06-20T00:00
// Make a DB round-trip deterministic (DB keeps micros):
Instant now = Instant.now().truncatedTo(ChronoUnit.MICROS);
// Compose with an adjuster: midnight at the start of next month
dt.with(TemporalAdjusters.firstDayOfNextMonth())
.truncatedTo(ChronoUnit.DAYS); // 2026-07-01T00:00go deeper
Knows truncatedTo zeroes smaller fields and can call truncatedTo(ChronoUnit.MINUTES).
Lists which units are valid, knows it floors (never rounds), and contrasts it with adjusters.
Applies truncation to fix nanosecond-vs-millisecond persistence/equality bugs and combines it with adjusters for compound date math.
Sets precision conventions across the system (storage vs in-memory), audits equality/serialization for precision pitfalls, and reasons about Instant truncation at API/DB boundaries.
## What precision means here A `LocalDateTime` like `2026-06-20T14:37:52.123456789` carries fields all the way down to nanoseconds. Often you don't want that much precision — you want "the top of the hour" or "midnight of that day" or "the minute, with seconds dropped." `truncatedTo` is the tool for that. ## The contract `truncatedTo(TemporalUnit unit)` returns a **copy** with every field **smaller than `unit`** set to zero (its minimum). Examples on `2026-06-20T14:37:52.123456789`: - `truncatedTo(ChronoUnit.HOURS)` -> `2026-06-20T14:00` - `truncatedTo(ChronoUnit.MINUTES)` -> `2026-06-20T14:37` - `truncatedTo(ChronoUnit.SECONDS)` -> `2026-06-20T14:37:52` - `truncatedTo(ChronoUnit.DAYS)` -> `2026-06-20T00:00` It is **floor-only**: 14:37:52 truncated to minutes is 14:37, never 14:38. There is no rounding mode. `ChronoUnit` is the standard enum of time units (NANOS, MICROS, MILLIS, SECONDS, MINUTES, HOURS, HALF_DAYS, DAYS, ...). ## Which units are allowed The unit must be **time-based and at most a day**. `DAYS` is allowed because the implementation treats a day as exactly 24 hours of nanoseconds. Anything coarser — `WEEKS`, `MONTHS`, `YEARS` — throws `UnsupportedTemporalUnitException`, because those have variable lengths (a month isn't a fixed number of seconds), so "zero out everything below a month" isn't well-defined here. If you need "start of month," that's a *date* operation — use a `TemporalAdjuster` (`firstDayOfMonth()`), not `truncatedTo`. Likewise, calling `truncatedTo` on a pure `LocalDate` doesn't make sense (a date has no sub-day fields to drop) and isn't offered; truncation lives on the time-bearing types: `LocalTime`, `LocalDateTime`, `OffsetDateTime`, `ZonedDateTime`, and `Instant`. ## Truncating an Instant / zoned values `Instant.truncatedTo(ChronoUnit.SECONDS)` is the canonical way to drop sub-second precision before persisting or comparing, since many databases store only microsecond or millisecond precision. For `ZonedDateTime`/`OffsetDateTime`, truncation operates on the local time fields with the offset preserved. ## truncatedTo vs TemporalAdjuster This is the key conceptual distinction asked in interviews: - A **TemporalAdjuster** (applied via `with`) **moves you to a related date** — next Friday, last day of the month, first of next month. It changes *which* date/time you point at according to a calendar rule. - **truncatedTo** **keeps the same point in time but lowers its precision**, zeroing the smaller fields. They are orthogonal and combine naturally. "Midnight at the start of next month" = `dt.with(TemporalAdjusters.firstDayOfNextMonth()).truncatedTo(ChronoUnit.DAYS)`. "This minute, seconds dropped" = `dt.truncatedTo(ChronoUnit.MINUTES)`. ## Nanosecond vs millisecond gotcha A frequent real-world bug: you create a timestamp in code (nanosecond precision), store it in a DB column that keeps only microseconds/milliseconds, read it back, and equality comparisons fail because the stored value lost the lower digits. Truncating to the storage precision *before* comparing or persisting (e.g. `truncatedTo(ChronoUnit.MILLIS)`) makes round-trips deterministic. This is why truncation matters beyond cosmetics.
- How do you get the start of the current day from a LocalDateTime?dt.truncatedTo(ChronoUnit.DAYS) zeroes hours/minutes/seconds/nanos. For a LocalDate, use date.atStartOfDay() to get a LocalDateTime at 00:00 instead.
- Why might two timestamps that should be equal compare as unequal across a DB round-trip?In-memory java.time values carry nanosecond precision; many databases truncate to micro/millisecond. Truncate both sides (e.g. truncatedTo(ChronoUnit.MICROS/MILLIS)) before comparing to make round-trips deterministic.
saying these in an interview costs you the question
- Expecting truncatedTo to round (it always floors)
- Calling truncatedTo(ChronoUnit.MONTHS/YEARS) — throws UnsupportedTemporalUnitException
- Thinking truncatedTo changes the date the way an adjuster does
- Comparing a nano-precision Instant to a DB-read millis value without truncating first