DEV Community

Jacob Kim
Jacob Kim

Posted on • Edited on

Working with Time Data in Go

On many occasions, you will have to write code that has to deal with time. You could write a clock program or measure the time difference between two points in your code. Either way, it's important to know how to manipulate time data in Go. It's pretty simple, and knowing some nifty functions can help you save time. Working with time data in Go requires you to import the time package from the Go standard library. This package has a lot of methods and types that you can use, but I took the most used ones and described them here in this post. If you would like to see more info on the subject, check out the docs page for the time package.

Tell the time

This is probably the most commonly used method in the package. How do we tell the current time? Like this:

t := time.Now()
fmt.Println(t)
Enter fullscreen mode Exit fullscreen mode
2023-02-06 23:01:48.9983151 -0500 EST m=+0.000030901
Enter fullscreen mode Exit fullscreen mode

That's the current time. It's pretty cumbersome, so I will break this down into components.

  • 2023-02-06: Year, month, day.

  • 23:01:48.9983151: Hour, minutes, seconds

  • -0500: Time difference from GMT.

  • EST: The current time zone you are in.

  • m=+0.000030901: Monotonic clock reading.

We will get to monotonic clock later in this post. We can move on for now.

Is there a better way to format this?

You bet.

t := time.Now()
fmt.Println(t.Year())
fmt.Println(t.Month())
fmt.Println(t.Day())
fmt.Println(t.Date())
fmt.Println(t.Hour())
fmt.Println(t.Minute())
fmt.Println(t.Second())
Enter fullscreen mode Exit fullscreen mode
2023
February
6
2023 February 6
23
26
6
Enter fullscreen mode Exit fullscreen mode

Here's how you can extract each element of the time. Pretty straightforward, right?

How can we print this in a prettier format?

fmt.Printf("%d %d %d\n", t.Year(), t.Month(), t.Day())
Enter fullscreen mode Exit fullscreen mode
2023 2 6
Enter fullscreen mode Exit fullscreen mode

You can see how we can use the fmt.Printf function to format the time to our liking.

But what if we want to show months by their names, like February instead of 2? What if we want to show the time on a 12-hour scale instead of 24? You can see how it gets complex very quickly.

There is a better way to format time

Fortunately, we have the time.Format function to help us. Let's see how it works.

fmt.Println(t.Format("Mon Jan 2 15:04:05 2006 MST"))
fmt.Println(t.Format("Mon Jan 2 15:04:05"))
fmt.Println(t.Format("2006/01/02"))
fmt.Println(t.Format("3:04PM"))
fmt.Println(t.Format("15:04PM"))
Enter fullscreen mode Exit fullscreen mode
Mon Feb 6 23:47:43 2023 EST
Mon Feb 6 23:47:43
2023/02/06
11:47PM
23:47PM
Enter fullscreen mode Exit fullscreen mode

I think this is the part that tripped me up the most when I first started learning about this topic. And it's also the part where you can see how cheeky the language designers were.

We can see that time.Format accepts a string that denotes the format we want our time to be in. This is the weird part, because Go is very, extremely particular about the format string's format.

Mon Jan 2 15:04:05 2006 MST
Enter fullscreen mode Exit fullscreen mode

The format string has to be a variation of this string, or else the code prints out weird times. Interestingly enough, if you exclude the Mon, each element of the format string represents an integer. Jan is 1, 2 is 2, 15 is 3, etc. Nerdy people, these developers.

1 2 3 4 5 6 -7
Enter fullscreen mode Exit fullscreen mode

From the code above, though, you can see how we can format our time the way we want it to. And we don't have to write extra functions to convert hours into 12 or 24-hour scales, or to map each integer to a month.

You could also use a predefined format as well, like so:

fmt.Println(time.UnixDate)
fmt.Println(time.RFC3339)
Enter fullscreen mode Exit fullscreen mode
Mon Jan _2 15:04:05 MST 2006
2006-01-02T15:04:05Z07:00
Enter fullscreen mode Exit fullscreen mode

Check the docs for time package for more formats.

What about different time zones?

