167 lines
4.0 KiB
Go
167 lines
4.0 KiB
Go
|
package main
|
||
|
|
||
|
import (
|
||
|
"fmt"
|
||
|
"strings"
|
||
|
"time"
|
||
|
|
||
|
"github.com/charmbracelet/lipgloss"
|
||
|
"github.com/dustin/go-humanize"
|
||
|
)
|
||
|
|
||
|
type calendarTime struct {
|
||
|
time.Time
|
||
|
}
|
||
|
|
||
|
// June 2025
|
||
|
// Su Mo Tu We Th Fr Sa
|
||
|
// 1 2 3 4 5 6 7
|
||
|
// 8 9 10 11 12 13 14
|
||
|
// 15 16 17 18 19 20 21
|
||
|
// 22 23 24 25 26 27 28
|
||
|
// 29 30
|
||
|
|
||
|
var dayCols = map[int]time.Weekday{
|
||
|
0: time.Monday,
|
||
|
1: time.Tuesday,
|
||
|
2: time.Wednesday,
|
||
|
3: time.Thursday,
|
||
|
4: time.Friday,
|
||
|
5: time.Saturday,
|
||
|
6: time.Sunday,
|
||
|
}
|
||
|
|
||
|
const brailleStart = 0x2800
|
||
|
const brailleEnd = 0x28FF
|
||
|
|
||
|
var baseStyle = lipgloss.NewStyle().Width(7 * 3)
|
||
|
|
||
|
var weekdayStyle = lipgloss.NewStyle().Foreground(lipgloss.Color("#2222f2"))
|
||
|
var weekendStyle = lipgloss.NewStyle().Foreground(lipgloss.Color("#ff0022"))
|
||
|
var monthNameStyle = lipgloss.NewStyle().Foreground(lipgloss.Color("#00dd80"))
|
||
|
var yearStyle = lipgloss.NewStyle().Foreground(lipgloss.Color("#a0dd10"))
|
||
|
var calendarTitle = baseStyle.AlignHorizontal(lipgloss.Center)
|
||
|
var dayStyle = lipgloss.NewStyle()
|
||
|
var dayStartStyle = dayStyle.Background(lipgloss.Color("#555555"))
|
||
|
var highlightStyle = dayStyle.Foreground(lipgloss.Color("#000000")).Background(lipgloss.Color("#ffffff")).Blink(true)
|
||
|
|
||
|
// calendar takes three calendarTime's, the calendar month to show, the "start" calendar time
|
||
|
// (shown with a underline) and the highlighted calendarTime.
|
||
|
func calendar(t calendarTime, start calendarTime, hl calendarTime, styles map[string]lipgloss.Style) string {
|
||
|
f, _ := t.firstLastDays()
|
||
|
out := calendarTitle.Render(fmt.Sprintf("%s %s", monthNameStyle.Render(f.Month().String()), yearStyle.Render(fmt.Sprint(f.Year()))))
|
||
|
out += "\n"
|
||
|
out += weekdayStyle.Render(" Mo Tu We Th Fr ")
|
||
|
out += weekendStyle.Render("Sa Su")
|
||
|
out += "\n"
|
||
|
|
||
|
at := f
|
||
|
col := 0
|
||
|
lines := 0
|
||
|
for {
|
||
|
if at.Weekday() == dayCols[col] {
|
||
|
|
||
|
// codePoint := brailleStart + rand.Intn(brailleEnd-brailleStart+1)
|
||
|
// brailleChar := rune(codePoint)
|
||
|
|
||
|
// if rand.Intn(100) < 80 {
|
||
|
// brailleChar = ' '
|
||
|
// }
|
||
|
// pre := lipgloss.NewStyle().Foreground(lipgloss.Color("#d00010")).Render(string(brailleChar))
|
||
|
|
||
|
dayStyle := dayStyle
|
||
|
specialStyle, ok := styles[at.Format(time.DateOnly)]
|
||
|
if ok {
|
||
|
dayStyle = specialStyle
|
||
|
}
|
||
|
|
||
|
dayString := fmt.Sprintf("%2d", at.Day())
|
||
|
padding := 3 - len(fmt.Sprint(at.Day()))
|
||
|
padding = 1
|
||
|
out += strings.Repeat(" ", padding)
|
||
|
|
||
|
if at.dayEqual(hl) {
|
||
|
out += highlightStyle.Render(dayString)
|
||
|
} else if at.dayEqual(start) {
|
||
|
out += dayStartStyle.Render(dayString)
|
||
|
} else {
|
||
|
out += dayStyle.Render(dayString)
|
||
|
}
|
||
|
} else {
|
||
|
col++
|
||
|
out += " "
|
||
|
continue
|
||
|
}
|
||
|
|
||
|
at = calendarTime{at.AddDate(0, 0, 1)}
|
||
|
if at.Month() != f.Month() {
|
||
|
break
|
||
|
}
|
||
|
|
||
|
col++
|
||
|
if col > 6 {
|
||
|
out += "\n"
|
||
|
col = 0
|
||
|
lines++
|
||
|
}
|
||
|
}
|
||
|
|
||
|
for lines < 6 {
|
||
|
out += "\n"
|
||
|
lines++
|
||
|
}
|
||
|
out += "\n"
|
||
|
return out
|
||
|
|
||
|
}
|
||
|
|
||
|
func (ct calendarTime) dayEqual(t calendarTime) bool {
|
||
|
y1, m1, d1 := ct.Date()
|
||
|
y2, m2, d2 := t.Date()
|
||
|
if y1 == y2 && m1 == m2 && d1 == d2 {
|
||
|
return true
|
||
|
}
|
||
|
return false
|
||
|
}
|
||
|
|
||
|
func (ct calendarTime) firstLastDays() (calendarTime, calendarTime) {
|
||
|
loc := ct.Location()
|
||
|
y, m, _ := ct.Date()
|
||
|
first := time.Date(y, m, 1, 0, 0, 0, 0, loc)
|
||
|
m++
|
||
|
if m > 12 {
|
||
|
y++
|
||
|
}
|
||
|
last := time.Date(y, m, 1, 0, 0, 0, 0, loc).AddDate(0, 0, -1)
|
||
|
return calendarTime{first}, calendarTime{last}
|
||
|
}
|
||
|
|
||
|
func (ct calendarTime) addDay() calendarTime {
|
||
|
return calendarTime{ct.AddDate(0, 0, 1)}
|
||
|
}
|
||
|
func (ct calendarTime) subDay() calendarTime {
|
||
|
return calendarTime{ct.AddDate(0, 0, -1)}
|
||
|
}
|
||
|
|
||
|
func (ct calendarTime) addWeek() calendarTime {
|
||
|
return calendarTime{ct.AddDate(0, 0, 7)}
|
||
|
}
|
||
|
func (ct calendarTime) subWeek() calendarTime {
|
||
|
return calendarTime{ct.AddDate(0, 0, -7)}
|
||
|
}
|
||
|
func (ct calendarTime) addMonth() calendarTime {
|
||
|
return calendarTime{ct.AddDate(0, 1, 0)}
|
||
|
}
|
||
|
func (ct calendarTime) subMonth() calendarTime {
|
||
|
return calendarTime{ct.AddDate(0, -1, 0)}
|
||
|
}
|
||
|
|
||
|
func (ct calendarTime) to(t calendarTime) string {
|
||
|
from := t.Time
|
||
|
to := ct.Time
|
||
|
rel := humanize.RelTime(t.Time, ct.Time, "earlier", "later")
|
||
|
days := int(from.Sub(to).Hours() / 24)
|
||
|
|
||
|
return fmt.Sprintf("%s / %d days", rel, days)
|
||
|
}
|