BadPirate
BadPirate

Reputation: 26187

Get a list of valid time zones in Go

I'd like to write a method that will populate a Go Language array with the common timezones that are accepted by the time.Format() call, for use in an HTML template (Form select to allow them to read and choose their timezone). Is there a common way to do this?

Upvotes: 24

Views: 38739

Answers (4)

clubcleaver
clubcleaver

Reputation: 11

Refined the answer provided by @Marsel,

Checks for Valid Time Zones, Automates OS lookup, and has no recursion (for readability) "Matter of preference".

Checked on "Ubuntu Server" and "Mint"

Hope it helps

package main

import (
    "fmt"
    "log"
    "os"
    "runtime"
    "strings"
    "time"
)

var zoneDirs = map[string]string{
    "android":   "/system/usr/share/zoneinfo/",
    "darwin":    "/usr/share/zoneinfo/",
    "dragonfly": "/usr/share/zoneinfo/",
    "freebsd":   "/usr/share/zoneinfo/",
    "linux":     "/usr/share/zoneinfo/",
    "netbsd":    "/usr/share/zoneinfo/",
    "openbsd":   "/usr/share/zoneinfo/",
    "solaris":   "/usr/share/lib/zoneinfo/",
}

var timeZones []string

func main() {
    // Reads the Directory corresponding to the OS
    dirFile, _ := os.ReadDir(zoneDirs[runtime.GOOS])
    for _, i := range dirFile {
        // Checks if starts with Capital Letter
        if i.Name() == (strings.ToUpper(i.Name()[:1]) + i.Name()[1:]) {
            if i.IsDir() {
                // Recursive read if directory
                subFiles, err := os.ReadDir(zoneDirs[runtime.GOOS] + i.Name())
                if err != nil {
                    log.Fatal(err)
                }
                for _, s := range subFiles {
                    // Appends the path to timeZones var
                    timeZones = append(timeZones, i.Name()+"/"+s.Name())
                }
            }
            // Appends the path to timeZones var
            timeZones = append(timeZones, i.Name())
        }
    }
    // Loop over timezones and Check Validity, Delete entry if invalid.
    // Range function doesnt work with changing length.
    for i := 0; i < len(timeZones); i++ {
        _, err := time.LoadLocation(timeZones[i])
        if err != nil {
            // newSlice = timeZones[:n]  timeZones[n+1:]
            timeZones = append(timeZones[:i], timeZones[i+1:]...)
            continue
        }
    }
    // Now we Can range timeZones for printing
    for _, i := range timeZones {
        fmt.Println(i)
    }
}

Produces Output:

Africa/Abidjan
Africa/Accra
Africa/Addis_Ababa
Africa/Algiers
Africa/Asmara
Africa/Asmera
Africa/Bamako
Africa/Bangui
Africa/Banjul
Africa/Bissau
Africa/Blantyre
Africa/Brazzaville
...

Playground Link: https://go.dev/play/p/DoisnTppfnp

Upvotes: 0

Marsel Novy
Marsel Novy

Reputation: 1817

To get a list of time zones, you can use something like:

package main

import (
    "fmt"
    "os"
    "strings"
)

var zoneDirs = []string{
    // Update path according to your OS
    "/usr/share/zoneinfo/",
    "/usr/share/lib/zoneinfo/",
    "/usr/lib/locale/TZ/",
}

var zoneDir string

func main() {
    for _, zoneDir = range zoneDirs {
        ReadFile("")
    }
}

func ReadFile(path string) {
    files, _ := os.ReadDir(zoneDir + path)
    for _, f := range files {
        if f.Name() != strings.ToUpper(f.Name()[:1]) + f.Name()[1:] {
            continue
        }
        if f.IsDir() {
            ReadFile(path + "/" + f.Name())
        } else {
            fmt.Println((path + "/" + f.Name())[1:])
        }
    }
}

output:

Africa/Abidjan
Africa/Accra
Africa/Addis_Ababa
Africa/Algiers
Africa/Asmara
Africa/Asmera
Africa/Bamako
Africa/Bangui
...

Upvotes: 37

C Southwell
C Southwell

Reputation: 71

Here is an example: https://play.golang.org/p/KFGQiW5A1P-

package main

import (
    "fmt"
    "io/ioutil"
    "strings"
    "unicode"
)

func main() {
    fmt.Println(GetOsTimeZones())
}

func GetOsTimeZones() []string {
    var zones []string
    var zoneDirs = []string{
        // Update path according to your OS
        "/usr/share/zoneinfo/",
        "/usr/share/lib/zoneinfo/",
        "/usr/lib/locale/TZ/",
    }

    for _, zd := range zoneDirs {
        zones = walkTzDir(zd, zones)

        for idx, zone := range zones {
            zones[idx] = strings.ReplaceAll(zone, zd+"/", "")
        }
    }

    return zones
}

func walkTzDir(path string, zones []string) []string {
    fileInfos, err := ioutil.ReadDir(path)
    if err != nil {
        return zones
    }

    isAlpha := func(s string) bool {
        for _, r := range s {
            if !unicode.IsLetter(r) {
                return false
            }
        }
        return true
    }

    for _, info := range fileInfos {
        if info.Name() != strings.ToUpper(info.Name()[:1])+info.Name()[1:] {
            continue
        }

        if !isAlpha(info.Name()[:1]) {
            continue
        }

        newPath := path + "/" + info.Name()

        if info.IsDir() {
            zones = walkTzDir(newPath, zones)
        } else {
            zones = append(zones, newPath)
        }
    }

    return zones
}

Upvotes: 5

jmaloney
jmaloney

Reputation: 12300

Go's time pkg uses a timezone database.

You can load a timezone location like this:

loc, err := time.LoadLocation("America/Chicago")
if err != nil {
    // handle error
}

t := time.Now().In(loc)

The Format function is not related to setting the time zone, this function takes a fixed reference time that allows you to format the date how you would like. Take a look at the time pkg docs.

For instance:

fmt.Println(t.Format("MST")) // outputs CST

Here is a running example

Upvotes: 0

Related Questions