skip to content

Explain the classic DateTimeFormatter pattern pitfalls: yyyy vs YYYY, MM vs MMM, and hh vs HH.

level: middleimportance: must knowfreq 78%

answer

  1. yyyy=calendar year, YYYY=week-year (off by 1 near New Year)
  2. MM=06 number, MMM=Jun name, MMMM=June; mm=minutes!
  3. dd=day-of-month, DD=day-of-year
  4. hh=12h needs 'a' (AM/PM); HH=24h
  5. Letters case-sensitive; 3+ letters => locale text

basics

~20 s

Lowercase yyyy is the calendar year; uppercase YYYY is the week-based year and can be wrong near New Year. MM is a two-digit month number, MMM is the month name. hh is 12-hour (needs AM/PM), HH is 24-hour.

solid answer

~50 s

Pattern letters are case-sensitive and easy to confuse. yyyy is the normal year-of-era; YYYY is the ISO week-based year, which differs from the calendar year for a few days around New Year (e.g. 30 Dec 2024 can fall in week-year 2025), so YYYY in a plain date pattern is almost always a bug. MM gives a zero-padded month number (06), MMM gives an abbreviated locale name (Jun), MMMM the full name (June) — these are very different outputs and require enough letters. dd is day-of-month, but DD is day-of-year (1-366), another silent trap. hh is the 12-hour clock (01-12) and is meaningless without an a (AM/PM) field; HH is the 24-hour clock (00-23). mm is minutes, MM is months — mixing them is a frequent typo. The fix: know that case matters, prefer yyyy-MM-dd HH:mm and reserve uppercase Y/D only when you truly mean week-year/day-of-year.

code

java · 7 lines
java
LocalDate d = LocalDate.of(2024, 12, 31);
System.out.println(d.format(DateTimeFormatter.ofPattern("yyyy-MM-dd"))); // 2024-12-31
System.out.println(d.format(DateTimeFormatter.ofPattern("YYYY-MM-dd"))); // 2025-12-31  (week-year!)

LocalTime t = LocalTime.of(14, 30);
System.out.println(t.format(DateTimeFormatter.ofPattern("HH:mm")));        // 14:30
System.out.println(t.format(DateTimeFormatter.ofPattern("hh:mm a", java.util.Locale.ENGLISH))); // 02:30 PM

go deeper

for a junior

Knows pattern letters are case-sensitive and can recite the common safe pattern yyyy-MM-dd HH:mm.

for a middle

Explains each pitfall (yyyy/YYYY, MM/MMM, dd/DD, hh/HH, mm) with a concrete example and can spot the bug in a given pattern.

for a senior

Understands ISO week-year semantics, locale sensitivity of text forms, and proactively code-reviews patterns; knows the kk/KK edge cases exist.

for a principal

Drives lint rules / static checks to catch YYYY/DD misuse, standardizes display vs interchange patterns org-wide, and reasons about locale + week-year correctness in reporting systems.

## How pattern strings work A *pattern* like `yyyy-MM-dd HH:mm` is a template: each **letter** is a placeholder for a date/time field, the **number of repeats** controls width/style, and any other characters are literal text. The letters are **case-sensitive** — `m` and `M` mean completely different things. That case-sensitivity is the source of nearly every formatter bug. ## yyyy vs YYYY (the New Year bug) - **`y` (lowercase) = year-of-era**, i.e. the ordinary calendar year. `2026`. - **`Y` (uppercase) = week-based year (ISO week-year)**. The ISO week-numbering scheme defines weeks Monday-Sunday and assigns the days around 1 January to whichever week 'owns' them. As a result the *week-year* can differ from the calendar year by one for a handful of days near New Year. - Example: 31 December 2024 is in ISO week 1 of 2025, so with pattern `YYYY` it formats as `2025`, while `yyyy` formats as `2024`. `YYYY` only makes sense when you also use week fields (`w` = week-of-year, `e`/`c` = day-of-week). In a plain calendar-date pattern it is almost certainly a bug. **Default to `yyyy`.** ## MM vs MMM vs MMMM, and the mm trap The `M` family is the month, but the count and case change the form: | Pattern | Output | Meaning | |---|---|---| | `M` | `6` | month number, no padding | | `MM` | `06` | month number, zero-padded to 2 digits | | `MMM` | `Jun` | abbreviated month **name** (locale-dependent) | | `MMMM` | `June` | full month **name** (locale-dependent) | | `MMMMM` | `J` | narrow name | Key points: 3+ letters switch from **number** to **text**, and text is **locale-sensitive** (`MMM` is `Jun` in English, `juin` in French). Also note **`mm` is minutes**, not months — `MM` vs `mm` is a classic copy-paste bug that produces `06` minutes when you meant June. ## dd vs DD - **`d` (lowercase) = day-of-month** (1-31). This is what you almost always want. - **`D` (uppercase) = day-of-year** (1-366). `DD` on 5 February gives `36`. Silent and wrong if you meant day-of-month. **Default to `dd`.** ## hh vs HH (and kk, KK) - **`h` = clock-hour of AM/PM, 1-12** — the 12-hour clock. It is meaningless on its own; you must add an **`a`** field for AM/PM, otherwise 1 PM and 1 AM both format/parse as `01` and parsing is ambiguous. - **`H` = hour-of-day, 0-23** — the 24-hour clock. Use this for unambiguous machine formats. - Less common: `k` = clock-hour 1-24, `K` = hour 0-11. Rarely needed. So `hh:mm` for 13:30 yields `01:30` (wrong without AM/PM); `HH:mm` yields `13:30`. ## A correct everyday pattern ```java DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss") // 2026-06-20 14:30:00 ``` And for a 12-hour, localized display you must include `a` and a locale: ```java DateTimeFormatter.ofPattern("hh:mm a", Locale.ENGLISH) // 02:30 PM ``` ## Mental checklist - Year: `yyyy` (lowercase) unless you genuinely use week numbering. - Month: `MM` number, `MMM`/`MMMM` name; never `mm` (that's minutes). - Day: `dd` (lowercase) day-of-month, not `DD` (day-of-year). - Hour: `HH` 24-hour; `hh` only with an `a` AM/PM field. - Letters are case-sensitive; text forms are locale-sensitive.

  • Give a concrete date where yyyy and YYYY differ.
    31 December 2024 is in ISO week 1 of 2025: yyyy -> 2024, YYYY -> 2025. Hence YYYY in a plain date pattern is usually a bug.
  • Why does the pattern hh:mm sometimes show the wrong hour?
    hh is the 12-hour clock (1-12). Without an 'a' AM/PM field, 13:00 formats as 01:00 and parsing back is ambiguous. Use HH for 24-hour time.

saying these in an interview costs you the question

  • Using YYYY in a normal date pattern (week-year bug near 31 Dec / 1 Jan)
  • Writing mm for month or MM for minutes
  • Using hh without an AM/PM field, then wondering why 13:00 prints as 01:00
  • Using DD expecting day-of-month
  • Assuming pattern letters are case-insensitive

context