Skip to content

Commit

Permalink
Add zone aliases
Browse files Browse the repository at this point in the history
  • Loading branch information
oz committed Feb 13, 2021
1 parent 43f23fb commit d1261d1
Show file tree
Hide file tree
Showing 6 changed files with 249 additions and 21 deletions.
10 changes: 10 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,16 @@ list of [tz data][tzdata] zone names:
<img align="center" src="./docs/demo.gif" />
</p>

# Configuration

## Zone Alias

tz is configured only through `TZ_LIST`, and that limits us to the tz
database names, but you can alias these names using a special value: the
tz name followed by `;` and your alias:

`TZ_LIST="Europe/Paris;EMEA office,US/Central;US office"`

# Building

Clone this repository and run:
Expand Down
60 changes: 41 additions & 19 deletions config.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,46 +25,68 @@ import (

// Config stores app configuration
type Config struct {
Zones []Zone
Zones []*Zone
}

// LoadConfig from os.UserConfigDir
// LoadConfig from environment
func LoadConfig() (*Config, error) {
conf := Config{
Zones: DefaultZones,
}

zoneEnv := os.Getenv("TZ_LIST")
if zoneEnv == "" {
tzList := os.Getenv("TZ_LIST")
if tzList == "" {
return &conf, nil
}
zoneNames := strings.Split(zoneEnv, ",")
if len(zoneNames) == 0 {
tzConfigs := strings.Split(tzList, ",")
if len(tzConfigs) == 0 {
return &conf, nil
}
zones := make([]Zone, len(zoneNames)+1)
zones := make([]*Zone, len(tzConfigs)+1)

// Setup with Local time zone
now := time.Now()
localZoneName, offset := now.Zone()

zones[0] = Zone{
zones[0] = &Zone{
Name: fmt.Sprintf("(%s) Local", localZoneName),
DbName: localZoneName,
Offset: offset / 3600,
}
for i, name := range zoneNames {
loc, err := time.LoadLocation(name)

// Add zones from TZ_LIST
for i, zoneConf := range tzConfigs {
zone, err := SetupZone(now, zoneConf)
if err != nil {
return nil, fmt.Errorf("looking up zone %s: %w", name, err)
}
then := now.In(loc)
shortName, offset := then.Zone()
zones[i+1] = Zone{
DbName: loc.String(),
Name: fmt.Sprintf("(%s) %s", shortName, loc),
Offset: offset / 3600,
return nil, err
}
zones[i+1] = zone
}
conf.Zones = zones

return &conf, nil
}

// SetupZone from current time and a zoneConf string
func SetupZone(now time.Time, zoneConf string) (*Zone, error) {
names := strings.Split(zoneConf, ";")
dbName := names[0]
var name string
if len(names) == 2 {
name = names[1]
}

loc, err := time.LoadLocation(dbName)
if err != nil {
return nil, fmt.Errorf("looking up zone %s: %w", dbName, err)
}
if name == "" {
name = loc.String()
}
then := now.In(loc)
shortName, offset := then.Zone()
return &Zone{
DbName: loc.String(),
Name: fmt.Sprintf("(%s) %s", shortName, name),
Offset: offset / 3600,
}, nil
}
82 changes: 82 additions & 0 deletions config_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
/**
* This file is part of tz.
*
* tz is free software: you can redistribute it and/or modify it under
* the terms of the GNU General Public License as published by the Free
* Software Foundation, either version 3 of the License, or (at your
* option) any later version.
*
* tz is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
* or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public
* License for more details.
*
* You should have received a copy of the GNU General Public License
* along with tz. If not, see <https://www.gnu.org/licenses/>.
**/
package main

import (
"testing"
"time"
)

func TestSetupZone(t *testing.T) {
now := time.Now()

tests := []struct {
zoneName string
ok bool
}{
{
zoneName: "Europe/Paris",
ok: true,
},
{
zoneName: "America/London",
ok: false,
},
}
for _, test := range tests {
_, err := SetupZone(now, test.zoneName)
if test.ok != (err == nil) {
t.Errorf("Expected %v, but got: %v", test.ok, err)
}
}
}

func TestSetupZoneWithCustomNames(t *testing.T) {
now := time.Now()

tests := []struct {
zoneName string
shortName string
ok bool
}{
{
zoneName: "Europe/Paris;bonjour",
shortName: "(CET) bonjour",
ok: true,
},
{
zoneName: "America/Mexico_City;hola",
shortName: "(CST) hola",
ok: true,
},
{
zoneName: "America/Invalid",
shortName: "",
ok: false,
},
}
for _, test := range tests {
z, err := SetupZone(now, test.zoneName)
if test.ok != (err == nil) {
t.Errorf("Expected %v, but got: %v", test.ok, err)
}
if z != nil && test.shortName != z.Name {
t.Errorf("Expected %v, but got: %v", test.shortName, z.Name)
}

}
}
2 changes: 1 addition & 1 deletion main.go
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ import (
var term = termenv.ColorProfile()

type model struct {
zones []Zone
zones []*Zone
hour int
}

Expand Down
114 changes: 114 additions & 0 deletions main_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,114 @@
/**
* This file is part of tz.
*
* tz is free software: you can redistribute it and/or modify it under
* the terms of the GNU General Public License as published by the Free
* Software Foundation, either version 3 of the License, or (at your
* option) any later version.
*
* tz is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
* or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public
* License for more details.
*
* You should have received a copy of the GNU General Public License
* along with tz. If not, see <https://www.gnu.org/licenses/>.
**/
package main

import (
"testing"

tea "github.com/charmbracelet/bubbletea"
)

func TestUpdateIncHour(t *testing.T) {
// "l" key -> go right
msg := tea.KeyMsg{
Type: tea.KeyRunes,
Runes: []rune{'l'},
Alt: false,
}

tests := []struct {
startHour int
nextHour int
}{
{startHour: 0, nextHour: 1},
{startHour: 1, nextHour: 2},
// ...
{startHour: 23, nextHour: 0},
}

for _, test := range tests {
m := model{
zones: DefaultZones,
hour: test.startHour,
}
nextState, cmd := m.Update(msg)
if cmd != nil {
t.Errorf("Expected nil Cmd, but got %v", cmd)
return
}
h := nextState.(model).hour
if h != test.nextHour {
t.Errorf("Expected %d, but got %d", test.nextHour, h)
}
}
}

func TestUpdateDecHour(t *testing.T) {
// "h" key -> go right
msg := tea.KeyMsg{
Type: tea.KeyRunes,
Runes: []rune{'h'},
Alt: false,
}

tests := []struct {
startHour int
nextHour int
}{
{startHour: 23, nextHour: 22},
{startHour: 22, nextHour: 21},
// ...
{startHour: 0, nextHour: 23},
}

for _, test := range tests {
m := model{
zones: DefaultZones,
hour: test.startHour,
}
nextState, cmd := m.Update(msg)
if cmd != nil {
t.Errorf("Expected nil Cmd, but got %v", cmd)
return
}
h := nextState.(model).hour
if h != test.nextHour {
t.Errorf("Expected %d, but got %d", test.nextHour, h)
}
}
}

func TestUpdateQuitMsg(t *testing.T) {
// "q" key -> quit
msg := tea.KeyMsg{
Type: tea.KeyRunes,
Runes: []rune{'q'},
Alt: false,
}

m := model{
zones: DefaultZones,
hour: 10,
}
_, cmd := m.Update(msg)
if cmd == nil {
t.Errorf("Expected tea.Quit Cmd, but got %v", cmd)
return
}
// tea.Quit is a function, we can't really test with == here, and
// calling it is getting into internal territory.
}
2 changes: 1 addition & 1 deletion zone.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ package main
import "time"

var name, offset = time.Now().Zone()
var DefaultZones = []Zone{
var DefaultZones = []*Zone{
{
Name: "Local",
DbName: name,
Expand Down

0 comments on commit d1261d1

Please sign in to comment.