How to get monotonic part of time.Time in Go

Issue

am currently working with timestamps and I would like to store in a variable the monotonic clock reading section.

Let’s say that I have this: 2022-03-31 10:20:26.370463 +0200 CEST m=+0.007725255 then I would like to get m=+0.007725255 in a different var, or at least the +0.007725255 section. What is the idiomatic way in Go to get it?

Solution

A Go time.Time stores 2 timestamps:

  • Wall clock
  • Monotonic duration since process start (optional, via time.Now)

m=+0.007725255 represents the monotonic duration since the start of the process (when present in a time.Time).

Go calculates this offset by recording time.startNano timestamp during initialisation (not public). time.Now uses startNano to calculate the monotonic duration stored in time.Time. There is no simple public API to directly retrieve this value since it should never be needed.

In practice, you should simply subtract 2 timestamps generated via time.Now in your current process and the result will be the monotonic duration. If you need to know the duration since process startup you should record a startup timestamp during initalisation.

Example:

package main

import (
    "errors"
    "fmt"
    "math"
    "strconv"
    "strings"
    "time"
)

func main() {
    t0 := time.Now()
    fmt.Println("...example event...")
    time.Sleep(time.Millisecond)
    t1 := time.Now()
    fmt.Println("Event start:", t0)
    fmt.Println("Event completed:", t1)

    fmt.Println("=== Not recommended ===")
    offsetT0, _ := monoOffset(t0)
    fmt.Println("Parsed start offset:", offsetT0)
    startNano, _ := calculateStartNano()
    fmt.Println("Calculate start offset via startNano: ", t0.Sub(startNano))

    fmt.Println("=== Recommended ===")
    fmt.Println("Example event duration:", t1.Sub(t0))
    fmt.Println("Time since startup", time.Since(t0))
}

// You should never need anything below here (code smell).

func monoOffset(t time.Time) (time.Duration, error) {
    // Recommend strings.Cut on Go1.18+.
    parts := strings.Split(t.String(), " m=")
    if len(parts) != 2 {
        return 0, errors.New("missing monotonic offset")
    }

    seconds, err := strconv.ParseFloat(parts[1], 64)
    if err != nil {
        return 0, err
    }

    nanos := math.Round(seconds * 1e9)
    return time.Duration(nanos), nil
}

func calculateStartNano() (time.Time, error) {
    now := time.Now()
    offset, err := monoOffset(now)
    if err != nil {
        return time.Time{}, err
    }
    return now.Add(-offset), nil
}

Outputs:

...example event...
Event start: 2022-04-16 16:54:25.088159496 +1000 AEST m=+0.000079273
Event completed: 2022-04-16 16:54:25.089438935 +1000 AEST m=+0.001358685
=== Not recommended ===
Parsed start offset : 79.273µs
Calculate start offset via startNano:  79.273µs
=== Recommended ===
Example event duration: 1.279412ms
Time since startup 2.016789ms

Answered By – mpx

Answer Checked By – David Goodson (GoLangFix Volunteer)

Leave a Reply

Your email address will not be published.