hjr265.me / blog /

Go Tidbit: Listing All Time Zones in Go

September 24, 2023 #100DaysToOffload #Go #Tidbit
Table of Contents
Go Programming Language and Time Zones

I wanted to list all the time zones in Go.

The standard library in Go comes with the time package. It also comes with the time/tzdata package.

The standard time package in Go is very well-thought-out. It makes date-time manipulation deceptively simple.

Yet, I could not find a way to list all the time zones.

Time Zone Database

The documentation for time.LoadLocation describes how Go uses four sources of time zone data:

  • The directory or uncompressed zip file named by the ZONEINFO environment variable

    The first source allows me to specify a time zone database. But I need to include one in the system where the Go program will run.

  • On a Unix system, the system’s standard installation location

    The second source is dependent on the operating system. The version of the database available may be different on different systems. And the system where the Go program will run may not even have one.

  • $GOROOT/lib/time/zoneinfo.zip

    The third source depends on Go. The data is already there if you have the Go compiler tools installed on your system. The database should be consistent across all installations of the same version of Go. However, the Go program cannot use this source without the Go compiler tools installed.

  • The time/tzdata package, if the Go program has imported it

    The fourth source, the time/tzdata package does not export anything. It embeds the database from the third source with the program. But, I cannot access any time zone data in the package unless I know the names.

What if I could generate the names of the time zones as a slice of strings present in the third source when compiling my Go program? And I embed the same database using the time/tzdata package.

That would be a reasonable compromise.

I can run the Go program on a system with no time zone database and rely solely on the embedded database. And I will know the names of all the time zones in the embedded database.

tzlist.go Generator

The go command has this oft-overlooked feature: go generate.

With this, I can add a go:generate directive in my Go program and generate Go files by processing the source.

I came up with this quick Go code generator:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
// Error handling omitted for brevity.

//go:build ignore

package main

import (
  "archive/zip"
  "bytes"
  _ "embed"
  "fmt"
  "io/fs"
  "os"
  "os/exec"
  "path/filepath"
)

func main() {
  fmt.Println(os.Environ())

  b, _ := exec.Command("go", "env", "GOROOT").Output()
  goroot := string(bytes.TrimSpace(b))

  zipname := filepath.Join(goroot, "lib", "time", "zoneinfo.zip")
  zipf, _ := os.Open(zipname)
  zipfi, _ := zipf.Stat()

  f, _ := os.Create(os.Getenv("GOFILE"))

  fmt.Fprintln(f, `// Code generated by "mktzlist.go"`)
  fmt.Fprintln(f, "//go:generate go run mktzlist.go")
  fmt.Fprintln(f)
  fmt.Fprintf(f, "package %s\n", os.Getenv("GOPACKAGE"))
  fmt.Fprintln(f)
  fmt.Fprintln(f, "var TimeZones = []string{")

  zr, _ := zip.NewReader(zipf, zipfi.Size())
  fs.WalkDir(zr, ".", func(path string, d fs.DirEntry, err error) error {
    if !d.IsDir() {
      fmt.Fprintf(f, "\t%q,\n", path)
    }
    return nil
  })

  fmt.Fprintln(f, "}")
}

I can then create a file named tzlist.go with the following contents:

1
2
3
//go:generate go run mktzlist.go

package tzlist

And run go generate to generate the TimeZones variable in the tzlist.go file:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
// Code generated by "mktzlist.go"
//go:generate go run mktzlist.go

package tzlist

var TimeZones = []string{
  "Africa/Abidjan",
  "Africa/Accra",
  "Africa/Addis_Ababa",
  // ...
  "W-SU",
  "WET",
  "Zulu",
}

The TimeZones variable is a slice of strings containing the names of all time zones from the zoneinfo.zip database.

Whenever Go updates the zoneinfo.zip file, I can rerun go generate to regenerate this file.

Wrap Up

In Go, I can import the time/tzdata to have a time zone database embedded in your Go program. I can achieve the same effect by building the Go program with the -tags timetzdata flag.

The Go program can use this embedded database if the time package cannot find any time zone database on the system.

But if I want a list of timezones in this embedded database, I can use the list generated ahead of compilation from the same source of truth that comes as a part of Go compiler tools: zoneinfo.zip.

Full Code

You can find the entire code for this on GitHub.


This post is 52nd of my #100DaysToOffload challenge. Want to get involved? Find out more at 100daystooffload.com.

Table of Contents

comments powered by Disqus