Duration in Go

We, lazy developers, like when we don’t have to type a lot and things just happen. Naturally, there’s a limit to how much should happen and finding this limit is often the hardest thing. Especially because it keeps moving.

Ruby is very friendly in this way and takes care of many things for us, implicit type conversion among them. Go on the other hand requires more guidance from the programmer. Generally, it is a good thing, because it leads to more explicit and predictable code; however, the cost is verbosity. It is by no means as verbose as Java for example, but we often switch between Ruby and Go and the difference is noticeable.

A situation when this comes up often is when dealing with time. Adding or subtracting from time in Ruby is simple:

puts Time.now + 5.minutes

Because Go doesn’t have overloaded operators, we need to call method time.Add which results in more keystrokes and a little bit more visual noise:

fmt.Println(time.Now().Add(time.Minute * 5))

Playground

but the result is the same. Let’s see what happens, when the time difference is supplied in a variable:

d := 5
fmt.Println(time.Now().Add(time.Minute * d))

Playground

Huh, an error about type mismatch. The reason for that is Go’s type inference. While in the first example the compiler can see, that we need time.Duration, it sets the type of the constant correctly. In the second case, we define and initialize a variable first and then try to use it. In that case, Go defaults to int and there’s no automatic conversion between between int and time.Duration.

OK, that’s easy to fix. Let’s convert the variable to time.Duration ourselves:

d := 5
fmt.Println(time.Now().Add(time.Minute * time.Duration(d)))

Playground

Great! It prints the current time plus five minutes once again.

But what happens if the time to add is something like 2.5 minutes?

d := 2.5
fmt.Println(time.Now().Add(time.Minute * time.Duration(d)))

Playground

The printed time is just 2 minutes in the future, not 2.5 minutes! This exact problem happened recently in our tests. Half of the test cases worked just fine, the others were missing few seconds. What is going on here?

We have a float64 variable being converted into time.Duration so that the duration can be added to time.Time.Go is happy to convert float64 to time.Duration but it goes through int64 because time.Duration is just an alias for int64. While conversion from float64 to int64 is predictable (the decimal part gets truncated), it’s easy to forget that time.Duration is just an alias for int64 when thinking about time; especially when we’re spoiled by a language which takes care of such things.

The solution here is to use the smallest time unit which makes sense for our use case:

d := 2 * 60 + 30
fmt.Println(time.Now().Add(time.Second * time.Duration(d)))

Playground

and to remember to think about the implementation of types when converting between them. Luckily, Go documentation is excellent and makes it very easy to check it, the only problem is taking the time to read it and fully realize what it says.

We're looking for developers to help us save energy

If you're interested in what we do and you would like to help us save energy, drop us a line at jobs@enectiva.cz.

comments powered by Disqus