Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feature: flag -dtfmt #18

Open
wants to merge 5 commits into
base: main
Choose a base branch
from
Open
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
188 changes: 131 additions & 57 deletions cmd/astral/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import (
"sort"
"strings"
"time"
"unicode/utf8"

"github.com/logrusorgru/aurora/v3"
"github.com/sj14/astral/pkg/astral"
Expand All @@ -21,9 +22,11 @@ var (
date = "undefined"
)

const dashes = "┈"

func main() {
var (
dateTimeFormat = "Jan _2 15:04"
dateTimeFormat = TimeFormatAstral
timeFormat = "15:04"

timeFlag = flag.String("time", time.Now().Format(time.RFC3339), "day/time used for the calculation")
Expand All @@ -32,6 +35,7 @@ func main() {
elevationFlag = flag.Float64("elev", 0, "elevation of the observer")
versionFlag = flag.Bool("version", false, fmt.Sprintf("print version information of this release (%v)", version))
)
flag.StringVar(&dateTimeFormat, "dtfmt", dateTimeFormat, "date/time format to use for output")
flag.Parse()

if *versionFlag {
Expand All @@ -41,6 +45,12 @@ func main() {
os.Exit(0)
}

dateTimeFormat, err := FormatName(dateTimeFormat)
if err != nil {
fmt.Fprintf(os.Stderr, "error '-dtfmt': %s", err)
return
}

observer := astral.Observer{Latitude: *latFlag, Longitude: *longFlag, Elevation: *elevationFlag}

t, err := time.Parse(time.RFC3339, *timeFlag)
Expand Down Expand Up @@ -109,89 +119,153 @@ func main() {
log.Fatalf("failed parsing moon phase: %v", err)
}

dashes := "┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈"

dates := make(map[time.Time]colorDesc)
dates[t] = colorDesc{desc: dashes}
dates[dawnAstronomical] = colorDesc{color: aurora.BgGray(8, " "), desc: "Dawn (Astronomical)"}
dates[dawnNautical] = colorDesc{color: aurora.BgGray(15, " "), desc: "Dawn (Nautical)"}
dates[dawnCivil] = colorDesc{color: aurora.BgIndex(111, " "), desc: "Dawn (Civil) Twilight Start Blue Hour Start"}
dates[goldenRisingStart] = colorDesc{color: aurora.BgIndex(208, " "), desc: "Golden Hour Start Blue Hour End"}
dates[sunrise] = colorDesc{color: aurora.BgIndex(214, " "), desc: "Sunrise Twilight End"}
dates[goldenRisingEnd] = colorDesc{color: aurora.BgIndex(226, " "), desc: "Golden Hour End"}
dates[noon] = colorDesc{color: aurora.BgIndex(226, " "), desc: "Noon"}
dates[goldenSettingStart] = colorDesc{color: aurora.BgIndex(214, " "), desc: "Golden Hour Start"}
dates[sunset] = colorDesc{color: aurora.BgIndex(208, " "), desc: "Sunset Twilight Start"}
dates[goldenSettingEnd] = colorDesc{color: aurora.BgIndex(111, " "), desc: "Golden Hour End Blue Hour Start"}
dates[duskCivil] = colorDesc{color: aurora.BgGray(18, " "), desc: "Dusk (Civil) Twilight End Blue Hour End "}
dates[duskNautical] = colorDesc{color: aurora.BgGray(15, " "), desc: "Dusk (Nautical)"}
dates[duskAstronomical] = colorDesc{color: aurora.BgGray(8, " "), desc: "Dusk (Astronomical)"}
dates[midnight] = colorDesc{color: aurora.BgBlack(" "), desc: "Midnight"}

var sortedTimes timeSlice

for key := range dates {
sortedTimes = append(sortedTimes, key)
dates := eventTable{
&eventEntry{t, colorDesc{special: true}},
&eventEntry{dawnAstronomical, colorDesc{color: aurora.BgGray(8, " "), desc: "Dawn (Astronomical)"}},
&eventEntry{dawnNautical, colorDesc{color: aurora.BgGray(15, " "), desc: "Dawn (Nautical)"}},
&eventEntry{dawnCivil, colorDesc{color: aurora.BgIndex(111, " "), desc: "Dawn (Civil) Twilight Start Blue Hour Start"}},
&eventEntry{goldenRisingStart, colorDesc{color: aurora.BgIndex(208, " "), desc: "Golden Hour Start Blue Hour End"}},
&eventEntry{sunrise, colorDesc{color: aurora.BgIndex(214, " "), desc: "Sunrise Twilight End"}},
&eventEntry{goldenRisingEnd, colorDesc{color: aurora.BgIndex(226, " "), desc: "Golden Hour End"}},
&eventEntry{noon, colorDesc{color: aurora.BgIndex(226, " "), desc: "Noon"}},
&eventEntry{goldenSettingStart, colorDesc{color: aurora.BgIndex(214, " "), desc: "Golden Hour Start"}},
&eventEntry{sunset, colorDesc{color: aurora.BgIndex(208, " "), desc: "Sunset Twilight Start"}},
&eventEntry{goldenSettingEnd, colorDesc{color: aurora.BgIndex(111, " "), desc: "Golden Hour End Blue Hour Start"}},
&eventEntry{duskCivil, colorDesc{color: aurora.BgGray(18, " "), desc: "Dusk (Civil) Twilight End Blue Hour End "}},
&eventEntry{duskNautical, colorDesc{color: aurora.BgGray(15, " "), desc: "Dusk (Nautical)"}},
&eventEntry{duskAstronomical, colorDesc{color: aurora.BgGray(8, " "), desc: "Dusk (Astronomical)"}},
&eventEntry{midnight, colorDesc{color: aurora.BgBlack(" "), desc: "Midnight"}},
}
sort.Sort(sortedTimes)
sort.Sort(dates)

fmt.Printf("Date/Time\t%v\n", t.Format(time.UnixDate))
fmt.Printf("Date/Time\t%v\n", t.Format(dateTimeFormat))
fmt.Printf("Latitude\t%v\nLongitude\t%v\nElevation\t%v\n", *latFlag, *longFlag, *elevationFlag)
fmt.Println()
fmt.Printf("Daylight\t%v\n", sunset.Sub(sunrise).Truncate(1*time.Second))
fmt.Printf("Night-Time\t%v\n", sunriseNextDay.Sub(sunset).Truncate(1*time.Second))
fmt.Printf("Moon Phase\t%v (%v)\n", moonDesc, moonPhase)
fmt.Printf("Moon Phase\t%v (%.2f)\n", moonDesc, moonPhase)
fmt.Println()

longestDesc := 0
for _, de := range dates {
if ld := utf8.RuneCountInString(de.desc); ld > longestDesc {
longestDesc = ld
}
}

lastColor := aurora.BgBlack(" ")
for _, key := range sortedTimes {
for _, de := range dates {

// calculate when the particular phase happend or will happen
inHours := math.Abs(key.Sub(t).Truncate(1 * time.Hour).Hours())
inMinutes := int(math.Abs((key.Sub(t).Truncate(1 * time.Minute).Minutes()))) % 60

agoOrUntil := fmt.Sprintf("%02.0f:%02d", inHours, inMinutes)
if key.Before(t) {
agoOrUntil = fmt.Sprintf("-%s", agoOrUntil)
} else {
agoOrUntil = fmt.Sprintf("+%s", agoOrUntil)
inHours := math.Abs(de.time.Sub(t).Truncate(1 * time.Hour).Hours())
inMinutes := int(math.Abs((de.time.Sub(t).Truncate(1 * time.Minute).Minutes()))) % 60

sign := "+"
if de.time.Before(t) {
sign = "-"
}
agoOrUntil := fmt.Sprintf("%s%02.0f:%02d", sign, inHours, inMinutes)

prefix := fmt.Sprintf("%v (%v)", de.time.Format(dateTimeFormat), agoOrUntil)

// edge case for the given time
if dates[key].desc == dashes {
prefixDashesCount := len(dateTimeFormat) - len(timeFormat) - 1
if prefixDashesCount < 0 {
prefixDashesCount = 0
}

prefixDashes := key.Format(strings.Repeat("┈", prefixDashesCount))
midDashes := strings.Repeat("┈", len(agoOrUntil)+2)
t := key.Truncate(1 * time.Minute).Format(timeFormat)

fmt.Printf("%v %v %v %v %v\n", prefixDashes, t, midDashes, lastColor, dates[key].desc)
continue
if de.special {

t := fmt.Sprintf(" %v ", de.time.Truncate(1*time.Minute).Format(timeFormat))
l := utf8.RuneCountInString(prefix) - utf8.RuneCountInString(t)

preDashes := strings.Repeat(dashes, l/2)
postDashes := strings.Repeat(dashes, l-(l/2))

prefix = fmt.Sprintf("%v%v%v", preDashes, t, postDashes)
de.color = lastColor
de.desc = strings.Repeat(dashes, longestDesc)
}

lastColor = dates[key].color
fmt.Printf("%v (%v) %v %v\n", key.Format(dateTimeFormat), agoOrUntil, dates[key].color, dates[key].desc)
lastColor = de.color

fmt.Printf("%v %v %v\n", prefix, de.color, de.desc)
}
}

type colorDesc struct {
color aurora.Value
desc string
color aurora.Value
desc string
special bool
}

type timeSlice []time.Time
type eventEntry struct {
time time.Time
colorDesc
}

func (p timeSlice) Len() int {
type eventTable []*eventEntry

func (p eventTable) Len() int {
return len(p)
}

func (p timeSlice) Less(i, j int) bool {
return p[i].Before(p[j])
func (p eventTable) Less(i, j int) bool {
return p[i].time.Before(p[j].time)
}

func (p timeSlice) Swap(i, j int) {
func (p eventTable) Swap(i, j int) {
p[i], p[j] = p[j], p[i]
}

const (
// astrals default time format
TimeFormatAstral = "Jan _2 15:04"
// TimeFormatGo handles Go's default time.Now() format
// (e.g. 2019-01-26 09:43:57.377055 +0100 CET m=+0.644739467)
TimeFormatGo = "2006-01-02 15:04:05.999999999 -0700 MST"
// TimeFormatSimple handles "2019-01-25 21:51:38"
TimeFormatSimple = "2006-01-02 15:04:05.999999999"
// TimeFormatHTTP instead of importing main with http.TimeFormat
// which would increase the binary size significantly.
TimeFormatHTTP = "Mon, 02 Jan 2006 15:04:05 GMT"
)

func FormatName(format string) (string, error) {

if format == TimeFormatAstral {
return TimeFormatAstral, nil
}

switch strings.ToLower(format) {
case "":
return TimeFormatGo, nil
case "unix":
return time.UnixDate, nil
case "ruby":
return time.RubyDate, nil
case "ansic":
return time.ANSIC, nil
case "rfc822":
return time.RFC822, nil
case "rfc822z":
return time.RFC822Z, nil
case "rfc850":
return time.RFC850, nil
case "rfc1123":
return time.RFC1123, nil
case "rfc1123z":
return time.RFC1123Z, nil
case "rfc3339":
return time.RFC3339, nil
case "rfc3339nano":
return time.RFC3339Nano, nil
case "stamp":
return time.Stamp, nil
case "stampmilli":
return time.StampMilli, nil
case "stampmicro":
return time.StampMicro, nil
case "stampnano":
return time.StampNano, nil
case "http":
return TimeFormatHTTP, nil
default:
return TimeFormatGo, fmt.Errorf("failed to parse format %q", format)
}
}