Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 6 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -171,7 +171,12 @@ The configuration is stored in `$XDG_CONFIG_HOME/gotz/config.json` (usually `~/.
// Indicates whether to colorize the blocks
"hours12": false,
// Indicates whether to use 12-hour format
"live": false
"live": false,
// Selects the sorting of the timezones
// (one of 'name' - lexicographically, 'offset' - TZ offset, 'none' - user defined)
"sorting": "name",
// Indicates whether to keep the local timezone on top when using sorting
"sort_local_top": true
}
```

Expand Down
34 changes: 33 additions & 1 deletion core/args.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ func ParseFlags(startConfig Config, appVersion string) (Config, time.Time, bool,
// Check for any changes
var changed bool
// Define configuration flags
var timezones, symbols, tics, stretch, inline, colorize, hours12, live string
var timezones, symbols, tics, stretch, inline, colorize, hours12, live, sorting, sortLocalTop string
flag.StringVar(
&timezones,
"timezones",
Expand Down Expand Up @@ -69,6 +69,21 @@ func ParseFlags(startConfig Config, appVersion string) (Config, time.Time, bool,
"",
"indicates whether to display time live (quit via 'q' or 'Ctrl+C') (one of: true, false)",
)
flag.StringVar(
&sorting,
"sorting",
SortingModeDefault,
"indicates how to sort the timezones (one of: "+
SortingModeNone+", "+
SortingModeOffset+", "+
SortingModeName+")",
)
flag.StringVar(
&sortLocalTop,
"sort-local-top",
"",
"indicates whether to keep the local timezone at the top (one of: true, false)",
)

// Define direct flags
var requestTime string
Expand Down Expand Up @@ -166,6 +181,23 @@ func ParseFlags(startConfig Config, appVersion string) (Config, time.Time, bool,
return startConfig, rt, changed, fmt.Errorf("invalid value for live: %s", live)
}
}
if sorting != "" {
changed = true
if !isValidSortingMode(sorting) {
return startConfig, rt, changed, fmt.Errorf("invalid sorting mode: %s", sorting)
}
startConfig.Sorting = sorting
}
if sortLocalTop != "" {
changed = true
if strings.ToLower(sortLocalTop) == "true" {
startConfig.SortLocalTop = true
} else if strings.ToLower(sortLocalTop) == "false" {
startConfig.SortLocalTop = false
} else {
return startConfig, rt, changed, fmt.Errorf("invalid value for sort-local-top: %s", sortLocalTop)
}
}

// Handle direct flags
if requestTime != "" {
Expand Down
11 changes: 0 additions & 11 deletions core/auxiliary.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,17 +10,6 @@ import (
"golang.org/x/term"
)

// maxStringLength returns the length of the longest string in the given slice.
func maxStringLength(s []string) int {
length := 0
for _, str := range s {
if len(str) > length {
length = len(str)
}
}
return length
}

// getTerminalWidth returns the width of the terminal.
func getTerminalWidth() int {
width, _, err := term.GetSize(0)
Expand Down
6 changes: 6 additions & 0 deletions core/configuration.go
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,12 @@ type Config struct {

// Indicates whether to continuously update.
Live bool `json:"live"`

// Defines the mode for sorting the timezones.
Sorting string `json:"sorting"`
// SortLocalTop indicates whether the local timezone should be kept at the
// top (independent of the sorting mode).
SortLocalTop bool `json:"sort_local_top"`
}

// Location describes a timezone the user wants to display.
Expand Down
54 changes: 38 additions & 16 deletions core/plot.go
Original file line number Diff line number Diff line change
Expand Up @@ -329,36 +329,58 @@ func PlotTime(plt Plotter, cfg Config, t time.Time) error {
}

// createTimeInfos creates the time info strings for all locations.
func createTimeInfos(cfg Config, t time.Time) (timeInfos []string, times []*time.Location, err error) {
// Init
timeInfos = make([]string, len(cfg.Timezones)+1)

// Prepare timeZones to plot
timeZones := make([]*time.Location, len(cfg.Timezones)+1)
descriptions := make([]string, len(cfg.Timezones)+1)
timeZones[0] = time.Local
descriptions[0] = "Local"
func createTimeInfos(cfg Config, t time.Time) (timeInfos []string, timeZones []*time.Location, err error) {
// Prepare timezones for plotting
locations := make([]locationContainer, len(cfg.Timezones)+1)
now := time.Now()
_, localOffset := now.In(time.Local).Zone()
locations[0] = locationContainer{
location: time.Local,
description: "Local",
offset: localOffset,
}
for i, tz := range cfg.Timezones {
// Get timezone
loc, err := time.LoadLocation(tz.TZ)
if err != nil {
return nil, nil, fmt.Errorf("error loading timezone %s: %s", tz.TZ, err)
}
_, offset := now.In(loc).Zone()
// Store timezone
timeZones[i+1] = loc
descriptions[i+1] = tz.Name
locations[i+1] = locationContainer{
location: loc,
description: tz.Name,
offset: offset,
}
}

// Sort timezones and convert
if cfg.Sorting != SortingModeNone {
sortLocations(locations, cfg.Sorting, cfg.SortLocalTop)
}
descriptionLength := maxStringLength(descriptions)

for i := range timeZones {
// Determine max description length
descriptionLength := 0
for _, location := range locations {
if len(location.description) > descriptionLength {
descriptionLength = len(location.description)
}
}

timeInfos = make([]string, len(cfg.Timezones)+1)
timeZones = make([]*time.Location, len(cfg.Timezones)+1)
for i, location := range locations {
// Prepare location and time infos
timeInfo := fmt.Sprintf("%-*s", descriptionLength, descriptions[i])
timeInfo := fmt.Sprintf("%-*s", descriptionLength, location.description)
timeInfo = fmt.Sprintf(
"%s: %s %s",
timeInfo,
formatDay(cfg.Hours12, t.In(timeZones[i])),
formatTime(cfg.Hours12, true, t.In(timeZones[i])))
formatDay(cfg.Hours12, t.In(location.location)),
formatTime(cfg.Hours12, true, t.In(location.location)),
)
// Store time info and timezone
timeInfos[i] = timeInfo
timeZones[i] = location.location
}

return timeInfos, timeZones, nil
Expand Down
59 changes: 59 additions & 0 deletions core/sorting.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
package core

import (
"sort"
"time"
)

// Define sorting modes
const (
// SortingModeNone keeps the order of the timezones as they are defined.
SortingModeNone = "none"
// SortingModeOffset sorts the timezones by their offset.
SortingModeOffset = "offset"
// SortingModeName sorts the timezones by their name.
SortingModeName = "name"
// SortingModeDefault is the default sorting mode.
SortingModeDefault = SortingModeNone
)

// isValidSortingMode checks if the given sorting mode is defined and valid.
func isValidSortingMode(mode string) bool {
switch mode {
case SortingModeNone, SortingModeOffset, SortingModeName:
return true
default:
return false
}
}

// locationContainer is a container for a location with additional information.
type locationContainer struct {
location *time.Location
description string
offset int
}

// sortLocations sorts the given locations based on the given sorting mode.
func sortLocations(locations []locationContainer, sortingMode string, localTop bool) {
sort.Slice(locations, func(i, j int) bool {
// If the local timezone should be kept at the top, check if one of the
// locations is the local timezone.
if localTop {
if locations[i].location == time.Local {
return true
} else if locations[j].location == time.Local {
return false
}
}
// Sort based on the sorting mode
switch sortingMode {
case SortingModeOffset:
return locations[i].offset < locations[j].offset
case SortingModeName:
return locations[i].description < locations[j].description
default:
return i < j
}
})
}
2 changes: 1 addition & 1 deletion go.mod
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
module github.com/merschformann/gotz

go 1.17
go 1.23.0

require (
github.com/adrg/xdg v0.4.0
Expand Down
11 changes: 11 additions & 0 deletions tests/testdata/static_sort.golden
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
now v 16:00
Local : Sat 24 Aug 1985 14:00 |
▒▒▒▒▒▒██████████████████|███████████▒▒▒▒▒▒▒▒▒▒▒▒
Berlin : Sat 24 Aug 1985 16:00 |
▒▒▒▒▒▒████████████████████████|█████▒▒▒▒▒▒▒▒▒▒▒▒
New York: Sat 24 Aug 1985 10:00 |
▒▒▒▒▒▒██████|███████████████████████▒▒▒▒▒▒▒▒▒▒▒▒
Shanghai: Sat 24 Aug 1985 22:00 |
████████████████████████▒▒▒▒▒▒▒▒▒▒▒▒| ▒▒▒▒▒▒██████
Sydney : Sun 25 Aug 1985 00:00 |
██████████████████▒▒▒▒▒▒▒▒▒▒▒▒ | ▒▒▒▒▒▒████████████
50 changes: 50 additions & 0 deletions tests/testdata/static_sort.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
{
"config_version": "1.0",
"timezones": [
{
"Name": "New York",
"TZ": "America/New_York"
},
{
"Name": "Berlin",
"TZ": "Europe/Berlin"
},
{
"Name": "Shanghai",
"TZ": "Asia/Shanghai"
},
{
"Name": "Sydney",
"TZ": "Australia/Sydney"
}
],
"style": {
"symbols": "rectangles",
"colorize": false,
"day_segments": {
"morning": 6,
"day": 8,
"evening": 18,
"night": 22
},
"coloring": {
"StaticColorMorning": "red",
"StaticColorDay": "yellow",
"StaticColorEvening": "red",
"StaticColorNight": "blue",
"StaticColorForeground": "",
"DynamicColorMorning": "red",
"DynamicColorDay": "yellow",
"DynamicColorEvening": "red",
"DynamicColorNight": "blue",
"DynamicColorForeground": "",
"DynamicColorBackground": ""
}
},
"tics": false,
"stretch": true,
"hours12": false,
"live": false,
"sorting": "name",
"sort_local_top": true
}