Skip to content

Commit 019af3d

Browse files
committed
feat(parse): add timezone override
1 parent 8c7622a commit 019af3d

4 files changed

Lines changed: 69 additions & 33 deletions

File tree

internal/cmd/now.go

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -82,11 +82,11 @@ func runNow(cmd *cobra.Command) error {
8282
}
8383

8484
switch mode {
85-
case outputPretty:
85+
case outputModePretty:
8686
return out.writePretty(cmd.OutOrStdout())
87-
case outputSimple:
87+
case outputModeSimple:
8888
return out.writeSimple(cmd.OutOrStdout())
89-
case outputJson:
89+
case outputModeJson:
9090
return out.writeJson(cmd.OutOrStdout())
9191
default:
9292
return fmt.Errorf("unexpected output format: %s", mode)

internal/cmd/parse.go

Lines changed: 44 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ import (
1313
"github.com/charmbracelet/lipgloss/v2"
1414
"github.com/charmbracelet/lipgloss/v2/table"
1515
"github.com/spf13/cobra"
16+
"github.com/spf13/viper"
1617

1718
"github.com/DanStough/epok/internal/styles"
1819
"github.com/DanStough/epok/parse"
@@ -35,7 +36,11 @@ other formats. It can handle timestamps in various precisions and formats.`,
3536
epok parse 1751074598
3637
3738
# Read from stdin
38-
pbpaste | epoch parse`,
39+
pbpaste | epoch parse
40+
41+
# Override displayed timezones
42+
epok parse 1751074598 -z Big\ Ben=Europe/London,Tokyo\ SkyTree=Asia/Tokyo,Empire\ State=America/New_York
43+
`,
3944

4045
Args: cobra.MaximumNArgs(1),
4146

@@ -48,6 +53,14 @@ pbpaste | epoch parse`,
4853
SilenceUsage: true,
4954
}
5055

56+
defaultLocales := map[string]string{
57+
"Local": "Local",
58+
"UTC": "UTC",
59+
}
60+
61+
parseCmd.Flags().StringToStringP("timezone", "z", defaultLocales,
62+
"override the map of locales:timezones. "+
63+
"Use 'Local' for system time.")
5164
return parseCmd
5265
}
5366

@@ -63,30 +76,39 @@ func runParse(cmd *cobra.Command, args []string) error {
6376
input = args[0]
6477
}
6578

66-
input = strings.TrimSpace(input)
67-
timestamp, err := parse.String(input)
79+
mode, err := getOutput()
6880
if err != nil {
69-
return fmt.Errorf("could not parse input: %w", err)
81+
return err
7082
}
7183

