Skip to content

Commit eb517a8

Browse files
joeybloggsjoeybloggs
authored andcommitted
initial commit.
1 parent c586734 commit eb517a8

File tree

5 files changed

+3590
-2
lines changed

5 files changed

+3590
-2
lines changed

README.md

Lines changed: 41 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,41 @@
1-
# tz
2-
Timezone Country and Zone data generated from timezonedb.com
1+
Package tz
2+
==========
3+
4+
![Project status](https://img.shields.io/badge/version-1.0.0-green.svg)
5+
[![GoDoc](https://godoc.org/github.com/go-playground/tz?status.svg)](https://godoc.org/github.com/go-playground/tz)
6+
![License](https://img.shields.io/dub/l/vibe-d.svg)
7+
8+
Package tz contains Timezone Country and Zone data generated from timezonedb.com
9+
10+
11+
This library is nothing special, it contains alphabetically sorted Country and Zone info that can be used within your project.
12+
13+
#### Motivation
14+
I got tired of rewriting this, or using a db, to store this information just so I can add to an HTML dropdown for selection. So in short here it is for use and hope it saves someone else some time as well.
15+
16+
#### NOTES
17+
This is intended to be used along side Go's own time logic eg. `time.LoadLocation`
18+
19+
##### Example using with
20+
```go
21+
22+
countries := tz.GetCountries()
23+
24+
// display to user for selection
25+
// once selected load the zones
26+
27+
country := tz.GetCountry(countryCode)
28+
29+
// display zone options to user with country.Zones
30+
// once selected by user use it in your application
31+
32+
zone := country zone, selected by user, gathered from posted data, or retrieved from db ( stored on user )...
33+
34+
loc, _ := time.LoadLocation(zone)
35+
36+
// now that you have location can use with Go's time package which handles timezone offsets & Daylight savings times.
37+
38+
time.ParseInLocation(...)
39+
time.Now().In(loc)
40+
41+
```

generate/generate.md

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
Generate TZ Data
2+
---------------
3+
4+
####Instructions:
5+
6+
- run `go run main.go` from within the generate directory...that's it.
7+
8+
Generate will not work on all systems, but that's ok, it's just used to created the tz_data.go file at the root of the project. If anybody wants to help make it Cross OS compatible I'm open to pull requests.

generate/main.go

Lines changed: 297 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,297 @@
1+
package main
2+
3+
import (
4+
"encoding/csv"
5+
"fmt"
6+
"io"
7+
"log"
8+
"os"
9+
"os/exec"
10+
"sort"
11+
"text/template"
12+
"time"
13+
14+
"bitbucket.org/metricaid/rnd/timezone_info"
15+
)
16+
17+
const (
18+
workDir = "tzdb"
19+
dbFilename = "timezonedb.csv.zip"
20+
dbURL = "https://timezonedb.com/files/" + dbFilename
21+
countryFile = "country.csv"
22+
zoneFile = "zone.csv"
23+
outputFile = "../tz_data.go"
24+
)
25+
26+
type countryColumn int
27+
28+
// Country Columns
29+
const (
30+
countryCode countryColumn = iota
31+
countryName
32+
)
33+
34+
type zoneColumn int
35+
36+
// Zone Columns
37+
const (
38+
ID zoneColumn = iota
39+
code
40+
name
41+
)
42+
43+
type byCountryName []tz.Country
44+
45+
func (a byCountryName) Len() int { return len(a) }
46+
func (a byCountryName) Swap(i, j int) { a[i], a[j] = a[j], a[i] }
47+
func (a byCountryName) Less(i, j int) bool { return a[i].Name < a[j].Name }
48+
49+
type byZoneName []tz.Zone
50+
51+
func (a byZoneName) Len() int { return len(a) }
52+
func (a byZoneName) Swap(i, j int) { a[i], a[j] = a[j], a[i] }
53+
func (a byZoneName) Less(i, j int) bool { return a[i].Name < a[j].Name }
54+
55+
func main() {
56+
57+
tmpl, err := template.New("gen").Parse(output)
58+
if err != nil {
59+
log.Fatal("ERROR parsing template:", err)
60+
}
61+
62+
cwd, err := os.Getwd()
63+
if err != nil {
64+
log.Fatal("ERROR determining current working DIR:", err)
65+
}
66+
67+
cmd := exec.Command("mkdir", "-p", workDir)
68+
err = cmd.Run()
69+
if err != nil {
70+
log.Fatal("ERROR creating working DIR:", err)
71+
}
72+
73+
err = os.Chdir(workDir)
74+
if err != nil {
75+
log.Fatal("ERROR switching to working DIR:", err)
76+
}
77+
78+
cmd = exec.Command("curl", "-O", dbURL)
79+
80+
err = cmd.Run()
81+
if err != nil {
82+
log.Fatal("ERROR downloading file:", err)
83+
}
84+
85+
cmd = exec.Command("unzip", dbFilename)
86+
87+
err = cmd.Run()
88+
if err != nil {
89+
log.Fatal("ERROR Unzipping file:", err)
90+
}
91+
92+
countries, err := process()
93+
if err != nil {
94+
log.Fatal("ERROR processing files:", err)
95+
}
96+
97+
err = os.Chdir(cwd)
98+
if err != nil {
99+
log.Fatal("ERROR switching to original working DIR:", err)
100+
}
101+
102+
f, err := os.OpenFile(outputFile, os.O_CREATE|os.O_WRONLY, 0777)
103+
if err != nil {
104+
log.Fatal("ERROR writing/creating tz data file:", err)
105+
}
106+
defer f.Close()
107+
108+
err = tmpl.Execute(f, countries)
109+
if err != nil {
110+
log.Fatal("ERROR executing template:", err)
111+
}
112+
113+
f.Close()
114+
115+
// after file written run gofmt on file
116+
cmd = exec.Command("gofmt", "-s", "-w", outputFile)
117+
if err = cmd.Run(); err != nil {
118+
log.Fatal("ERROR running gofmt:", err)
119+
}
120+
121+
err = os.RemoveAll(workDir)
122+
if err != nil {
123+
log.Fatal("ERROR removing working DIR:", err)
124+
}
125+
126+
}
127+
128+
func process() ([]tz.Country, error) {
129+
130+
var err error
131+
132+
cmap := make(map[string]int)
133+
countries := make([]tz.Country, 0, 10)
134+
135+
// process countries
136+
cf, err := os.Open(countryFile)
137+
if err != nil {
138+
return nil, err
139+
}
140+
defer cf.Close()
141+
142+
r := csv.NewReader(cf)
143+
144+
for {
145+
146+
row, err := r.Read()
147+
if err == io.EOF {
148+
break
149+
}
150+
if err != nil {
151+
log.Fatal(err)
152+
}
153+
154+
c := tz.Country{
155+
Code: row[countryCode],
156+
Name: row[countryName],
157+
}
158+
cmap[c.Code] = len(countries)
159+
160+
countries = append(countries, c)
161+
}
162+
163+
// process zones
164+
165+
zf, err := os.Open(zoneFile)
166+
if err != nil {
167+
return nil, err
168+
}
169+
defer zf.Close()
170+
171+
r = csv.NewReader(zf)
172+
173+
for {
174+
175+
row, err := r.Read()
176+
if err == io.EOF {
177+
break
178+
}
179+
if err != nil {
180+
log.Fatal(err)
181+
}
182+
183+
z := tz.Zone{
184+
CountryCode: row[code],
185+
Name: row[name],
186+
}
187+
188+
// test zone is working in Go
189+
_, err = time.LoadLocation(z.Name)
190+
if err != nil {
191+
fmt.Println("*********************ERROR:", err)
192+
continue
193+
}
194+
195+
idx, ok := cmap[z.CountryCode]
196+
if !ok {
197+
continue
198+
}
199+
200+
countries[idx].Zones = append(countries[idx].Zones, z)
201+
}
202+
203+
// sort alphabetically
204+
sort.Sort(byCountryName(countries))
205+
206+
for _, c := range countries {
207+
sort.Sort(byZoneName(c.Zones))
208+
}
209+
210+
return countries, nil
211+
}
212+
213+
var output = `package tz
214+
215+
import "sync"
216+
217+
// GENERATED FILE DO NOT MODIFY DIRECTLY
218+
219+
var (
220+
once sync.Once
221+
mapped map[string]Country
222+
countries = []Country{
223+
{{ range $c := .}}{
224+
Code: "{{ $c.Code }}",
225+
Name: "{{ $c.Name }}",
226+
Zones: []Zone{
227+
{{ range $z := $c.Zones }}{
228+
CountryCode: "{{ $z.CountryCode }}",
229+
Name: "{{ $z.Name }}",
230+
},
231+
{{ end }}
232+
},
233+
},
234+
{{ end }}
235+
}
236+
)
237+
238+
func init() {
239+
// load + index countries into map
240+
// for below functions.
241+
242+
once.Do(func() {
243+
mapped = make(map[string]Country)
244+
245+
for i := 0; i < len(countries); i++ {
246+
mapped[countries[i].Code] = countries[i]
247+
}
248+
})
249+
}
250+
251+
// GetCountries returns an array of all countries.
252+
// Most common use: for loading into a country dropdown
253+
// in HTML.
254+
func GetCountries() []Country {
255+
return countries
256+
}
257+
258+
// GetCountry returns a single Country that matches the country
259+
// code passed and whether it was found
260+
func GetCountry(code string) (c Country, found bool) {
261+
c, found = mapped[code]
262+
return
263+
}
264+
`
265+
266+
// func main() {
267+
268+
// time.Local = time.UTC
269+
270+
// loc, err := time.LoadLocation("America/Toronto")
271+
// if err != nil {
272+
// fmt.Println("ERROR:", err)
273+
// }
274+
275+
// utc := time.Now()
276+
277+
// fmt.Println(" NOW UTC:", utc)
278+
279+
// local := utc.In(loc)
280+
// fmt.Println("LOCAL TIME:", local)
281+
282+
// edt, err := time.Parse("2006-01-02", "2016-04-01")
283+
// if err != nil {
284+
// fmt.Println("ERROR:", err)
285+
// }
286+
287+
// est, err := time.Parse("2006-01-02", "2016-12-01")
288+
// if err != nil {
289+
// fmt.Println("ERROR:", err)
290+
// }
291+
292+
// fmt.Println("EDT UTC:", edt)
293+
// fmt.Println("EST UTC:", est)
294+
295+
// fmt.Println("EDT LOCAL:", edt.In(loc))
296+
// fmt.Println("EST LOCAL:", est.In(loc))
297+
// }

tz.go

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
package tz
2+
3+
// Zone contains a single Country's Zone information
4+
type Zone struct {
5+
CountryCode string
6+
Name string
7+
}
8+
9+
// Country contains a single Country's information
10+
type Country struct {
11+
Code string
12+
Name string
13+
Zones []Zone
14+
}

0 commit comments

Comments
 (0)