Working with Time Data in Go
Tags: go, tutorial, beginners, programming
Published: 2023-02-07 05:20:14

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:

```go
t := time.Now()
fmt.Println(t)
```

```
2023-02-06 23:01:48.9983151 -0500 EST m=+0.000030901
```

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.

```go
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())
```

```
2023
February
6
2023 February 6
23
26
6
```

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

How can we print this in a prettier format?

```go
fmt.Printf("%d %d %d\n", t.Year(), t.Month(), t.Day())
```

```
2023 2 6
```

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.

```go
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"))
```

```
Mon Feb 6 23:47:43 2023 EST
Mon Feb 6 23:47:43
2023/02/06
11:47PM
23:47PM
```

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
```

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
```

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:

```go
fmt.Println(time.UnixDate)
fmt.Println(time.RFC3339)
```

```
Mon Jan _2 15:04:05 MST 2006
2006-01-02T15:04:05Z07:00
```

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.

```go
nt := time.Now()
lt := time.Now().Local()
ut := time.Now().UTC()
fmt.Println(nt)
fmt.Println(lt)
fmt.Println(ut)
```

```
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
```

`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?

```go
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))
```

```
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
```

`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`.

```go
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)
```

```
2023-02-06 23:49:01 +0000 UTC
```

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.

```go
t1 := time.Now()
fmt.Println(t1)
time.Sleep(time.Second())
t2 := time.Now()
fmt.Println(time.Now())
fmt.Println(t2.Sub(t1))
```

```
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
```

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](https://medium.com/@jpoly1219/time-in-go-b5da2f3a4bea).