skip to content

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

level: middleimportance: should knowfreq 52%

answer

  1. a sum of number-plus-suffix terms
  2. the suffix list stops somewhere
  3. nanoseconds up to hours, nothing longer
  4. no calendar units in an elapsed type
  5. "2h45m" parses, "1d" does not

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.

solid answer

~50 s

`time.ParseDuration(s string) (time.Duration, error)` accepts a possibly signed sequence of decimal numbers, each with an optional fraction and a required unit suffix. The valid units are `"ns"`, `"us"` (also written with the micro sign), `"ms"`, `"s"`, `"m"` and `"h"`. Terms are summed, so `"2h45m"` is two hours plus forty-five minutes, and fractions are allowed, so `"-1.5h"` and `"1.5s"` are valid. Anything else is an error: `"1d"` fails with an unknown-unit error because `time.Duration` is a fixed count of nanoseconds and a calendar day is not a fixed span — day-level arithmetic belongs to `time.Time.AddDate`. A number with no unit, such as `"5"`, is also an error. The output of `Duration.String()` is designed to round-trip back through `ParseDuration`, which is why configuration values are best stored in that same form and parsed once at startup rather than as bare ints.

code

go · 11 lines
go
d, err := time.ParseDuration("1h30m")
// d == 90 * time.Minute, err == nil

d, err = time.ParseDuration("-1.5h")
// d == -90 * time.Minute, err == nil

_, err = time.ParseDuration("1d")
// err != nil: the unit "d" is not one of ns, us, ms, s, m, h

_, err = time.ParseDuration("5")
// err != nil: no unit suffix, and there is no default unit

go deeper

for a junior

Recall that the units run from ns to h and that terms are written together, as in "2h45m". Know that time.ParseDuration returns an error you must check, not just a value.

for a middle

Explain why the grammar stops at hours: a Duration is a fixed nanosecond count, so calendar-length units would be a lie. Be able to name the errors — unknown unit, missing unit, embedded space.

for a senior

Argue for durations in configuration as strings parsed at startup with a fail-fast error naming the key, rather than numbers whose unit is documented only in a comment.

for a principal

Own the answer when operators want a day or week suffix: decide as a policy whether such settings are elapsed time or calendar steps, and keep the two out of one field rather than bolting a custom unit onto a standard format.

## The grammar `time.ParseDuration` turns a string into a `time.Duration`. Its input is a **possibly signed sequence of decimal numbers, each with an optional fraction and a unit suffix**. Formally that means one or more terms glued together with no separator, optionally preceded by `+` or `-`: - `"300ms"` - `"1.5s"` - `"2h45m"` - `"-1.5h"` - `"1h30m10s"` The terms are summed. `"2h45m"` is `2*time.Hour + 45*time.Minute`. A leading sign applies to the whole thing, so `"-1h30m"` is negative ninety minutes, and negative durations are perfectly ordinary values in Go. ## The unit set — and what is missing The valid suffixes are exactly: | suffix | meaning | |---|---| | `ns` | nanoseconds | | `us` (or the micro sign) | microseconds | | `ms` | milliseconds | | `s` | seconds | | `m` | minutes | | `h` | hours | That is the complete list. There is no `d`, no `w`, no `y`. `time.ParseDuration("1d")` returns a zero Duration and an error reporting an unknown unit `"d"`. The reason is not an oversight. A `time.Duration` is a fixed count of nanoseconds — an *elapsed* quantity. "One day" is a calendar notion whose length in elapsed time is not always the same, and "one month" is worse still. Go keeps the two kinds of arithmetic in separate APIs: elapsed spans are Durations added with `time.Time.Add`, calendar steps are integers passed to `time.Time.AddDate(years, months, days)`. Refusing a `d` suffix keeps a config file from quietly promising something the type cannot deliver. If you genuinely mean 24 hours of elapsed time, write `"24h"` — and then you have said the honest thing. A number with no unit at all, such as `"5"`, is also rejected: there is no default unit to fall back on. ## Errors The error path matters because `ParseDuration` is almost always fed a value a human typed into configuration or a flag. The failures you will actually see are an unknown unit (`"1d"`, `"10 sec"`), a missing unit (`"5"`), and a completely unparseable string (`""`, `"abc"`). Whitespace is not accepted, so `"1h 30m"` fails while `"1h30m"` succeeds — a surprisingly common support ticket. Because the error message includes the offending input, wrapping it with the config key that supplied it is usually enough context for whoever has to fix the file: d, err := time.ParseDuration(raw) if err != nil { return fmt.Errorf("config key %q: %w", key, err) } ## Round-tripping with String `Duration.String()` emits a form that `ParseDuration` accepts: `1h30m0s`, `500ms`, `1.5s`, `0s`. The two are deliberate inverses. Two consequences follow. First, a duration written into a log, a config template or a generated file can be read back without a custom format. Second, `String()` output uses the same unit vocabulary, so it never emits a `d` — a very long duration prints as hours, for example `72h0m0s` rather than `3d`. ## Where this shows up in real code Three entry points cover most services: 1. **Configuration files and environment variables.** Store the value as a string (`"30s"`, `"5m"`) and call `ParseDuration` once at startup, failing fast on error. Storing a bare number instead forces every reader to remember whether the unit was seconds or milliseconds — the unit lives in the string, where it can be checked. 2. **Command-line flags.** `flag.Duration("timeout", 30*time.Second, "request timeout")` and `flag.DurationVar` parse the value with the same grammar, so `-timeout=1m30s` works out of the box and a bad value is reported by the flag package. 3. **User-supplied intervals.** Anywhere an operator types a retention window or a poll interval, this grammar is the least surprising thing to accept, because it is what every other Go tool accepts. ## The mental model to carry away `ParseDuration` is the text front door to a type that is nothing but an int64 of nanoseconds. Everything about its behaviour follows from that: units only as far as hours, no calendar units, fractions allowed because the underlying integer is fine-grained, and a `String()` form that is the exact inverse. When a request for a `d` suffix comes up in review, the answer is not "add a helper that multiplies by 24" — it is to decide whether the value is elapsed time (write `"24h"`) or a calendar step (store an integer number of days and use `AddDate`).

  • A config file says `poll: "1h 30m"` and parsing fails. What is wrong?
    The space. `time.ParseDuration` accepts terms concatenated with no separator, so `"1h30m"` parses and `"1h 30m"` does not. Whitespace is not skipped anywhere in the grammar, including around a leading sign. Trimming the whole string is fine; removing interior spaces silently would hide typos, so the better fix is a clear error naming the config key and the rejected value.
  • Your operators keep asking for a `"7d"` retention setting. What do you do?
    Decide which kind of arithmetic they mean. If it is elapsed time, `"168h"` says exactly that and stays inside the standard grammar. If they mean seven calendar days — the same wall-clock time a week later — the value is not a Duration at all; store an integer day count and apply it with `time.Time.AddDate(0, 0, 7)`. A custom parser that maps `d` to 24 hours quietly commits you to the first answer while sounding like the second.
  • Does `Duration.String()` always produce something `ParseDuration` can read back?
    Yes — the two are designed as inverses, which is why `String()` sticks to the same unit vocabulary and prints `72h0m0s` rather than inventing a day suffix. That round trip is what makes durations safe to write into generated config, logs and test fixtures without a bespoke format.

saying these in an interview costs you the question

  • Believes "1d" parses as twenty-four hours
  • Thinks a bare number defaults to seconds
  • Expects spaces between terms to be ignored
  • Stores timeouts as bare ints with the unit only in the name
  • Assumes ParseDuration accepts words like "1 hour"