Time zones are automatically detected, as shown above. However, there may be cases where you need to show time in different time zones.

nt := time.Now()
lt := time.Now().Local()
ut := time.Now().UTC()
fmt.Println(nt)
fmt.Println(lt)
fmt.Println(ut)
Enter fullscreen mode Exit fullscreen mode
2023-02-07 00:09:08.7052349 -0500 EST m=+0.000121601
2023-02-07 00:09:08.705235 -0500 EST
2023-02-07 05:09:08.705235 +0000 UTC
Enter fullscreen mode Exit fullscreen mode

Local() gets the local time zone, which is the same as what time.Now() would automatically detect. Calling UTC() will convert the time zone to UTC.

But what if we need something more robust?

l, _ := time.LoadLocation("UTC")
fmt.Printf("%s\n", nt.In(l))

l, _ = time.LoadLocation("Europe/London")
fmt.Printf("%s\n", nt.In(l))

l, _ = time.LoadLocation("America/Los_Angeles")
fmt.Printf("%s\n", nt.In(l))

l, _ = time.LoadLocation("Asia/Seoul")
fmt.Printf("%s\n", nt.In(l))
Enter fullscreen mode Exit fullscreen mode
2023-02-07 05:12:10.4423244 +0000 UTC
2023-02-07 05:12:10.4423244 +0000 GMT
2023-02-06 21:12:10.4423244 -0800 PST
2023-02-07 14:12:10.4423244 +0900 KST
Enter fullscreen mode Exit fullscreen mode

time.LoadLocation will load a locale of your choice. You can use this result to convert your time by passing in to yourTime.In.

You can also read time from strings

In many cases, you will have to read in a string. Often, these will be timestamps. Wouldn't it be great to use the time library's functions on these timestamp strings?

By default, the time library works on time.Time types. However, we can parse these timestamp strings by using time.Parse.

timestamp := "2023-02-06 23:49:01"
ts, err := time.Parse("2006-01-02 15:04:05", timestamp)
if err != nil {
    fmt.Println(err)
}
fmt.Println(ts)
Enter fullscreen mode Exit fullscreen mode
2023-02-06 23:49:01 +0000 UTC
Enter fullscreen mode Exit fullscreen mode

You can also format ts using the Format method described above.

Wait, what is a monotonic clock?

Let's come back to this topic. It sounds a lot scarier than it actually is.

Your computer has a clock that measures time. Chances are that this time is rather volatile. Ever had the experience where your clock gets off by a couple of hours after traveling to another country? Ever had to set your clock again to match it with your watch? This clock is called the wall clock, and it is prone to change.

While the wall clock is good at telling the time, it's not good for measuring time. For instance, look at this piece of code.

t1 := time.Now()
fmt.Println(t1)
time.Sleep(time.Second())
t2 := time.Now()
fmt.Println(time.Now())
fmt.Println(t2.Sub(t1))
Enter fullscreen mode Exit fullscreen mode
2023-02-06 23:23:32.2520709 -0500 EST m=+0.000031501
2023-02-06 23:23:33.2525152 -0500 EST m=+1.000475801
1.0004442s
Enter fullscreen mode Exit fullscreen mode

The above code measures the time elapsed between t1 and t2. This seems obvious because we waited one second before declaring t2. But what if we somehow managed to switch our OS time settings in that span? If we add 4 hours to our system's clock, does that mean the time elapsed between t1 and t2 would be 4 hours and 1 second? That's ridiculous!

This is why Go uses a monotonic clock to measure time instead. You can see from the m values in the printed times that the time difference is about one second. Monotonic clocks allow for accurate measurement of time.

Conclusion

We are so familiar with the concept of time that we tend to take our understanding of it for granted. However, time tends to be one of the more frustrating things to represent in a computer. Fortunately, Go developers have abstracted away most of the raw conversions from us, so that we can use the simple-to-understand functions of the time package. This post covers a lot of the necessary functions, but if you need the nitty-gritty details, you can always refer to the official docs.

Thank you for reading! You can also view this post on Medium.

Top comments (0)