skip to content

Instants, Durations and Clocks

time.Time values, Duration arithmetic, the 2006-01-02 reference layout and zone loading, plus the hidden monotonic reading that makes Sub and == disagree.

part ofGo (Golang)overview, primer and where to startread it →
on this pageshow

explore

questions

17

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
open as a page

What two clock readings does Go's time.Now() put into one time.Time, and which one does time.Since use?

level: juniorimportance: must knowfreq 50%

basics

~20 s

time.Now() returns a time.Time holding both a wall-clock reading, which tells the calendar date and time and can be stepped by the system, and a monotonic reading, which only moves forward. time.Since subtracts using the monotonic reading.

open as a page

In Go, what is a time.Duration and how do you express a three-second value?

level: juniorimportance: must knowfreq 78%

basics

~20 s

A time.Duration is an int64 count of nanoseconds, not a struct, so durations are plain numbers you can add, subtract and compare. Write three seconds as 3 * time.Second, multiplying by one of the time package's unit constants.

open as a page

What do time.UTC, time.Local and t.In(loc) mean for a Go time.Time value?

level: juniorimportance: must knowfreq 52%

basics

~20 s

A time.Time carries an instant plus a pointer to a time.Location that decides how its calendar fields print. time.UTC and time.Local are two such locations, and t.In(loc) returns the same instant displayed in loc, not a different point in time.

open as a page

What zone does time.Parse assign when the input carries no offset, and how does time.ParseInLocation differ?

level: middleimportance: must knowfreq 60%

basics

~20 s

time.Parse returns a time in UTC whenever the value carries no zone offset or abbreviation, so a local wall-clock string is silently read as UTC. time.ParseInLocation reads the same digits as wall-clock time in a *time.Location you supply.

open as a page

What does encoding/json emit for a time.Time field, and what timestamp input will it reject?

level: middleimportance: should knowfreq 52%

basics

~20 s

A time.Time marshals to a quoted RFC 3339 string with sub-second precision, because time.Time implements MarshalJSON. Decoding accepts only a quoted RFC 3339 string, so an epoch number or a space-separated datetime fails with an unmarshal error.

open as a page

Why can two time.Time values for the same instant differ under ==, and when must you use Equal?

level: middleimportance: should knowfreq 52%

basics

~20 s

== compares a time.Time's whole representation: its wall reading, its monotonic reading and its location pointer. Two values for the same instant differ if one still carries a monotonic reading or sits in another location. Equal compares only the instant.

open as a page

Which time.Time operations strip its monotonic clock reading, and what changes once one has?

level: middleimportance: should knowfreq 42%

basics

~20 s

Operations that reinterpret or recompute the wall clock strip it: UTC, Local, In, Round, Truncate, AddDate, and every encoding such as MarshalJSON or GobEncode. Add keeps it. Once stripped, Sub, Before, After and Equal fall back to the wall clock.

open as a page

Which duration strings does Go's time.ParseDuration accept, and why does "1d" fail?

level: middleimportance: should knowfreq 52%

basics

~10 s

time.ParseDuration accepts signed decimal numbers carrying the unit suffixes ns, us, ms, s, m and h, so "300ms", "-1.5h" and "2h45m" all parse. There is no day unit, so "1d" returns an error.

open as a page

Where does time.LoadLocation get zone data, and what does importing time/tzdata change?

level: middleimportance: should knowfreq 44%

basics

~20 s

time.LoadLocation searches the ZONEINFO directory or zip, then the system zoneinfo directory, then $GOROOT/lib/time/zoneinfo.zip, then the copy embedded by time/tzdata if that package is imported. Blank-importing time/tzdata makes a binary work where no zone files exist.

open as a page

A Go webhook parses partner dates with layout "01/02/2006", but the partner sends day-first. Why do the bugs appear only after the 12th?

level: seniorimportance: should knowfreq 44%

basics

~20 s

In a Go layout 01 is the month and 02 the day. Days of 12 or under are valid either way, so Parse succeeds and stores the wrong date. From the 13th the month is out of range and Parse errors.

open as a page

time.Since returns a negative duration for a start time.Time your harness reloaded from JSON — how do you diagnose and fix it?

level: seniorimportance: should knowfreq 30%

basics

~20 s

Decoding drops the monotonic reading, so time.Since fell back to subtracting wall clocks, and the system clock was stepped backwards between the two readings. Print both values and look for the m= suffix, then measure from an in-process time.Time instead.

open as a page

A billing job computes the nth renewal as start.AddDate(0, n, 0); for a customer who signed up on 31 January 2025, why do two charges land in March?

level: seniorimportance: should knowfreq 44%

basics

~20 s

AddDate adds the fields and then normalises like time.Date, so 31 January plus one month becomes 31 February, which rolls forward to 3 March 2025. Plus two months is 31 March, so February is skipped and March billed twice.

open as a page

How does Go's time.Date behave for a local time erased or repeated by a DST change?

level: seniorimportance: should knowfreq 34%

basics

~10 s

time.Date never reports the problem: for a local time erased by a spring-forward gap or repeated by a fall-back overlap, it returns a Time correct in one of the two offsets, without saying which.

open as a page

Why doesn't Truncate(24 * time.Hour) on a time.Time give you local midnight?

level: middleimportance: nice to knowfreq 32%

basics

~20 s

time.Time.Truncate rounds an instant down to a multiple of the duration measured from Go's zero time, January 1 of year 1 UTC, never from the calendar presentation. For a day boundary, rebuild the instant with time.Date.

open as a page

Should your webhook API mandate RFC 3339 timestamps or accept each partner's own date format at the edge?

level: principalimportance: nice to knowfreq 30%

basics

~20 s

Publish one required format and always emit it; accept deviations only as a short, explicitly enumerated per-partner list with fixtures and an end date. The cost of a permissive edge is not the parsing code, it is owning an unbounded accepted set forever.

open as a page

How do you decide whether Go services embed time/tzdata or rely on the image's zoneinfo?

level: principalimportance: nice to knowfreq 22%

basics

~20 s

Decide by who must fix a wrong zone rule, and how fast. Embedding time/tzdata makes a binary self-contained but pins zone data to the toolchain that built it; relying on the base image lets the platform team patch it without a rebuild.

open as a page