Skip to content

Day 038 — time: Durations, Formatting & Timers

Month 2 · Week 2 · ⬅ Day 037 · Day 039 ➡ · Journal index

🎯 Learning Objective

Work confidently with time.Time and time.Duration: do duration arithmetic, parse/format with Go's reference layout, and use timers/tickers without leaking them.

📚 Topics

  • time.Duration (int64 nanoseconds) · unit constants · ParseDuration
  • The reference layout Mon Jan 2 15:04:05 MST 2006
  • Format / Parse · prebuilt layouts (RFC3339, Kitchen)
  • time.After, NewTimer, NewTicker, time.Since/Until
  • Monotonic vs wall clock

📖 Reading / Sources

📝 Notes

  • time.Duration is an int64 count of nanoseconds, not seconds. Always build it from unit constants: 90 * time.Minute, never a bare 90 → [[duration-is-nanoseconds]].
  • Time math uses methods, not operators: t.Add(d), t.Sub(t2) (returns a Duration), time.Since(t) (= Now().Sub(t)), time.Until(t). Round/Truncate snap a duration/time to a unit.
  • Formatting uses a magic reference date, not strftime: Mon Jan 2 15:04:05 MST 2006 (numerically 01/02 03:04:05PM '06 -0700). You write the layout as that exact date in the shape you want → [[reference-time-layout]].
  • Prefer the prebuilt layout constants when they fit: time.RFC3339, time.DateOnly, time.Kitchen. Parse is the inverse of Format using the same layout string.
  • Time zones matter: time.Now() is local; use .UTC() for storage/logs. Parse without a zone in the layout yields UTC; ParseInLocation controls it.
  • Timers/tickers:
  • time.After(d) returns a <-chan Time that fires once — perfect in a select for a timeout. (It can't be stopped; for tight loops prefer a reusable Timer.)
  • time.NewTimer(d) is the stoppable one-shot; Stop() it when no longer needed.
  • time.NewTicker(d) fires repeatedly; you must Stop() it (usually defer) or it leaks a goroutine/resource forever → [[always-stop-ticker]].
  • Monotonic clock: time.Now() embeds a monotonic reading, so Sub/Since measure true elapsed time even if the wall clock jumps (NTP/DST). Don't measure elapsed time by subtracting two formatted wall times.

💻 Code Examples

d := 90 * time.Minute
fmt.Println(d, d.Hours()) // 1h30m0s 1.5

t := time.Date(2026, time.August, 2, 15, 4, 5, 0, time.UTC)
fmt.Println(t.Format("2006-01-02 15:04")) // 2026-08-02 15:04

select {
case r := <-work:
    use(r)
case <-time.After(50 * time.Millisecond): // one-shot timeout
    fmt.Println("timed out")
}

Full code: examples/month-02/time-timers/main.go · Run: go run ./examples/month-02/time-timers

🏋️ Exercises / Practice

Exercise Status Link
Humanize(d) → compact 2d3h4m5s strings exercises/month-02/week-2/humandur

🐛 Mistakes Made

  • Wrote time.Sleep(100) expecting 100 ms — it's 100 nanoseconds. Needs 100 * time.Millisecond.
  • Tried strftime codes (%Y-%m-%d) in Format → use the reference date 2006-01-02 instead.
  • Forgot defer ticker.Stop() and leaked a goroutine in a long-running loop.

❓ Open Questions

  • When is time.Tick (the fire-and-forget helper) acceptable, given it can never be stopped?

🧠 Active Recall (answer without looking)

  1. Q: What is the reference time you use to build a Format/Parse layout, and why that value?

    A `Mon Jan 2 15:04:05 MST 2006` — chosen because its components are the sequence 1,2,3,4,5,6,7 (month=1, day=2, hour=3pm=15, minute=4, second=5, year=06, zone=-0700). You write the date in the shape you want output.

  2. Q: Why must you Stop() a time.Ticker, but a time.After channel needs no cleanup?

    A A `Ticker` keeps firing forever and holds runtime resources/a goroutine until stopped — leaking one is a real leak. `time.After` is a single-shot timer that fires once and is garbage-collected after; nothing to stop (though a `Timer` you create and abandon early should be `Stop()`ed).

🪶 Feynman Reflection

A Duration is just a big nanosecond counter, so you scale it with unit constants. Formatting is unusual: instead of cryptic %Y codes, Go shows you one specific date — January 2nd, 2006, 3:04:05 PM — and asks you to type that date the way you want yours to look. Timers are alarm clocks: After rings once, a Ticker rings on a schedule until you unplug it.

🕳️ Knowledge Gaps

  • Subtleties of Round vs Truncate across DST boundaries.
  • time.Timer.Reset correctness with a drained vs non-drained channel.

✅ Summary

I can do duration math, parse/format with the reference layout, choose After/Timer/Ticker correctly, and measure elapsed time with the monotonic clock via time.Since.

⏭️ Next Steps / Prep for Tomorrow

  • Day 039: the net/http client — building requests, reading responses, and closing bodies.

Time spent Difficulty Confidence
90 min 🟦🟦⬜⬜⬜ 🟦🟦🟦⬜⬜

Suggested commit: feat(examples): time durations, formatting, and timers (day 038)