Skip to content

Commit

Permalink
Initial import
Browse files Browse the repository at this point in the history
  • Loading branch information
oz committed Jan 30, 2021
0 parents commit 092a39a
Show file tree
Hide file tree
Showing 8 changed files with 1,000 additions and 0 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
tz
674 changes: 674 additions & 0 deletions COPYING

Large diffs are not rendered by default.

18 changes: 18 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
# 🌐 A time zone helper

tz helps you schedule things across time zones. It is an interactive TUI
program that displays time across a few time zones of your choosing.

# Building

Clone this repository and run:

```
go build .
```

# License

The GPL3 license.

Copyright (c) 2021 Arnaud Berthomier
30 changes: 30 additions & 0 deletions config.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
/**
* 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

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

// LoadConfig from os.UserConfigDir
func LoadConfig() (*Config, error) {
cfg := Config{
Zones: DefaultZones,
}
return &cfg, nil
}
8 changes: 8 additions & 0 deletions go.mod
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
module github.com/oz/tz

go 1.15

require (
github.com/charmbracelet/bubbletea v0.12.4
github.com/muesli/termenv v0.7.4
)
34 changes: 34 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
github.com/charmbracelet/bubbletea v0.12.4 h1:2cfryCnDOe7SvQF/Dj99irXKpFlfoPgQjEDdvP1pLpk=
github.com/charmbracelet/bubbletea v0.12.4/go.mod h1:tp9tr9Dadh0PLhgiwchE5zZJXm5543JYjHG9oY+5qSg=
github.com/containerd/console v1.0.1 h1:u7SFAJyRqWcG6ogaMAx3KjSTy1e3hT9QxqX7Jco7dRc=
github.com/containerd/console v1.0.1/go.mod h1:XUsP6YE/mKtz6bxc+I8UiKKTP04qjQL4qcS3XoQ5xkw=
github.com/google/goterm v0.0.0-20190703233501-fc88cf888a3f/go.mod h1:nOFQdrUlIlx6M6ODdSpBj1NVA+VgLC6kmw60mkw34H4=
github.com/lucasb-eyer/go-colorful v1.0.3 h1:QIbQXiugsb+q10B+MI+7DI1oQLdmnep86tWFlaaUAac=
github.com/lucasb-eyer/go-colorful v1.0.3/go.mod h1:R4dSotOR9KMtayYi1e77YzuveK+i7ruzyGqttikkLy0=
github.com/mattn/go-isatty v0.0.12 h1:wuysRhFDzyxgEmMf5xjvJ2M9dZoWAXNNr5LSBS7uHXY=
github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU=
github.com/mattn/go-runewidth v0.0.9/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI=
github.com/mattn/go-runewidth v0.0.10 h1:CoZ3S2P7pvtP45xOtBw+/mDL2z0RKI576gSkzRRpdGg=
github.com/mattn/go-runewidth v0.0.10/go.mod h1:RAqKPSqVFrSLVXbA8x7dzmKdmGzieGRCM46jaSJTDAk=
github.com/muesli/reflow v0.2.1-0.20210115123740-9e1d0d53df68 h1:y1p/ycavWjGT9FnmSjdbWUlLGvcxrY0Rw3ATltrxOhk=
github.com/muesli/reflow v0.2.1-0.20210115123740-9e1d0d53df68/go.mod h1:Xk+z4oIWdQqJzsxyjgl3P22oYZnHdZ8FFTHAQQt5BMQ=
github.com/muesli/termenv v0.7.2 h1:r1raklL3uKE7rOvWgSenmEm2px+dnc33OTisZ8YR1fw=
github.com/muesli/termenv v0.7.2/go.mod h1:ct2L5N2lmix82RaY3bMWwVu/jUFc9Ule0KGDCiKYPh8=
github.com/muesli/termenv v0.7.4 h1:/pBqvU5CpkY53tU0vVn+xgs2ZTX63aH5nY+SSps5Xa8=
github.com/muesli/termenv v0.7.4/go.mod h1:pZ7qY9l3F7e5xsAOS0zCew2tME+p7bWeBkotCEcIIcc=
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/rivo/uniseg v0.1.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
github.com/rivo/uniseg v0.2.0 h1:S1pD9weZBuJdFmowNwbpi7BJ8TNftyUImj/0WQi72jY=
github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20201012173705-84dcc777aaee h1:4yd7jl+vXjalO5ztz6Vc1VADv+S/80LGJmyl1ROJ2AI=
golang.org/x/crypto v0.0.0-20201012173705-84dcc777aaee/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200916030750-2334cc1a136f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20201009025420-dfb3f7c4e634 h1:bNEHhJCnrwMKNMmOx3yAynp5vs5/gRy+XWFtZFu7NBM=
golang.org/x/sys v0.0.0-20201009025420-dfb3f7c4e634/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
141 changes: 141 additions & 0 deletions main.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,141 @@
/**
* 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 (
"fmt"
"os"
"strings"
"time"

tea "github.com/charmbracelet/bubbletea"
"github.com/muesli/termenv"
)

var term = termenv.ColorProfile()

type model struct {
zones []Zone
hour int
}

func (m model) Init() tea.Cmd {
// Just return `nil`, which means "no I/O right now, please."
return nil
}

func (m model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
switch msg := msg.(type) {

case tea.KeyMsg:
switch msg.String() {
case "ctrl+c", "q", "esc":
return m, tea.Quit

case "left", "h":
if m.hour == 0 {
m.hour = 23
} else {
m.hour--
}

case "right", "l":
if m.hour > 22 {
m.hour = 0
} else {
m.hour++
}
}
}
return m, nil
}

func (m model) View() string {
s := "What time is it?\n\n"

// Show hours for each zone
for zi, z := range m.zones {
hours := strings.Builder{}
startAt := 0
if zi > 0 {
startAt = (z.offset - m.zones[0].offset) % 24
}

// A list of hours
for i := startAt; i < startAt+24; i++ {
hour := ((i % 24) + 24) % 24
out := termenv.String(fmt.Sprintf("%2d", hour))

out = out.Foreground(term.Color(hourColorCode(hour)))
// Cursor
if m.hour == i-startAt {
out = out.Background(term.Color("41")).Foreground(term.Color("#000000"))
}
hours.WriteString(out.String())
hours.WriteString(" ")
}

zoneHeader := termenv.String(fmt.Sprintf("%s %s: %s", z.ClockEmoji(), z, z.ShortDT()))
zoneHeader = zoneHeader.Background(term.Color("234")).Foreground(term.Color("255"))

s += fmt.Sprintf("%s\n%s\n\n", zoneHeader, hours.String())
}

s += "\nPress q to quit.\n"
return s
}

// Return a color matching the time of the day at a given hour.
func hourColorCode(hour int) (color string) {
switch hour {
// Morning
case 7, 8:
color = "12"

// Day
case 9, 10, 11, 12, 13, 14, 15, 16, 17:
color = "11"

// Evening
case 18, 19:
color = "3"

// Night
default:
color = "17"
}
return color
}

func main() {
now := time.Now()
config, err := LoadConfig()
if err != nil {
fmt.Fprintf(os.Stderr, "Error loading config: %s", err)
os.Exit(2)
}
var initialModel = model{
zones: config.Zones,
hour: now.Hour(),
}
p := tea.NewProgram(initialModel)
if err := p.Start(); err != nil {
fmt.Printf("Alas, there's been an error: %v", err)
os.Exit(1)
}

}
94 changes: 94 additions & 0 deletions zone.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
/**
* 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 "time"

var name, offset = time.Now().Zone()
var DefaultZones = []Zone{
{
name: "Local",
dbName: name,
offset: offset / 3600,
},
{
name: "Paris",
dbName: "Europe/Paris",
offset: 1,
},
{
name: "New-York",
dbName: "America/New_York",
offset: -5,
},
{
name: "UTC",
dbName: "UTC",
offset: 0,
},
}

// EmojiClocks maps hour to the corresponding clock emoji
var EmojiClocks = map[int]string{
0: "πŸ•›",
1: "πŸ•",
2: "πŸ•‘",
3: "πŸ•’",
4: "πŸ•“",
5: "πŸ•”",
6: "πŸ••",
7: "πŸ•–",
8: "πŸ•—",
9: "πŸ•˜",
10: "πŸ•™",
11: "πŸ•™",
}

// Zone stores the name of a time zone and its integer offset from UTC.
type Zone struct {
dbName string // tz db name
name string // custom name
offset int // Integer offset from UTC
}

func (z Zone) String() string {
return z.name
}

// ClockEmoji returns the corresponding emoji clock for a given hour
func (z Zone) ClockEmoji() (clock string) {
h := ((z.currentTime().Hour() % 12) + 12) % 12
return EmojiClocks[h]
}

// ShortDT returns the current time in short format.
func (z Zone) ShortDT() string {
return z.currentTime().Format("3:04PM, Mon 02")
}

func (z Zone) currentTime() time.Time {
now := time.Now()
zName, _ := now.Zone()
if z.dbName != zName {
loc, err := time.LoadLocation(z.dbName)
if err != nil {
return now
}
return now.In(loc)
}
return now
}

0 comments on commit 092a39a

Please sign in to comment.