Search code examples
goformattingduration

Limiting significant digits in formatted durations


I am timing some unpredictable I/O. This code

started := time.Now()
time.Sleep(123456789 * time.Nanosecond) // unpredictable process    
fmt.Printf("%v", time.Since(started))

Produces

123.456789ms

I like the automatic selection and printing of unit scale (ms, μs, ns etc) because I don't know in advance whether the timed operation takes microseconds, milliseconds or seconds to complete.

I don't like the precision - I'd prefer to report only two or three significant digits. Is there a simple way to limit the precision in the formatting directive %v or similar?


Solution

  • Foreword: I released this utility in github.com/icza/gox, see timex.Round().


    I don't think there's a simple way because when printed using the default format (e.g. %v), Duration.String() is called to produce the string representation. It returns a string value, so formatting options like number of fraction digits are not applicable anymore.

    One way to control the resulting fraction digits is to truncate or round the duration before printing it, using Duration.Truncate() or Duration.Round().

    Of course the unit to which the duration should be truncated or rounded to depends on the duration's value, but the logic is not that hard:

    var divs = []time.Duration{
        time.Duration(1), time.Duration(10), time.Duration(100), time.Duration(1000)}
    
    func round(d time.Duration, digits int) time.Duration {
        switch {
        case d > time.Second:
            d = d.Round(time.Second / divs[digits])
        case d > time.Millisecond:
            d = d.Round(time.Millisecond / divs[digits])
        case d > time.Microsecond:
            d = d.Round(time.Microsecond / divs[digits])
        }
        return d
    }
    

    Let's test it with different durations:

    ds := []time.Duration{
        time.Hour + time.Second + 123*time.Millisecond, // 1h0m1.123s
        time.Hour + time.Second + time.Microsecond,     // 1h0m1.000001s
        123456789 * time.Nanosecond,                    // 123.456789ms
        123456 * time.Nanosecond,                       // 123.456µs
        123 * time.Nanosecond,                          // 123ns
    }
    
    for _, d := range ds {
        fmt.Printf("%-15v", d)
        for digits := 0; digits <= 3; digits++ {
            fmt.Printf("%-15v", round(d, digits))
    
        }
        fmt.Println()
    }
    

    Output will be (try it on the Go Playground):

    duration       0 digits       1 digit        2 digits       3 digits
    -----------------------------------------------------------------------
    1h0m1.123s     1h0m1s         1h0m1.1s       1h0m1.12s      1h0m1.123s     
    1h0m1.000001s  1h0m1s         1h0m1s         1h0m1s         1h0m1s         
    123.456789ms   123ms          123.5ms        123.46ms       123.457ms      
    123.456µs      123µs          123.5µs        123.46µs       123.456µs      
    123ns          123ns          123ns          123ns          123ns