time.Now() and the Monotonic Clock

Avatar of the author Willem Schots
11 Jan, 2024 (Updated 12 Jan, 2024)
~10 min.
RSS

In the documentation of Go’s time package you will find the term “monotonic clock” roughly 30 times. However, you likely won’t ever work with it directly. So, what is it for and why should you care?

In the previous article, we looked at how time.Time values represent “time instants” on timelines using a time.Location reference.

When created using time.Now() a time.Time can also contain a different time representation: The monotonic clock reading.

It’s represented on a timeline that is entirely seperate from to the ones provided by time.Location.

For example, a time.Time value in the time.UTC location with a monotonic clock reading would look like this when visualized:

Location and monotonic timelines of a time.Time value
Time instant represented by a time.Time value has two elements, a location and a monotonic clock reading.

In this article we will take a look at:

  • What it means for a clock to be monotonic.
  • Why this is a desired property when “measuring time”.
  • How this all works in the time package.
  • And finally, what it means for your programs.

Let’s check

To begin, let’s take a look at a monotonic clock reading for ourselves.

Run the example below and take a look at the output.

main.go
package main

import (
	"fmt"
	"time"
)

func main() {
	tUTC := time.Date(2023, 12, 19, 13, 37, 0, 0, time.UTC)
	tNow := time.Now()

	fmt.Println(tUTC.String())
	fmt.Println(tNow.String())
}

As you can see, the time created via time.Now() contains an additional value at the end of its String() output. Something like:

... m=+0.000000001

This is the monotonic clock reading.

It’s included in the output of the String() method for debugging purposes, but it’s not exported by the time.Time type.

To explain why it’s not exported, we need to zoom out a bit and look at monotonic clocks and “wall clocks”.

What is a monotonic clock?

A clock is monotonic when it gives a guarantee about its readings: Every new reading is always greater than or equal to (>=) the previous reading.

In other words, it can’t be “turned back”.

For example, suppose we have a clock that always returns integer values.

The following readings are monotonic:

1 2 2 3 4 5 6 8 10

Every reading is either:

  • Equal to the previous reading.
  • Greater than the previous reading.

These readings are not monotonic:

1 2 2 1 2 3 3 4 6

Inbetween the third (2) and fourth (1) reading the clock was “set back” one unit, resulting in the fourth reading being lower than the one before.

Conceptually you might expect all clocks to be monotonic, but that’s not the case.

What is a wall clock?

When you look at a clock on the wall and say “it’s 13:37” that might seem like an easy thing to do.

However, if you dig a bit deeper it quickly turns rather complex: These wall clocks are generally coordinated using UTC and bound to a geographic location.

The coordination depends on human factors and is a social issue. I won’t dig into the details (check out my guide for that), but the rules change all the time in ways that can’t be computed or predicted:

  • Daylight saving time.
  • Time zone changes.
  • Leap seconds.

As a result wall clocks are turned back all the time.

They are not monotonic.

Telling time vs measuring time

The difference between wall clocks and monotonic clocks is sometimes described as:

  • Wall clocks are for “telling time”: assigning a useful meaning to a clock reading.
  • Monotonic clocks are for “measuring time”: showing the passage of time between clock readings.

A wall clock can be used to tell time in a way that is coordinated with the rest of the world. For example, you can see it’s 13:37 in Iceland, and determine that corresponds to 12:37 in Cape Verde.

Wall clocks are inconvenient for measuring time. If you attempt to measure the interval between two clock readings, you need to compensate for any changes made to the clock inside that interval.

In contrast, to get the interval of two monotonic clock readings (from the same clock) you can subtract one from the other. There is no risk of the clock being set back.

However, monotonic clocks are not convenient for coordinating time with the rest of the world. The world sets back their clocks all the time.

System clock and monotonic clock

This is why most computer systems offer two clocks.

The system clock is a wall clock, it’s coordinated with the rest of the world and can be used to tell time.

The monotonic clock is a monotonic clock that is reset when the system restarts, it’s only guarantee is that it ticks forward (at a consistent pace). Clock readings are not coordinated, but can be used for measuring time while a program is running.

If you need to measure time across system restarts, you will need to fall back to using the system clock.

This is why the monotonic clock reading is unexported in Go's time.Time type. It doesn't have any meaning outside of the "current system boot" context.

Every system has a system clock, and most systems have a monotonic clock.

Reading clocks in Go: time.Now()

In Go the time.Time is used for both telling time and measuring time.

To achieve this, the time.Time type can store two elements: a wall time element and a monotonic element.

The wall time element is always present and works using the time.Location mechanism discussed earlier.

Before version 1.9 the time package only used the wall time, this lead to the Cloudflare leap second bug at the very end of 2016.

After that the monotonic clock was introduced in the Go time package.

Calling time.Now() will create a new time.Time value and set:

  • The wall time element to the current system clock reading.
  • The monotonic element to the current monotonic clock reading (if the system has one).

Let’s take a look at a very unsafe example that shows these elements in use.

main.go
package main

import (
	"fmt"
	"time"
	"unsafe"
)

func main() {
	before := time.Now()

	// subtract 60 seconds from the monotonic element of
	// before. It now contains mismatched wall time element
	// and monotonic element.
	unsafeAddMonotonic(&before, -60*time.Second)

	// print it.
	fmt.Println(before.Format(time.RFC3339))

	// sleep for 3 seconds.
	time.Sleep(3.0 * time.Second)

	after := time.Now()

	// print after
	fmt.Println(after.Format(time.RFC3339))

	// print the duration between before and after.
	fmt.Printf("took %.1fs\n", after.Sub(before).Seconds())
}

