skip to content

Calendar and Elapsed Arithmetic

Adding a Duration is not the same as adding a calendar month: Add walks nanoseconds while AddDate walks dates, and ParseDuration turns a config string like 1h30m into one. Both come up constantly.

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

questions

4

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

level: juniorimportance: must knowfreq 78%

answer

  1. it is a number, not a struct
  2. one integer, one fixed unit
  3. int64 of nanoseconds
  4. multiply by a unit constant
  5. 3 * time.Second, 500 * time.Millisecond

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.

solid answer

~40 s

`time.Duration` is declared as `type Duration int64`, and the integer it holds is a count of nanoseconds. The `time` package exports unit constants — `time.Nanosecond`, `time.Microsecond`, `time.Millisecond`, `time.Second`, `time.Minute`, `time.Hour` — so you build a value by multiplying: `3 * time.Second`, `500 * time.Millisecond`, `2*time.Hour + 30*time.Minute`. That multiplication works because `3` is an untyped constant that takes on the `Duration` type; an `int` variable does not, so `n * time.Second` will not compile and you write `time.Duration(n) * time.Second`. Because a Duration is an integer, the ordinary arithmetic and comparison operators work on it directly. To get a plain number back out, use the methods — `d.Seconds()` returns a `float64`, `d.Milliseconds()` an `int64` — rather than a conversion, because `int64(d)` gives nanoseconds. Printing a Duration shows its `String()` form, such as `1h30m0s`.

code

go · 10 lines
go
timeout := 3 * time.Second
fmt.Println(timeout)        // prints: 3s
fmt.Println(int64(timeout)) // prints: 3000000000

n := 3
// timeout = n * time.Second  // compile error: mismatched types int and time.Duration
timeout = time.Duration(n) * time.Second

lease := 2*time.Hour + 30*time.Minute
fmt.Println(lease) // prints: 2h30m0s

go deeper

for a junior

Be ready to say a Duration is an int64 of nanoseconds and to write 3 * time.Second, 500 * time.Millisecond and 2time.Hour + 30time.Minute without hesitating.

for a middle

Explain why an untyped constant multiplies with time.Second while an int variable needs time.Duration(n), and name the methods that convert a Duration back to a number.

for a senior

Show judgment about where durations enter a service — configuration, flags, computed spans — and insist on Duration-typed values rather than bare ints whose unit lives only in a variable name.

for a principal

Own the convention across teams: timeouts and intervals in config files and exported APIs are time.Duration, never an int of unstated milliseconds, so the unit cannot be lost at a service boundary.

