Oracle Java SE 21 study materials
Oracle Java SE 21 study resources

The December Report Filed Under the Wrong Year

Oracle Java SE 21 · Put your knowledge to work in context.

All case studies

The December Report Filed Under the Wrong Year

A payroll service writes a nightly CSV summary. Each LocalDate is formatted with DateTimeFormatter.ofPattern("YYYY-MM-dd"). On Monday 30 December 2024 finance receives a row labelled 2025-12-30, so December totals land in the wrong fiscal year; Friday 27 December 2024 prints correctly. The same class totals reimbursements by summing double amounts and renders them with String.format("%,.2f", total); an amount computed as 2.675 prints as 2.67 and auditors report a one-cent mismatch against the ledger. A junior developer proposes adding a day to the parsed date to "correct" the year and switching the money fields to float to save memory. Output must be ISO-8601 and byte-identical on every JVM regardless of default locale and time zone.

1. Why does 30 December 2024 render as `2025-12-30` while 27 December 2024 renders correctly, and what should replace the pattern?

Read the worked answer

Pattern letters are not interchangeable:

  • y = year-of-era, u = proleptic year
  • Y = week-based year (ISO-8601 rules from WeekFields.ISO)

ISO week 1 is the week containing the first Thursday. Monday 2024-12-30 falls in ISO week 1 of 2025, so YYYY prints 2025. Friday 2024-12-27 is in week 52 of 2024, so it happens to print correctly - the bug is data-dependent, not fixed by the day of the week observed.

Correct options:

  • DateTimeFormatter.ofPattern("uuuu-MM-dd")
  • DateTimeFormatter.ISO_LOCAL_DATE

yyyy would also print the right digits for CE dates, but uuuu is unambiguous and is what the exam expects for parsing. Adding a day is wrong: it corrupts every other date in the report. LocalDate carries no zone, so the JVM time zone is not a factor here - it would be if the code used ZonedDateTime/Instant.

2. Why does the reimbursement total print as `2.67` when the intended value is 2.675, and what is the correct representation?

Read the worked answer

2.675 has no exact binary representation; the stored value is 2.67499999.... String.format("%.2f", ...) rounds that actual binary value, so it goes down, not up. The literal is not "2.675" at the moment the formatter sees it.

Fixes:

  • Use BigDecimal, constructed from a String or BigDecimal.valueOf(double) - never new BigDecimal(double), which captures the full binary error.
  • Do arithmetic with add, then setScale(2, RoundingMode.HALF_UP) once, at the boundary; do not round every intermediate step.
  • Format with NumberFormat.getCurrencyInstance(locale) for display.

float is strictly worse: a 24-bit significand produces the same class of error at larger magnitude, plus new rounding on every conversion. Saving memory on money is not a trade the exam rewards. Also keep summation order stable or sum BigDecimals, since double addition is not associative.

3. The report must be byte-identical everywhere, but a colleague runs with `-Duser.language=fr`. What breaks, and what is the rule?

Read the worked answer

ofPattern("dd MMM yyyy") and String.format("%,.2f") both resolve against Locale.getDefault(Locale.Category.FORMAT). Under fr the month becomes déc. and the decimal separator becomes a comma, so a golden-file comparison fails and any downstream parser breaks.

Rules to apply:

  • Machine-readable output: pin the locale (Locale.ROOT) or, better, use DateTimeFormatter.ISO_LOCAL_DATE and plain BigDecimal.toPlainString().
  • Human-facing output: choose the locale explicitly - ofLocalizedDate(FormatStyle.MEDIUM).withLocale(Locale.FRANCE), NumberFormat.getCurrencyInstance(locale) - rather than inheriting the JVM default.
  • The same reasoning applies to the default time zone: it is only relevant when an Instant or ZonedDateTime is converted to text, and then the zone should be passed explicitly, not read from the JVM.