72-
// TODO: consider having this load from a config
73-
locales := map[string]*time.Location{
74-
"Local": time.Local,
75-
"UTC": time.UTC,
84+
timezones := viper.GetStringMapString("timezone")
85+
if len(timezones) == 0 {
86+
return errors.New("must specify at least one locale timezone")
7687
}
7788

78-
out := newParseOutput(input, timestamp, locales)
79-
mode, err := getOutput()
89+
locales := make(map[string]*time.Location, len(timezones))
90+
for name, timezone := range timezones {
91+
loc, err := time.LoadLocation(timezone)
92+
if err != nil {
93+
return fmt.Errorf("invalid timezone %s for locale %s: %w", timezone, name, err)
94+
}
95+
locales[name] = loc
96+
}
97+
98+
input = strings.TrimSpace(input)
99+
timestamp, err := parse.String(input)
80100
if err != nil {
81-
return err
101+
return fmt.Errorf("could not parse input: %w", err)
82102
}
83103

104+
out := newParseOutput(input, timestamp, locales)
105+
84106
switch mode {
85-
case outputPretty:
107+
case outputModePretty:
86108
return out.writePretty(cmd.OutOrStdout())
87-
case outputSimple:
109+
case outputModeSimple:
88110
return out.writeSimple(cmd.OutOrStdout())
89-
case outputJson:
111+
case outputModeJson:
90112
return out.writeJson(cmd.OutOrStdout())
91113
default:
92114
return fmt.Errorf("unexpected output format: %s", mode)
@@ -208,10 +230,16 @@ func (o *parseOutput) writePretty(w io.Writer) error {
208230
style = sheet.Table.OddRow
209231
}
210232

211-
// TODO (dans): these need to change if we make the timezones configurable
233+
localeWidth := 8
234+
for _, locale := range o.Locales {
235+
if len(locale.Name) > localeWidth {
236+
localeWidth = len(locale.Name)
237+
}
238+
}
239+
212240
switch col {
213241
case 0:
214-
style = style.Width(8)
242+
style = style.Width(localeWidth + 2) // include padding
215243
case 1:
216244
style = style.Width(32)
217245
case 2:

internal/cmd/parse_test.go

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,8 @@ func Test_Parse(t *testing.T) {
3636
args: []string{
3737
"parse",
3838
"1751770507\n",
39+
"-z", // We need to force an exact timezone for "local" to work across machines with different settings.
40+
"Local=America/New_York,UTC=UTC",
3941
},
4042
expectedOutput: []string{
4143
afterOutput,
@@ -45,6 +47,8 @@ func Test_Parse(t *testing.T) {
4547
name: "happy path - valid unix timestamp stdin",
4648
args: []string{
4749
"parse",
50+
"-z", // We need to force an exact timezone for "local" to work across machines with different settings.
51+
"Local=America/New_York,UTC=UTC",
4852
},
4953
in: "1751770507\n",
5054
expectedOutput: []string{
@@ -55,6 +59,8 @@ func Test_Parse(t *testing.T) {
5559
name: "happy path - timestamp is before 'now'",
5660
args: []string{
5761
"parse",
62+
"-z", // We need to force an exact timezone for "local" to work across machines with different settings.
63+
"Local=America/New_York,UTC=UTC",
5864
},
5965
in: "946080000\n",
6066
expectedOutput: []string{
@@ -66,6 +72,8 @@ func Test_Parse(t *testing.T) {
6672
args: []string{
6773
"parse",
6874
"-ojson",
75+
"-z", // We need to force an exact timezone for "local" to work across machines with different settings.
76+
"Local=America/New_York,UTC=UTC",
6977
},
7078
in: "1751770507\n",
7179
expectedOutput: []string{

internal/cmd/root.go

Lines changed: 14 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -139,35 +139,35 @@ func bindFlags(cmd *cobra.Command) {
139139
}
140140
}
141141

142-
type output string
142+
type outputMode string
143143

144144
const (
145-
outputJson output = "json"
146-
outputPretty output = "pretty"
147-
outputSimple output = "simple"
145+
outputModeJson outputMode = "json"
146+
outputModePretty outputMode = "pretty"
147+
outputModeSimple outputMode = "simple"
148148
)
149149

150-
func getOutput() (output, error) {
150+
func getOutput() (outputMode, error) {
151151
str := viper.GetString("output")
152-
output := output(str)
152+
output := outputMode(str)
153153

154154
switch output {
155155
// support shorthands
156-
case outputPretty, "p":
157-
output = outputPretty
158-
case outputSimple, "s":
159-
output = outputSimple
160-
case outputJson, "j":
161-
output = outputJson
156+
case outputModePretty, "p":
157+
output = outputModePretty
158+
case outputModeSimple, "s":
159+
output = outputModeSimple
160+
case outputModeJson, "j":
161+
output = outputModeJson
162162
default:
163163
return "", fmt.Errorf("invalid output flag: %s", output)
164164
}
165165

166166
isInteractive := term.IsTerminal(int(os.Stdout.Fd()))
167167

168168
// Downgrade styling for non-interactive terminals
169-
if !isInteractive && output == outputPretty {
170-
output = outputSimple
169+
if !isInteractive && output == outputModePretty {
170+
output = outputModeSimple
171171
}
172172
return output, nil
173173
}

0 commit comments

Comments
 (0)