## The type is a number, not a record In Go's standard library the type is declared as: type Duration int64 That is the whole story about its representation. A `time.Duration` is a single 64-bit signed integer, and the unit of that integer is **nanoseconds**. It does not hold a start instant, an end instant, a unit field, or a calendar component. `time.Duration(1500000000)` and `1500 * time.Millisecond` and `1.5 * float64` converted appropriately all denote the same thing: 1,500,000,000 nanoseconds. This is different from the duration types in many other languages, which are structs or objects carrying an amount plus a unit. Go collapses that to one integer with a fixed unit, which is why duration arithmetic in Go is just integer arithmetic. ## Building a duration The package exports one constant per unit: - `time.Nanosecond` = 1 - `time.Microsecond` = 1000 * Nanosecond - `time.Millisecond` = 1000 * Microsecond - `time.Second` = 1000 * Millisecond - `time.Minute` = 60 * Second - `time.Hour` = 60 * Minute Notice where the list stops. There is no `time.Day` and no `time.Week`, because a calendar day is not a fixed number of nanoseconds everywhere — that kind of arithmetic belongs to `time.Time.AddDate`, not to `Duration`. You compose values by multiplying and adding: timeout := 3 * time.Second poll := 500 * time.Millisecond lease := 2*time.Hour + 30*time.Minute ## Why an untyped constant works and an int variable does not Go has no implicit conversion between named integer types. `3 * time.Second` compiles because `3` is an *untyped constant*: it has no type of its own until context gives it one, and the context here is `time.Duration`, so it becomes a Duration constant. Write the same thing with a variable and it fails: n := 3 d := n * time.Second // invalid operation: mismatched types int and time.Duration The fix is an explicit conversion of the number, not of the constant: d := time.Duration(n) * time.Second A very common bug is to convert the wrong way round — `time.Duration(n)` on its own means *n nanoseconds*, which for `n := 3` is three nanoseconds, not three seconds. Always multiply by the unit you mean. ## Getting a number back out Because the underlying type is `int64`, a conversion is legal but almost never what you want: - `int64(d)` — nanoseconds. Correct, but easy to misread as "the number of whatever unit I was thinking of". - `d.Nanoseconds()`, `d.Microseconds()`, `d.Milliseconds()` — `int64`, truncated toward zero. - `d.Seconds()`, `d.Minutes()`, `d.Hours()` — `float64`, so fractions survive. Dividing by a unit constant also works and is idiomatic when you want a whole number of units: `d / time.Millisecond` yields a `Duration` whose value is the count of milliseconds, so it usually appears wrapped, as `int64(d / time.Millisecond)`. ## Arithmetic and comparison Since it is an integer type, everything you expect works: `a + b`, `b - a`, `d * 2`, `d / 2`, `a < b`, `a == b`. Multiplying two durations together is legal Go but meaningless (nanoseconds squared), so treat `d1 * d2` as a bug. Durations can be negative — `time.Until(t)` for a past `t` returns a negative value, and `-1 * time.Second` is fine. The range is the range of `int64` nanoseconds: roughly ±292 years. That is plenty for timeouts and elapsed measurements and not enough for calendar spans, which is another reason calendar work uses `time.Time` and `AddDate`. ## Printing `Duration` implements `String()`, and the format is a human-readable sum of units with the largest unit first: `1h30m0s`, `1.5s`, `500ms`, `0s`. `fmt.Println(3 * time.Second)` therefore prints `3s`, not `3000000000`. This is why durations read well in logs without any formatting work, and why a log field that shows a raw nanosecond count is a sign someone converted to `int64` on the way out. ## Where durations come from You rarely type them all by hand. `time.Time.Sub`, `time.Since` and `time.Until` return durations; `time.ParseDuration` builds one from a string such as `"1h30m"`; `flag.Duration` reads one from the command line. The value you get from any of them is the same plain int64 of nanoseconds, so it composes with everything above.

  • Why does `n * time.Second` fail to compile when `n` is an `int` variable, and what is the fix?
    Go has no implicit conversion between named types, so an `int` variable and a `time.Duration` cannot be multiplied. The literal `3` works only because an untyped constant adopts the Duration type from context. Convert the number explicitly and multiply by the unit: `time.Duration(n) * time.Second`. Writing `time.Duration(n)` alone means n nanoseconds, which is the usual off-by-a-billion bug.
  • How would you log a duration as a whole number of milliseconds?
    Call `d.Milliseconds()`, which returns an `int64` truncated toward zero. `int64(d / time.Millisecond)` is equivalent and also idiomatic. What you must not do is `int64(d)` — that is nanoseconds, and it silently produces a number a million times too large. If you want fractions, `d.Seconds()` returns a `float64`.
  • What is the largest span a `time.Duration` can represent?
    About 292 years in either direction, because it is an int64 of nanoseconds. `time.Time.Sub` saturates rather than wrapping: if the gap between two instants exceeds the range, it returns the maximum or minimum Duration. For spans measured in years, keep working with `time.Time` values and calendar arithmetic instead of durations.

saying these in an interview costs you the question

  • Calls time.Duration a struct holding a start and an end
  • Thinks int64(d) yields seconds rather than nanoseconds
  • Writes time.Second(3) as though the constant were a function
  • Expects n * time.Second to compile for an int variable
  • Uses time.Duration(3) expecting three seconds
  • Assumes printing a Duration shows the raw nanosecond count
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

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

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