Time in Go: A primer
Basic introduction to Go’s time package
April 26, 2016
Time is of the essence.
This initially started out as merely a few snippets of code regarding the formatting of dates, but then I decided that it is probably of more use if I publish it.
I extended the snippets slightly, and added some examples of commonly used
functionality within the time
library.
The reason why I have these snippets is because, well, Go’s date/time formatting is a bit odd. While I agree it increases readability, it takes some getting used to, after many years of formatting dates in pretty much the same way across different languages.
A short list of subjects that will be (partly) covered:
Formatting
Let’s start with formatting. Formatting is based on the following reference time:
Mon Jan 2 15:04:05 -0700 MST 2006
What does this mean? This means if you would want a specific date formatted like:
26-04-2016
, you would use the reference time like this: 02-01-2006
.
It’s pretty straight forward right? You rewrite the reference time in the format you desire, and Go will spit out the correct date for you.
package main
import (
"fmt"
"time"
)
func main() {
t := time.Now()
fmt.Println(t.Format("02-01-2006"))
fmt.Println(t.Format("02-01-2006 15:04:05"))
}
// Output:
// 26-04-2016
// 26-04-2016 08:03:23
These are two formats I commonly use myself. Aside from using your own formats,
the time
package provides you with a wide choice of standardized formats.
const (
ANSIC = "Mon Jan _2 15:04:05 2006"
UnixDate = "Mon Jan _2 15:04:05 MST 2006"
RubyDate = "Mon Jan 02 15:04:05 -0700 2006"
RFC822 = "02 Jan 06 15:04 MST"
RFC822Z = "02 Jan 06 15:04 -0700" // RFC822 with numeric zone
RFC850 = "Monday, 02-Jan-06 15:04:05 MST"
RFC1123 = "Mon, 02 Jan 2006 15:04:05 MST"
RFC1123Z = "Mon, 02 Jan 2006 15:04:05 -0700" // RFC1123 with numeric zone
RFC3339 = "2006-01-02T15:04:05Z07:00"
RFC3339Nano = "2006-01-02T15:04:05.999999999Z07:00"
Kitchen = "3:04PM"
// Handy time stamps.
Stamp = "Jan _2 15:04:05"
StampMilli = "Jan _2 15:04:05.000"
StampMicro = "Jan _2 15:04:05.000000"
StampNano = "Jan _2 15:04:05.000000000"
)
Which can be used like this:
package main
import (
"fmt"
"time"
)
func main() {
t := time.Now()
fmt.Println(t.Format(time.RFC822Z))
}
// Output: 26 Apr 16 08:10 +0200
As you can see my time offset is displayed in the last example, which brings us to locations.
Locations
As you can see in the previous example my time offset is displayed, this is
because by default (when using time.Now()
) Go uses your local machine time.
So how do you handle users with different locations? You would want to display the
correct times depending on your user’s location.
Generally, you’ll first want to bring back every time
to a specific location.
Go provides the UTC()
function out of the box, this is what I’ll use.
Officially UTC is not a timezone but a standard. Hence the name Location
in
the time
package.
package main
import (
"fmt"
"time"
)
func main() {
t := time.Now().UTC()
fmt.Println(t.Format(time.RFC822Z))
}
// Output: 26 Apr 16 06:10 +0000
This is something you might store somewhere in a database. Now when you want to display this time to someone who lives in Panama, you might do the following:
package main
import (
"fmt"
"time"
)
func retrieveNormalizedTimeFromDataSource() time.Time {
// Normally you'd fetch the time from a specific datasource here.
return time.Date(2016, time.April, 26, 6, 10, 0, 0, time.UTC)
}
func main() {
// Make sure to handle the error ;), omitted for brevity
userLocation, _ := time.LoadLocation("America/Panama")
t := retrieveNormalizedTimeFromDataSource()
t = t.In(userLocation)
fmt.Println(t.Format(time.RFC822Z))
}
// Output: 26 Apr 16 01:10 -0500
time.Date
is another way to construct a time.Time
struct, more about that
here.
You can also define your own zones, be wary though, you’ll have to keep track
of things like Daylight Saving Time yourself. The LoadLocation
way of handling
this problem is preferred.
This is how you would define your own zone:
package main
import (
"fmt"
"time"
)
func retrieveNormalizedTimeFromDataSource() time.Time {
// Normally you'd fetch the time from a specific datasource here.
return time.Date(2016, time.April, 26, 6, 10, 0, 0, time.UTC)
}
func main() {
userLocation := time.FixedZone("example", 3600*8) // UTC +0800
t := retrieveNormalizedTimeFromDataSource()
t = t.In(userLocation)
fmt.Println(t.Format(time.RFC822Z))
}
// Output: 26 Apr 16 14:10 +0800
The offset
, the second parameter, is amount of seconds east of UTC. Which means
you should use a negative amount if you want to go west of UTC.
Parsing
Parsing is pretty much the inverse of formatting, and uses the same reference
time. Let’s say we want to parse the date 26-04-2016 08:10:23
.
package main
import (
"fmt"
"time"
)
func main() {
t, _ := time.Parse("02-01-2006 15:04:05", "26-04-2016 08:10:23")
fmt.Println(t.Format(time.RFC822Z))
}
// Output: 26 Apr 16 08:10 +0000
As you can see, my used input 26-04-2016 08:10:23
is parsed based on the
reference time 02-01-2006 15:04:05
.
My machine’s time offset is UTC+0200
, however the date has been parsed as UTC+0000
.
If you’re not careful this may cause unexpected results.
There are two ways to fix this, first of all, you could provide an offset, like this:
package main
import (
"fmt"
"time"
)
func main() {
t, _ := time.Parse("02-01-2006 15:04:05 -0700", "26-04-2016 08:10:23 +0200")
fmt.Println(t.Format(time.RFC822Z))
}
// Output: 26 Apr 16 08:10 +0200
But ParseInLocation
might be easier, depending on your scenario. It works the
same as Parse
, except you specify a Location
as second parameter.
package main
import (
"fmt"
"time"
)
func main() {
t, _ := time.ParseInLocation("02-01-2006 15:04:05", "26-04-2016 08:10:23", time.Local)
fmt.Println(t.Format(time.RFC822Z))
}
// Output: 26 Apr 16 08:10 +0200
That’s pretty much it for parsing :)!
Comparison & calculations
Using time for comparisons and calculations is the last subject I would like to cover. As an example I will show you how to compare two dates:
package main
import (
"fmt"
"time"
)
func main() {
// Tiny disclaimer: All I could find about the first C release was the
// year, so May 15 is fiction ;) Just like the time for both of them :)
cReleaseDate := time.Date(1972, time.May, 15, 12, 0, 0, 0, time.UTC)
goReleaseDate := time.Date(2009, time.November, 10, 12, 0, 0, 0, time.UTC)
if goReleaseDate.After(cReleaseDate) {
fmt.Println("Go was released years after C!")
} else {
fmt.Println("C was inspired by Go. Go was first.")
}
}
// Output: Go was released years after C!
A small reminder on using Before
and After
. You would use it just as you
would read it in English: if goReleaseDate came After cReleaseDate.
Other than that, it’s possible to calculate the difference between two dates:
package main
import (
"fmt"
"time"
)
func main() {
blinkOne := time.Date(2016, time.April, 26, 8, 10, 23, int(time.Millisecond*500), time.UTC)
blinkTwo := time.Date(2016, time.April, 26, 8, 10, 27, int(time.Millisecond*250), time.UTC)
timeBetweenBlinks := blinkTwo.Sub(blinkOne)
fmt.Printf("%s between two blinks", timeBetweenBlinks)
}
// Output: 3.75s between two blinks
Depending on your usecase there is a shorthand way to do this as well.
package main
import (
"fmt"
"time"
)
func main() {
start := time.Now()
for i := 0; i < 1000; i++ {
}
duration := time.Since(start)
fmt.Printf("Operation took: %s", duration)
}
// Output: Operation took: 1.808µs
Conclusion
I hope this was a decent introduction to the time
package if you’re starting
out with Go. Otherwise I hope it serves as good reference material when you’re
dealing with the time
package :).
Hope you enjoyed the article!