Highly reliable Go code - use clocks not time

Q: How can you tell if someone is a programmer?

A: Easy! Say “time zones” and see if they flinch!

Time and clocks always sound straightforward when you don’t think too hard about them. Unfortunately for us, the job of a programmer is to think hard about the things they are building. And one of the hardest things to get right is testing code that depends on time.

Imagine you have a cache that expires items after an hour, or a retry mechanism that waits 30 seconds between attempts. How do you test these without waiting? Your unit tests would take forever to run!

When working with Go, you often see time.Sleep() and time.Now(). In C the equivalents might be sleep() or usleep() and time(). In C++ you should probably use std::this_thread::sleep_for() and system_clock::now(). In Python it’s datetime.datetime.now() and time.sleep().

In all of these functions, you consult data derived from the OS-managed hardware clock. Because that clock is managed outside of your code, you don’t control it! Worse, you are up a creek when it comes to testing. I would like to propose that, instead of using these global functions, you define an interface called Clock and provide at least two implementations: SystemClock and StaticClock.

In Go this would be:

package clocks

type Clock interface {
    Now() time.Time
    Sleep(time.Duration)
}

type SystemClock struct{}
func (SystemClock) Now() time.Time { return time.Now() }
func (SystemClock) Sleep(d time.Duration) { time.Sleep(d) }

type StaticClock struct {
    Time time.Time
}
func (sc *StaticClock) Now() time.Time { return sc.Time }
func (sc *StaticClock) Sleep(d time.Duration) { sc.Time += d }

With these two implementations, you can embed the clock in your code you want to test. Which means that you can cause your code to use the static clock! All of its sleeps will be instantaneous - you can verify using Now() that time has passed, but you will never have to wait for a Sleep() to complete. They have been transformed into additions!

To use this, you’ll need to change the objects you have that use time to have another class member - the clock. Then at test time you can pass in a StaticClock and at runtime you can pass in the SystemClock. By testing with clock objects, you can make your unit tests both faster and significantly less flaky.

Just for completeness, here’s clocks in Python:

import datetime, time

class SystemClock(object):
    def now(self) -> datetime.datetime:
        return datetime.datetime.now()
    
    def sleep(self, seconds: float):
        time.sleep(seconds)


class StaticClock(object):
    def __init__(self, currentTime: datetime.datetime):
        self.currentTime = currentTime
    
    def now(self) -> datetime.datetime:
        return self.currentTime
    
    def sleep(self, seconds: float):
        self.currentTime += datetime.timedelta(seconds=seconds)

Using these clocks, you can decide whether you want the OS to be in control of your code’s perception of time, or whether you want to be in control. I find that in unit tests I almost always want to be in control, while in production I want to hand control to the OS.

This is called “dependency injection” when generalized, and can be of great help in many contexts. In my opinion, clocks are one of the very best dependencies to inject.

A Practical Example

Let’s look at a real-world example. Suppose you have a cache that needs to expire items after a certain duration:

type Cache struct {
    clock  Clock
    items  map[string]item
}

type item struct {
    value      string
    expiresAt  time.Time
}

func NewCache(clock Clock) *Cache {
    return &Cache{
        clock: clock,
        items: make(map[string]item),
    }
}

func (c *Cache) Set(key string, value string, ttl time.Duration) {
    c.items[key] = item{
        value:     value,
        expiresAt: c.clock.Now().Add(ttl),
    }
}

func (c *Cache) Get(key string) (string, bool) {
    item, exists := c.items[key]
    if !exists {
        return "", false
    }
    if c.clock.Now().After(item.expiresAt) {
        return "", false
    }
    return item.value, true
}

Now testing this cache becomes trivial:

func TestCacheExpiration(t *testing.T) {
    clock := &StaticClock{Time: time.Now()}
    cache := NewCache(clock)
    
    cache.Set("key", "value", 5*time.Minute)
    
    // Advance time by 4 minutes
    clock.Sleep(4 * time.Minute)
    value, exists := cache.Get("key")
    if !exists {
        t.Error("Item should still exist")
    }
    
    // Advance time by 2 more minutes
    clock.Sleep(2 * time.Minute)
    _, exists = cache.Get("key")
    if exists {
        t.Error("Item should have expired")
    }
}

This test runs instantly, is deterministic, and clearly demonstrates the cache’s behavior. Without StaticClock, this test would take 6 minutes!

In production, you would use:

cache := NewCache(&SystemClock{})

Key Benefits

  1. Testability: Tests run instantly and are deterministic
  2. Control: You decide when and how time advances
  3. Isolation: Your code is isolated from system clock changes
  4. Clarity: Dependencies on time are explicit in your interfaces

Use it!

This pattern is your friend whenever you need to:

This is not a new trick, but it’s very useful and it is under-used, and it can make your life much better when you need to test code that relies on time.

All code in this blog post is CC0, which means you can freely use it however you want.

Happy coding!

Contribute to the discussion by replying on BlueSky!