// unsafeAddMonotonic that adds the provided value to the
// unexported ext field of the t.
// ONLY FOR THIS EXAMPLE. Never do this in your code.
func unsafeAddMonotonic(t *time.Time, add time.Duration) {
	var sample int64
	ptrToT := unsafe.Pointer(t)
	ptrToT = unsafe.Pointer(uintptr(ptrToT) + unsafe.Sizeof(sample))
	ptrToExt := (*int64)(ptrToT)
	*ptrToExt += int64(add)
}

In this example we create a mismatch between the two elements by (unsafely!) subtracting 60s from the monotonic element of the before value.

This 60s difference isn’t reflected in the times we see when displaying before and after as RFC3339 timestamps:

2009-11-10T23:00:00Z
2009-11-10T23:00:03Z

This is because the Format function only considers the wall time elements of the before and after values. It’s a “time telling” operation.

However, it is visible in the duration we compute using after.Sub(before):

took 63.0s

This shows that the Sub method uses the monotonic elements in the time.Time values. In other words: it’s a “time measuring” operation.

Monotonic-first functions

The only way to create a time.Time value with a monotonic element is:

  • Using the time.Now function (on a system with a monotonic clock).
  • Deriving a new time.Time value by calling t.Add(u) on a t value with a monotonic element.

The monotonic element is used by a limited set of methods:

  • t.After(u)
  • t.Before(u)
  • t.Equal(u)
  • t.Compare(u)
  • t.Sub(u)

These methods all use the monotonic element when it’s present in both t and u. It falls back to using the wall time element when it’s missing in t, u or both of them.

The t.Add(d) method adds the duration d to both the wall time and the monotonic element (if it’s present in t).

Wall time functions

All other functions only use or construct time.Time values with a wall time element. Values returned by time.Date, time.Parse or time.Unix* will never contain a monotonic element for example.

Noteably, some methods actively strip out the monotonic element:

  • t.AddDate(y, m, d)
  • t.Round(d)
  • t.Truncate(d)

Serializing functions

Since the monotonic element has no value outside of the “current system boot” context, it’s also never included when serializing a time.Time value.

Calling t.Format(layout), t.Marshal* or t.GobEncode() will not include the monotonic element.

This means that serializing and unserializing a time.Time value can be lossy. You will lose the monotonic element.

Practical tips

Practically this means that there are two kinds of time.Time values:

  • Those with only a wall time element.
  • Those with both a wall time and monotonic element.

As we saw, the behavior of some methods is different depending on the kind of value they’re working with.

Test data

Usually you want test data to be consistent between test runs. To create consistent time.Time values it’s common to use time.Date or time.Parse. This will only ever create values with a wall time element.

If in production these values are created using time.Now() you will end up with values containing both a wall time and a monotonic element.

This could result in different behavior between tests and production.

Depending on what you’re doing, this might (not) be an issue, but it’s good to be aware of it.

Remove monotonic element

The recommended way to remove the monotonic element of a time.Time value is using a t.Round(0) method call.

See this code snippet for an example.

Measure execution time

To measure execution time of an operation you can calculate the difference between two time.Time values created via time.Now().

See the example below.

main.go
package main

import (
	"fmt"
	"time"
)

func main() {
	start := time.Now()
	
	// emulate an operation by sleeping for 3 seconds.
	time.Sleep(time.Second * 3)

	finish := time.Now()

	// finish - start = took
	took := finish.Sub(start)

	fmt.Printf("took %dms\n", took.Milliseconds())
}

Time not moving forward

The monotonic clock is guaranteed to not “move backwards”, but it can return the same value multiple times.

In other words: Two calls to time.Now() might return the same time.Time value.

For example:

main.go
package main

import (
	"fmt"
	"time"
)

func main() {
	t1 := time.Now()
	t2 := time.Now()

	switch t1.Compare(t2) {
	case -1:
		// should never happen
		fmt.Println("t1 is after t2")
	case 0:
		// can happen
		fmt.Println("t1 is equal to t2")
	case 1:
		// can happen
		fmt.Println("t1 is before t2")
	}
}

Assuming that only case 1 can happen will result in bugs.

Don’t assume UTC is monotonic

This article resulted in a question on Reddit that is worth repeating here:

Is UTC not monotonic? The examples given in the article are all geography based, which has its application but is often not relevant.

The UTC standard itself is monotonic. Time always moves forward.

However, applications don’t read UTC time directly.

They read UTC time from the system clock, which can potentially:

  • Get out of sync and be adjusted by a network time protocol (NTP) service.
  • Be adjusted by the OS.
  • Be adjusted by the user.

In addition, systems handle UTC leap seconds in different ways. On some systems the same second gets repeated twice, making it look like the system clock was moved back a second.

Conclusion

In this article we took a look at the monotonic clock and its role in computing and more specifically it’s use in Go.

Key takeaways:

  • A monotonic clock is a clock that can’t be “moved back”.
  • Wall clocks are not monotonic clocks.
  • Wall clocks are for telling time.
  • Monotonic clocks are for measuring time.
  • The time.Time type can contain both a wall element and a monotonic element.
  • Only time.Now() and t.Add(d) can create time.Time values with a monotonic element.
  • “Monotonic first” functions use the monotonic element when possible.
  • There are other functions that ignore or actively remove the monotonic element.

I hope this article helps you write robust, time-aware applications. Keep these concepts in mind when you build your next project.

As always, if you have any questions or comments, feel free to reach out.

Happy coding!

Get my free newsletter every second week

Used by 500+ developers to boost their Go skills.

"I'll share tips, interesting links and new content. You'll also get a brief guide to time for developers for free."

Avatar of the author
Willem Schots

Hello! I'm the Willem behind willem.dev

I created this website to help new Go developers, I hope it brings you some value! :)

You can follow me on Bluesky, Twitter/X or LinkedIn.

Thanks for reading!