Skip to content

Commit d1261d1

Browse files
committed
Add zone aliases
1 parent 43f23fb commit d1261d1

6 files changed

Lines changed: 249 additions & 21 deletions

File tree

README.md

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,16 @@ list of [tz data][tzdata] zone names:
2020
<img align="center" src="./docs/demo.gif" />
2121
</p>
2222

23+
# Configuration
24+
25+
## Zone Alias
26+
27+
tz is configured only through `TZ_LIST`, and that limits us to the tz
28+
database names, but you can alias these names using a special value: the
29+
tz name followed by `;` and your alias:
30+
31+
`TZ_LIST="Europe/Paris;EMEA office,US/Central;US office"`
32+
2333
# Building
2434

2535
Clone this repository and run:

config.go

Lines changed: 41 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -25,46 +25,68 @@ import (
2525

2626
// Config stores app configuration
2727
type Config struct {
28-
Zones []Zone
28+
Zones []*Zone
2929
}
3030

31-
// LoadConfig from os.UserConfigDir
31+
// LoadConfig from environment
3232
func LoadConfig() (*Config, error) {
3333
conf := Config{
3434
Zones: DefaultZones,
3535
}
3636

37-
zoneEnv := os.Getenv("TZ_LIST")
38-
if zoneEnv == "" {
37+
tzList := os.Getenv("TZ_LIST")
38+
if tzList == "" {
3939
return &conf, nil
4040
}
41-
zoneNames := strings.Split(zoneEnv, ",")
42-
if len(zoneNames) == 0 {
41+
tzConfigs := strings.Split(tzList, ",")
42+
if len(tzConfigs) == 0 {
4343
return &conf, nil
4444
}
45-
zones := make([]Zone, len(zoneNames)+1)
45+
zones := make([]*Zone, len(tzConfigs)+1)
4646

47+
// Setup with Local time zone
4748
now := time.Now()
4849
localZoneName, offset := now.Zone()
49-
50-
zones[0] = Zone{
50+
zones[0] = &Zone{
5151
Name: fmt.Sprintf("(%s) Local", localZoneName),
5252
DbName: localZoneName,
5353
Offset: offset / 3600,
5454
}
55-
for i, name := range zoneNames {
56-
loc, err := time.LoadLocation(name)
55+
56+
// Add zones from TZ_LIST
57+
for i, zoneConf := range tzConfigs {
58+
zone, err := SetupZone(now, zoneConf)
5759
if err != nil {
58-
return nil, fmt.Errorf("looking up zone %s: %w", name, err)
59-
}
60-
then := now.In(loc)
61-
shortName, offset := then.Zone()
62-
zones[i+1] = Zone{
63-
DbName: loc.String(),
64-
Name: fmt.Sprintf("(%s) %s", shortName, loc),
65-
Offset: offset / 3600,
60+
return nil, err
6661
}
62+
zones[i+1] = zone
6763
}
6864
conf.Zones = zones
65+
6966
return &conf, nil
7067
}
68+
69+
// SetupZone from current time and a zoneConf string
70+
func SetupZone(now time.Time, zoneConf string) (*Zone, error) {
71+
names := strings.Split(zoneConf, ";")
72+
dbName := names[0]
73+
var name string
74+
if len(names) == 2 {
75+
name = names[1]
76+
}
77+
78+
loc, err := time.LoadLocation(dbName)
79+
if err != nil {
80+
return nil, fmt.Errorf("looking up zone %s: %w", dbName, err)
81+
}
82+
if name == "" {
83+
name = loc.String()
84+
}
85+
then := now.In(loc)
86+
shortName, offset := then.Zone()
87+
return &Zone{
88+
DbName: loc.String(),
89+
Name: fmt.Sprintf("(%s) %s", shortName, name),
90+
Offset: offset / 3600,
91+
}, nil
92+
}

config_test.go

Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,82 @@
1+
/**
2+
* This file is part of tz.
3+
*
4+
* tz is free software: you can redistribute it and/or modify it under
5+
* the terms of the GNU General Public License as published by the Free
6+
* Software Foundation, either version 3 of the License, or (at your
7+
* option) any later version.
8+
*
9+
* tz is distributed in the hope that it will be useful, but WITHOUT
10+
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
11+
* or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public
12+
* License for more details.
13+
*
14+
* You should have received a copy of the GNU General Public License
15+
* along with tz. If not, see <https://www.gnu.org/licenses/>.
16+
**/
17+
package main
18+
19+
import (
20+
"testing"
21+
"time"
22+
)
23+
24+
func TestSetupZone(t *testing.T) {
25+
now := time.Now()
26+
27+
tests := []struct {
28+
zoneName string
29+
ok bool
30+
}{
31+
{
32+
zoneName: "Europe/Paris",
33+
ok: true,
34+
},
35+
{
36+
zoneName: "America/London",
37+
ok: false,
38+
},
39+
}
40+
for _, test := range tests {
41+
_, err := SetupZone(now, test.zoneName)
42+
if test.ok != (err == nil) {
43+
t.Errorf("Expected %v, but got: %v", test.ok, err)
44+
}
45+
}
46+
}
47+
48+
func TestSetupZoneWithCustomNames(t *testing.T) {
49+
now := time.Now()
50+
51+
tests := []struct {
52+
zoneName string
53+
shortName string
54+
ok bool
55+
}{
56+
{
57+
zoneName: "Europe/Paris;bonjour",
58+
shortName: "(CET) bonjour",
59+
ok: true,
60+
},
61+
{
62+
zoneName: "America/Mexico_City;hola",
63+
shortName: "(CST) hola",
64+
ok: true,
65+
},
66+
{
67+
zoneName: "America/Invalid",
68+
shortName: "",
69+
ok: false,
70+
},
71+
}
72+
for _, test := range tests {
73+
z, err := SetupZone(now, test.zoneName)
74+
if test.ok != (err == nil) {
75+
t.Errorf("Expected %v, but got: %v", test.ok, err)
76+
}
77+
if z != nil && test.shortName != z.Name {
78+
t.Errorf("Expected %v, but got: %v", test.shortName, z.Name)
79+
}
80+
81+
}
82+
}

main.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@ import (
2929
var term = termenv.ColorProfile()
3030

3131
type model struct {
32-
zones []Zone
32+
zones []*Zone
3333
hour int
3434
}
3535

main_test.go

Lines changed: 114 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,114 @@
1+
/**
2+
* This file is part of tz.
3+
*
4+
* tz is free software: you can redistribute it and/or modify it under
5+
* the terms of the GNU General Public License as published by the Free
6+
* Software Foundation, either version 3 of the License, or (at your
7+
* option) any later version.
8+
*
9+
* tz is distributed in the hope that it will be useful, but WITHOUT
10+
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
11+
* or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public
12+
* License for more details.
13+
*
14+
* You should have received a copy of the GNU General Public License
15+
* along with tz. If not, see <https://www.gnu.org/licenses/>.
16+
**/
17+
package main
18+
19+
import (
20+
"testing"
21+
22+
tea "github.com/charmbracelet/bubbletea"
23+
)
24+
25+
func TestUpdateIncHour(t *testing.T) {
26+
// "l" key -> go right
27+
msg := tea.KeyMsg{
28+
Type: tea.KeyRunes,
29+
Runes: []rune{'l'},
30+
Alt: false,
31+
}
32+
33+
tests := []struct {
34+
startHour int
35+
nextHour int
36+
}{
37+
{startHour: 0, nextHour: 1},
38+
{startHour: 1, nextHour: 2},
39+
// ...
40+
{startHour: 23, nextHour: 0},
41+
}
42+
43+
for _, test := range tests {
44+
m := model{
45+
zones: DefaultZones,
46+
hour: test.startHour,
47+
}
48+
nextState, cmd := m.Update(msg)
49+
if cmd != nil {
50+
t.Errorf("Expected nil Cmd, but got %v", cmd)
51+
return
52+
}
53+
h := nextState.(model).hour
54+
if h != test.nextHour {
55+
t.Errorf("Expected %d, but got %d", test.nextHour, h)
56+
}
57+
}
58+
}
59+
60+
func TestUpdateDecHour(t *testing.T) {
61+
// "h" key -> go right
62+
msg := tea.KeyMsg{
63+
Type: tea.KeyRunes,
64+
Runes: []rune{'h'},
65+
Alt: false,
66+
}
67+
68+
tests := []struct {
69+
startHour int
70+
nextHour int
71+
}{
72+
{startHour: 23, nextHour: 22},
73+
{startHour: 22, nextHour: 21},
74+
// ...
75+
{startHour: 0, nextHour: 23},
76+
}
77+
78+
for _, test := range tests {
79+
m := model{
80+
zones: DefaultZones,
81+
hour: test.startHour,
82+
}
83+
nextState, cmd := m.Update(msg)
84+
if cmd != nil {
85+
t.Errorf("Expected nil Cmd, but got %v", cmd)
86+
return
87+
}
88+
h := nextState.(model).hour
89+
if h != test.nextHour {
90+
t.Errorf("Expected %d, but got %d", test.nextHour, h)
91+
}
92+
}
93+
}
94+
95+
func TestUpdateQuitMsg(t *testing.T) {
96+
// "q" key -> quit
97+
msg := tea.KeyMsg{
98+
Type: tea.KeyRunes,
99+
Runes: []rune{'q'},
100+
Alt: false,
101+
}
102+
103+
m := model{
104+
zones: DefaultZones,
105+
hour: 10,
106+
}
107+
_, cmd := m.Update(msg)
108+
if cmd == nil {
109+
t.Errorf("Expected tea.Quit Cmd, but got %v", cmd)
110+
return
111+
}
112+
// tea.Quit is a function, we can't really test with == here, and
113+
// calling it is getting into internal territory.
114+
}

zone.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ package main
1919
import "time"
2020

2121
var name, offset = time.Now().Zone()
22-
var DefaultZones = []Zone{
22+
var DefaultZones = []*Zone{
2323
{
2424
Name: "Local",
2525
DbName: name,

0 commit comments

Comments
 (0)