skip to content

Why does Go's time formatting use the layout string "2006-01-02" instead of a pattern like "yyyy-MM-dd"?

level: juniorimportance: must knowfreq 78%

answer

  1. Go shows you an example, not letters
  2. One fixed instant, retyped in your shape
  3. The numbers count 1 through 7
  4. 01 is month, 02 is day
  5. 15 is the odd one: the 24-hour hour

basics

~20 s

Go has no pattern letters. A layout is one fixed reference instant, Mon Jan 2 15:04:05 MST 2006, written in the shape you want output. So 2006 means year, 01 month, 02 day, 15 hour, 04 minute, 05 second.

solid answer

~40 s

Go's `time` package uses example-based layouts rather than a pattern language. Every layout is the single reference instant `Mon Jan 2 15:04:05 MST 2006` typed out in the format you want, and `Format` and `Parse` both read the same layout. The numbers are mnemonic: `01/02 03:04:05PM '06 -0700` runs 1 through 7 for month, day, hour, minute, second, year and zone offset. `15` is the 24-hour hour. Anything in the layout that is not a recognised reference element is copied through verbatim, which is why `Format("YYYY-MM-DD")` returns the literal text `YYYY-MM-DD` with no error at all. In practice you rarely hand-write layouts: constants such as `time.RFC3339`, `time.RFC1123`, `time.DateOnly` and `time.Kitchen` cover most cases.

code

go · 6 lines
go
t := time.Date(2024, time.March, 5, 14, 9, 7, 0, time.UTC)

t.Format("2006-01-02")        // "2024-03-05"
t.Format("02 Jan 2006 15:04") // "05 Mar 2024 14:09"
t.Format(time.RFC3339)        // "2024-03-05T14:09:07Z"
t.Format("YYYY-MM-DD")        // "YYYY-MM-DD" — not an error, just literal text

go deeper

for a junior

Memorise the reference instant Mon Jan 2 15:04:05 MST 2006 and be able to write a layout for a date you are shown. Know that 2006 is the year, 01 the month, 02 the day and 15 the 24-hour hour.

for a middle

Explain why Format never errors on a bad layout and what it does with unrecognised text, and cover the padding variants (2, 02, _2) and the fractional-second forms .000 versus .999.

for a senior

Show the review habit: prefer the named constants over hand-written layouts, and require a test that asserts the exact rendered string, because a wrong layout is silent in both directions.

for a principal

Own the convention. Decide which layout constant every service emits, and treat hand-rolled layouts in shared code as a defect to be replaced with a named constant or a small shared helper.

## The idea Most languages give you a *pattern language* for dates: `yyyy-MM-dd`, `%Y-%m-%d`, `dd/MM/yyyy`. You have to learn which letter means what, and whether the letter is case-sensitive (in many of them `MM` is month and `mm` is minute, which is a famous source of bugs). Go took a different route. There is exactly one **reference instant**: ``` Mon Jan 2 15:04:05 MST 2006 ``` A layout string is that instant written the way you want your output to look. If you want `2024-03-05`, you write the reference date in that shape: `2006-01-02`. If you want `05 Mar 2024 14:09`, you write `02 Jan 2006 15:04`. The layout is a worked example, not a grammar. ## The mnemonic The reference instant is chosen so the numeric elements run 1, 2, 3, 4, 5, 6, 7 in a natural order: ``` 01/02 03:04:05PM '06 -0700 ``` - `01` — month (January) - `02` — day of month - `03` — hour on a 12-hour clock - `04` — minute - `05` — second - `06` — two-digit year (`2006` for four digits) - `-0700` — zone offset (MST is UTC-7) The one that breaks the pattern is **`15`** — the hour on a 24-hour clock, so called because 15:04 is 3:04 PM. If your layout has `15` you do not add `PM`; if it has `03` you almost always must. ## The full element vocabulary - **Year**: `2006` (four digit), `06` (two digit). - **Month**: `1` (unpadded), `01` (zero padded), `Jan`, `January`. - **Day**: `2`, `02` (zero padded), `_2` (space padded). Day of year: `002`, `__2`. - **Weekday**: `Mon`, `Monday`. - **Hour**: `15` (24 hour), `3`, `03` (12 hour, needs `PM` or `pm`). - **Minute**: `4`, `04`. **Second**: `5`, `05`. - **Fractional seconds**: `.000` / `.000000` / `.000000000` keep trailing zeros; `.999` / `.999999` / `.999999999` drop them, and drop the decimal point entirely when the fraction is zero. - **Zone**: `MST` (abbreviation), `-0700`, `-07:00`, `-07`, and the `Z` forms `Z0700` / `Z07:00`, which print a literal `Z` when the time is UTC and a numeric offset otherwise. ## Format never fails `Time.Format(layout string) string` returns a string and no error. It walks the layout looking for reference elements and copies everything else — separators, spaces, and any text that happens not to be an element — straight through. That is a feature for punctuation (`"Mon, 02 Jan 2006"` works) and a trap for habits from other languages: `t.Format("YYYY-MM-DD")` compiles, runs, and returns the four characters `YYYY`, a hyphen, `MM`, a hyphen and `DD`, forever, on every input. There is no error to catch; the only defence is a test that asserts the rendered string. The same vocabulary drives parsing. `time.Parse(layout, value)` uses the layout to say what each piece of the input means, and it *does* return an error when the value does not fit. ## Use the constants The package ships the common layouts as constants, and reaching for them is both shorter and safer than retyping the reference instant: - `time.RFC3339` = `"2006-01-02T15:04:05Z07:00"` — the one to default to for machine interchange. - `time.RFC3339Nano` = `"2006-01-02T15:04:05.999999999Z07:00"` — same, with sub-second precision and trailing zeros trimmed. - `time.RFC1123` = `"Mon, 02 Jan 2006 15:04:05 MST"`, and `time.RFC1123Z` with a numeric offset instead of an abbreviation — the HTTP-ish shape. - `time.DateOnly` (`"2006-01-02"`), `time.DateTime` (`"2006-01-02 15:04:05"`), `time.TimeOnly` (`"15:04:05"`), `time.Kitchen` (`"3:04PM"`). ## Why this design The payoff is that a layout is self-documenting: you can read `"02 Jan 2006"` and know exactly what comes out, without consulting a table of letters, and there is no `MM`/`mm` hazard. The cost is that the numbers are arbitrary until you have memorised them, and a mistyped element is not a syntax error — it is either silently literal text on the format side, or a silently different field on the parse side. That second cost is where the classic `01` versus `02` bug lives: both are valid elements, so swapping them parses many real dates without complaint.

  • Walk me through what each number in the reference layout means.
    Read it as `01/02 03:04:05PM '06 -0700`: 1 is the month, 2 the day of month, 3 the hour on a 12-hour clock, 4 the minute, 5 the second, 6 the two-digit year (`2006` for four digits) and `-0700` the zone offset. The exception is `15`, the 24-hour hour, chosen because 15:04 is 3:04 PM.
  • How do you get a zero-padded day versus a space-padded one?
    `02` is zero padded (`05`), `_2` is space padded (` 5`), and a bare `2` is unpadded (`5`). The same three-way choice exists for the month as `01`, and for the day of year as `002` and `__2`. Picking the wrong one is only a cosmetic bug when formatting, but when parsing, a fixed-width element will refuse input of the wrong width.
  • What is the difference between the fractional-second elements `.000` and `.999`?
    `.000` is fixed width: it always prints that many digits, trailing zeros included. `.999` prints the same precision but trims trailing zeros, and omits the decimal point entirely when the fraction is zero. `time.RFC3339Nano` uses `.999999999`, which is why a whole-second timestamp comes out with no fractional part at all.

Instead of handing you a grammar of pattern letters, Go hands you one worked example — a single date already written out — and asks you to rewrite that same date in the shape you want.

saying these in an interview costs you the question

  • Says Go uses yyyy/MM/dd style pattern letters
  • Thinks 01 is the day and 02 the month
  • Expects Format to return an error for a bad layout
  • Writes 03 for the hour and omits PM
  • Believes the reference numbers are arbitrary rather than 1-7 in order