Duration in Go
Apr 17, 2016 · 3 minute read · Commentsgolang
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))
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))
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)))
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)))
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)))
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.