Go's time.Duration Type Unravelled

Jun 11, 2013


I have been struggling with using the Time package that comes in the Go standard library. My struggles have come from two pieces of functionality. First, trying to capture the number of milliseconds between two different time periods. Second, comparing that duration in milliseconds against a pre-defined time span. It sounds like a no brainier but like I said, I have been struggling.

In the Time package there is a custom type called Duration and a set of helper constants:

type Duration int64

const (
  Nanosecond Duration = 1
  Microsecond = 1000 * Nanosecond
  Millisecond = 1000 * Microsecond
  Second = 1000 * Millisecond
  Minute = 60 * Second
  Hour = 60 * Minute
)

I looked at this maybe 1000 times but it didn’t mean anything to me. I just want to compare two time periods, get the duration back, compare the duration and do something if the right amount of time has elapsed. I could not for the life of me understand how this structure was going to help me unravel this mystery.

I wrote this Test function but it didn’t work.

func Test() {
  var waitFiveHundredMillisections int64 = 500

  startingTime := time.Now().UTC()
  time.Sleep(10 * time.Millisecond)
  endingTime := time.Now().UTC()

  var duration time.Duration = endingTime.Sub(startingTime)
  var durationAsInt64 = int64(duration)

  if durationAsInt64 >= waitFiveHundredMillisections {
    fmt.Printf("Time Elapsed : Wait[%d] Duration[%d]\n",
       waitFiveHundredMillisections, durationAsInt64)
  } else {
    fmt.Printf("Time DID NOT Elapsed : Wait[%d] Duration[%d]\n",
       waitFiveHundredMillisections, durationAsInt64)
  }
}

When I ran the Test function and look at the output the code thinks that 500 milliseconds have elapsed.

Time Elapsed : Wait[500] Duration[10724798]

So what went wrong? I looked at the Duration type and constants again.

type Duration int64

const (
  Nanosecond Duration = 1
  Microsecond = 1000 * Nanosecond
  Millisecond = 1000 * Microsecond
  Second = 1000 * Millisecond
  Minute = 60 * Second
  Hour = 60 * Minute
)

The basic unit of time for the Duration type is a Nanosecond. Ah, that is why when casting a Duration type that contains 10 milliseconds to an int64 I get 10,000,000.

So direct casting is not going to work. I need a different strategy and a better understanding of how to use and convert the Duration type.

I know it would be best to use the Duration type natively. This will minimize problems when using the type. Based on the constant values, I can create a Duration type variable in the following ways:

func Test() {
  var duration_Milliseconds time.Duration = 500 * time.Millisecond
  var duration_Seconds time.Duration = (1250 * 10) * time.Millisecond
  var duration_Minute time.Duration = 2 * time.Minute

  fmt.Printf("Milli [%v]\nSeconds [%v]\nMinute [%v]\n",
    duration_Milliseconds,
    duration_Seconds,
    duration_Minute)
}

I created 3 variables of type Duration. Then by using the time constants, I am able to create the correct duration time span values. When I use the standard library Printf function and the %v operator I get the following output:

Milli [500ms]
Seconds [12.5s]
Minute [2m0s]

This is very cool. The Printf function knows how to natively display a Duration type. Based on the value in each Duration variable, the Printf function prints the value in the proper time period. I am also getting the values I expected.

The Duration type does have member functions that will convert the value of the Duration variable to a native Go type, either in int64 or float64 respectively:

func Test() {
  var duration_Seconds time.Duration = (1250 * 10) * time.Millisecond
  var duration_Minute time.Duration = 2 * time.Minute

  var float64_Seconds float64 = duration_Seconds.Seconds()
  var float64_Minutes float64 = duration_Minute.Minutes()

  fmt.Printf("Seconds [%.3f]\nMinutes [%.2f]\n",
    float64_Seconds,
    float64_Minutes)
}

I noticed pretty quickly that there is no Milliseconds function. There is a function for every other time unit but Milliseconds. When I display the Seconds and Minutes natively I get the following output as expected:

Seconds [12.500]
Minutes [2.00]

However I need the Milliseconds, So why is the Milliseconds function missing?

The designers of Go didn’t want to lock me into a single native type for the Milliseconds. They wanted me to have options.

The following code converts the value of the Duration variable to Milliseconds as both an int64 and float64:

func Test() {
  var duration_Milliseconds time.Duration = 500 * time.Millisecond

  var castToInt64 int64 = duration_Milliseconds.Nanoseconds() / 1e6
  var castToFloat64 float64 = duration_Milliseconds.Seconds() * 1e3

  fmt.Printf("Duration [%v]\ncastToInt64 [%d]\ncastToFloat64 [%.0f]\n",
    duration_Milliseconds,
    castToInt64,
    castToFloat64)
}

If I divide the Nanoseconds by 1e6 I get the Milliseconds as a int64. If I multiply the Seconds by 1e3 I get the Milliseconds as a float64.

Here is the output:

Duration [500ms]
castToInt64 [500]
castToFloat64 [500]

If you are wondering what 1e6 or 1e3 represents you are not alone:

1e3 = 103 = One Thousand = 1 plus 3 0’s

1e6 = 106 = One Million  = 1 plus 6 0’s

Now that I understand what a Duration type is and how best to use and manipulate it, I have my final test example using Milliseconds:

func Test() {
  var waitFiveHundredMillisections time.Duration = 500 * time.Millisecond

  startingTime := time.Now().UTC()
  time.Sleep(600 * time.Millisecond)
  endingTime := time.Now().UTC()

  var duration time.Duration = endingTime.Sub(startingTime)

  if duration >= waitFiveHundredMillisections {
    fmt.Printf("Wait %v\nNative [%v]\nMilliseconds [%d]\nSeconds [%.3f]\n",
       waitFiveHundredMillisections,
       duration,
       duration.Nanoseconds()/1e6,
       duration.Seconds())
  }
}

I get the following output:

Wait 500ms
Native [601.091066ms]
Milliseconds [601]
Seconds [0.601]

Instead of comparing native types to determine if the time has elapsed I am comparing two Duration types. This is much cleaner. When displaying the values I am using the Duration type custom formatting and converting the value of the Duration variable to Milliseconds as both an int64 and float64.

It took a while but eventually using the Duration type started to make sense. As always I hope this helps someone else navigate using the Duration type in their Go applications.


Ultimate Go Programming LiveLessons

Ultimate Go Programming LiveLessons provides an intensive, comprehensive, and idiomatic view of the Go programming language. This course focuses on both the specification and implementation of the language, including topics ranging from language syntax, design, and guidelines to concurrency, testing, and profiling. This class is perfect for anyone who wants a jump-start in learning Go or wants a more thorough understanding of the language and its internals.

Learn more

Go Training

We have taught Go to thousands of developers all around the world since 2014. There is no other company that has been doing it longer and our material has proven to help jump start developers 6 to 12 months ahead of their knowledge of Go. We know what knowledge developers need in order to be productive and efficient when writing software in Go.

Our Go, Web and Data Science classes are perfect for both experienced and beginning engineers. We start every class from the beginning and get very detailed about the internals, mechanics, specification, guidelines, best practices and design philosophies. We cover a lot about "if performance matters" with a focus on mechanical sympathy, data oriented design, decoupling and writing production software.

Learn More

To learn about Corporate training events, options and special pricing please contact:

William Kennedy
ArdanLabs (www.ardanlabs.com)
bill@ardanlabs.com