September 6, 2019

what time is it?

Often, I look up current time in another timezone just by websearch. There’s a better way; here’s one quick approach using Go and the Unix zoneinfo database.

I might be able to do better, but here’s what I’ve got for now:

package main

import (
	"fmt"
	"log"
	"os"
	"path/filepath"
	"sort"
	"strings"
	"time"
)

const zoneinfo = "/usr/share/zoneinfo/"

// Layout provides a readable format for setting and printing time information
const Layout = "Jan 2, 2006 at " + time.Kitchen

// TimeZone represents the region and subregion of a timezone,
// and provides a reference to the Unix database
type TimeZone struct {
	UnixZoneInfo string
	Region       string
	Subregion    string

	location *time.Location
}

// BySubregion is a container for TimeZone pointers, implementing the Sort interface
type BySubregion []*TimeZone

func (tz BySubregion) Len() int           { return len(tz) }
func (tz BySubregion) Swap(a, b int)      { tz[a], tz[b] = tz[b], tz[a] }
func (tz BySubregion) Less(a, b int) bool { return tz[a].Subregion > tz[b].Subregion }

// Find returns a pointer to a TimeZone matching the given subregion string
func (tz BySubregion) Find(subregion string) *TimeZone {
	if sort.
	bestMatch := func(i int) bool { return tz[i].Subregion <= subregion }
	idx := sort.Search(tz.Len(), bestMatch)
	return tz[idx]
}

// Location finds the Unix timezone information and returns it as a pointer to *time.Location
func (tz TimeZone) Location() *time.Location {
	location, _ := time.LoadLocation(tz.Region + "/" + tz.Subregion)

	return location
}

// SetValue creates a time.Time in the timezone for the given value
func (tz TimeZone) SetValue(value string) (time.Time, error) {
	return time.ParseInLocation(Layout, value, tz.Location())
}

// Adjust creates a time.Time in the timezone for the given time
func (tz TimeZone) Adjust(t time.Time) time.Time {
	return t.In(tz.Location())
}

// PrintAdjustment first adjusts the given time for the timezone, then formats it according to Layout
func (tz TimeZone) PrintAdjustment(t time.Time) string {
	return tz.Adjust(t).Format(Layout)
}

// LoadTimezone reads given timezone region and subregion information from the Unix database
func LoadTimezone(timezone string) *TimeZone {
	parts := strings.Split(timezone, "/")

	return &TimeZone{
		UnixZoneInfo: timezone,
		Region:       parts[len(parts)-2],
		Subregion:    parts[len(parts)-1],
	}
}

// LoadAllTimeZones reads all the timezone information from the Unix database
func LoadAllTimeZones(timezones ...string) BySubregion {
	var subregions BySubregion
	for _, timezone := range timezones {
		subregions = append(subregions, LoadTimezone(timezone))
	}

	sort.Sort(subregions)

	return subregions
}

func main() {
	kitchenTime := os.Args[1]

	files, err := filepath.Glob(zoneinfo + "**/**")
	if err != nil {
		log.Fatal(err)
	}

	timezones := LoadAllTimeZones(files...)

	fmt.Printf("\nLocation     | Time\n")
	fmt.Printf("-------------|-------\n")

	local := timezones.Find(os.Args[2])

	t, _ := local.SetValue(kitchenTime)

	fmt.Printf("%-12s | %6s\n", local.Subregion, local.PrintAdjustment(t))

	for _, location := range os.Args[3:] {
		elsewhere := timezones.Find(location)

		fmt.Printf("%-12s | %6s\n", elsewhere.Subregion, elsewhere.PrintAdjustment(t))
	}
	fmt.Printf("\n")
}

The code loads the Unix timeinfo database into memory then relies on command line arguments to build a text table comparing different regional time values. Here’s an example running the above code

jared$ go run main.go "Sep 6, 2019 at 10:00PM" Tokyo New_York Phoenix Madrid

Location     | Time
-------------|-------
Tokyo        | Sep 6, 2019 at 10:00PM
New_York     | Sep 6, 2019 at 9:00AM
Phoenix      | Sep 6, 2019 at 6:00AM
Madrid       | Sep 6, 2019 at 3:00PM

And that’s it! The code above is free – do what you’d like with it.

Content by © Jared Davis 2019
Theme by © Emir Ribic 2017

Powered by Hugo & Kiss.