time and context Recipes
time and context Recipes
Overview
time measures and schedules; context propagates deadlines and cancel across API boundaries. Together they implement request budgets.
Language-oriented time intro: Time. Concurrency-focused context: Context.
time essentials
now := time.Now()
t := time.Date(2026, 8, 3, 12, 0, 0, 0, time.UTC)
d := 1500 * time.Millisecond
d, err := time.ParseDuration("1h30m")Format and parse
Go’s reference time is Mon Jan 2 15:04:05 MST 2006 (layout by example):
const layout = time.RFC3339
s := now.UTC().Format(layout)
t, err := time.Parse(layout, s)Prefer RFC3339 / RFC3339Nano for APIs.
Timers and tickers
timer := time.NewTimer(2 * time.Second)
defer timer.Stop()
select {
case <-timer.C:
case <-ctx.Done():
if !timer.Stop() {
<-timer.C // drain if already fired (pre-1.23 habit; see release notes)
}
}
ticker := time.NewTicker(time.Second)
defer ticker.Stop()
for {
select {
case <-ctx.Done():
return ctx.Err()
case t := <-ticker.C:
_ = t
}
}Always Stop tickers to avoid leaks.
context essentials
ctx := context.Background()
ctx, cancel := context.WithCancel(ctx)
defer cancel()
ctx, cancel = context.WithTimeout(ctx, 3*time.Second)
defer cancel()
ctx, cancel = context.WithDeadline(ctx, time.Now().Add(time.Second))
defer cancel()Values (use sparingly)
type key int
const requestID key = 1
ctx = context.WithValue(ctx, requestID, "abc")
id, _ := ctx.Value(requestID).(string)Store request-scoped metadata (IDs), not optional parameters that belong in function args.
Waiting on cancel
select {
case <-ctx.Done():
return ctx.Err() // context.Canceled or DeadlineExceeded
case res := <-results:
return res, nil
}Recipes
Bound a function
func fetch(ctx context.Context, url string) ([]byte, error) {
req, err := http.NewRequestWithContext(ctx, http.MethodGet, url, nil)
if err != nil {
return nil, err
}
resp, err := http.DefaultClient.Do(req)
// ...
}Remaining budget
if dl, ok := ctx.Deadline(); ok {
remaining := time.Until(dl)
if remaining < 50*time.Millisecond {
return errBudgetExhausted
}
}Sleep that respects cancel
func sleep(ctx context.Context, d time.Duration) error {
t := time.NewTimer(d)
defer t.Stop()
select {
case <-ctx.Done():
return ctx.Err()
case <-t.C:
return nil
}
}Runnable example
go mod init example
go run .package main
import (
"context"
"fmt"
"time"
)
func work(ctx context.Context, name string, d time.Duration) error {
select {
case <-time.After(d):
fmt.Println(name, "done")
return nil
case <-ctx.Done():
fmt.Println(name, "canceled:", ctx.Err())
return ctx.Err()
}
}
func main() {
now := time.Now().UTC()
fmt.Println("rfc3339:", now.Format(time.RFC3339))
d, _ := time.ParseDuration("250ms")
fmt.Println("duration:", d)
ctx, cancel := context.WithTimeout(context.Background(), 200*time.Millisecond)
defer cancel()
_ = work(ctx, "slow", 500*time.Millisecond)
ctx2, cancel2 := context.WithTimeout(context.Background(), time.Second)
defer cancel2()
_ = work(ctx2, "fast", 50*time.Millisecond)
if dl, ok := ctx2.Deadline(); ok {
fmt.Println("remaining>0?", time.Until(dl) > 0)
}
}Expected output:
rfc3339: 2026-...
duration: 250ms
slow canceled: context deadline exceeded
fast done
remaining>0? true
What to notice: - Timeout cancels waiters blocked in select on ctx.Done(). - Always defer cancel() to free timer resources even on success. - Format APIs with RFC3339 unless you have a domain-specific layout.
Try next: Nest a child WithTimeout shorter than the parent and observe which deadline fires first.