summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--templates/report.t13
-rw-r--r--zeitgeist.go160
2 files changed, 169 insertions, 4 deletions
diff --git a/templates/report.t b/templates/report.t
index ce1e74c..d82b8bf 100644
--- a/templates/report.t
+++ b/templates/report.t
@@ -4,7 +4,16 @@ Start: {{.Start}}
End: {{.End}}
Beneficiary: {{.Beneficiary}}
-{{ range $id, $wp := .Workpackages }}
+
+# Calendar
+
+{{ range $y, $c := .Calendars }}
+{{ $c }}
+{{ end }}
+
+
+
+{{ range $id, $wp := .Workpackages -}}
## Workpackage {{ $id }}: {{ $wp.Title }}
{{ range $tid, $task := $wp.Tasks }}
{{$task.ID}}: {{ $task.Title }}
@@ -21,7 +30,7 @@ Beneficiary: {{.Beneficiary}}
{{- $tl := .Timeline }}{{- $hd := .Holidays }}
# Days
-{{ range .Days }}
+{{ range .Days 2024 03 }}
{{if or .IsWeekend .IsHoliday }}# {{end}}{{.Date}}{{ if .IsHoliday}} ({{ index $hd .Date}}){{end -}}
{{ with .Planned }} Planned: {{ .Total }}{{ end }}{{ with .Worked }} Worked: {{ .Total }}{{end}}
{{- end }}
diff --git a/zeitgeist.go b/zeitgeist.go
index c161fa0..e20ad35 100644
--- a/zeitgeist.go
+++ b/zeitgeist.go
@@ -2,6 +2,7 @@ package main
import (
"bufio"
+ "bytes"
"embed"
"encoding/json"
"flag"
@@ -37,6 +38,7 @@ type Project struct {
Planning string
Workpackages map[string]Workpackage
Holidays map[Date]string
+ Blocked Timeline
Timeline Timeline
Planned Timeline
}
@@ -123,6 +125,7 @@ func (u Amount) String() string {
r := ""
if int(m) > 0 {
r = fmt.Sprintf("%dm", int(m))
+ d -= DaysInMonth * m
}
if int(d) > 0 {
r += fmt.Sprintf("%dd", int(d))
@@ -540,9 +543,58 @@ func (f *FullDate) Difference() Amount {
return f.Planned.Total() - f.Worked.Total()
}
-func (p *Project) Days() []FullDate {
+func (p *Project) Days(ymd ...int) []FullDate {
dates := []FullDate{}
- for d := time.Time(p.Start); d.Before(time.Time(p.End)); d = d.AddDate(0, 0, 1) {
+ start := time.Time(p.Start)
+ end := time.Time(p.End)
+
+ if len(ymd) > 0 {
+ year := ymd[0]
+ month := -1
+ day := -1
+
+ if len(ymd) > 1 {
+ month = ymd[1]
+ }
+
+ if len(ymd) > 2 {
+ day = ymd[2]
+ }
+
+ if month < 0 && day < 0 {
+ s := time.Date(year, time.January, 1, 0, 0, 0, 0, time.UTC)
+ e := s.AddDate(1, 0, -1)
+ if s.Before(start) {
+ slog.Error("year out of range", "year", year)
+ return nil
+ }
+ start = s
+ if e.Before(end) {
+ end = e
+ }
+ } else if day < 0 {
+ s := time.Date(year, time.Month(month), 1, 0, 0, 0, 0, time.UTC)
+ e := s.AddDate(0, 1, -1)
+ if s.Before(start) {
+ slog.Error("year and month out of range", "year", year, "month", month, "start", start, "s", s)
+ return nil
+ }
+ start = s
+ if e.Before(end) {
+ end = e
+ }
+ } else {
+ s := time.Date(year, time.Month(month), day, 0, 0, 0, 0, time.UTC)
+ if s.Before(start) {
+ slog.Error("year, month, day out of range", "year", year, "month", month, "day", day)
+ return nil
+ }
+ start = s
+ end = s
+ }
+ }
+
+ for d := start; !d.Equal(end); d = d.AddDate(0, 0, 1) {
d := Date(d)
dates = append(dates,
@@ -556,3 +608,107 @@ func (p *Project) Days() []FullDate {
}
return dates
}
+
+func (p *Project) monthCal(year, month int) []string {
+ /*
+ 2 9 16 23 30
+ 3 10 17 24 31
+ 4 11 18 25
+ 5 12 19 26
+ 6 13 20 27
+ 7 14 21 28
+ 1 8 15 22 29
+ */
+
+ const cw = 4
+ const wd = 6 * cw
+ const sp = " "
+
+ lines := [7]string{}
+ m := time.Month(month)
+ date := time.Date(year, m, 1, 0, 0, 0, 0, time.UTC)
+ skip := 0
+ for i := 0; i < 42; i++ {
+ d := int((date.Weekday() + 6) % 7)
+ if i < d {
+ lines[i] += sp
+ skip += 1
+ continue
+ }
+ if date.Month() != m {
+ lines[d] += sp
+ } else {
+ l := 2
+ if i-skip > 6 {
+ l = 3
+ }
+ str := fmt.Sprintf("%[1]*d", l, i+1-skip)
+ suf := " "
+ if _, ok := p.Holidays[Date(date)]; ok {
+ str = fmt.Sprintf("%[1]*s", l, "☺")
+ } else if _, ok := p.Blocked[Date(date)]; ok {
+ suf = "¬"
+ } else if _, ok := p.Timeline[Date(date)]; ok {
+ suf = "•"
+ } else if _, ok := p.Planned[Date(date)]; ok {
+ suf = "¤" //"¶"
+ }
+ lines[d] += str + suf
+ }
+ date = date.AddDate(0, 0, 1)
+ }
+
+ return lines[:]
+}
+
+func (p *Project) MonthCalendar(year, month int) string {
+ lines := []string{"Mo", "Tu", "We", "Th", "Fr", "Sa", "Su"}
+ c := p.monthCal(year, month)
+ h := fmt.Sprintf(" %-[1]*s %4d\n", len(bytes.Runes([]byte(c[0])))-9, time.Month(month), year)
+ for i, pre := range lines {
+ lines[i] = pre + " " + c[i]
+ }
+ return h + strings.Join(lines, "\n")
+}
+
+func (p *Project) QuaterYearCalendar(year, quarter int) string {
+ return "not implemented"
+}
+
+func (p *Project) YearCalendar(year int) string {
+ t := [12][]string{}
+ for i := range 12 {
+ t[i] = p.monthCal(year, i+1)
+ }
+ wd := len(bytes.Runes([]byte(t[0][0])))
+ lines := []string{}
+ pre := []string{"Mo", "Tu", "We", "Th", "Fr", "Sa", "Su"}
+
+ for q := range 4 {
+ header := ""
+ for m := range 3 {
+ header += fmt.Sprintf(" %-[1]*s", wd-2, time.Month(q*3+m+1))
+ }
+ lines = append(lines, header)
+
+ for w, line := range pre {
+ for m := range 3 {
+ line += " " + t[q*3+m][w]
+ }
+ lines = append(lines, line)
+ }
+ lines = append(lines, "")
+ }
+
+ s := fmt.Sprintf("%[1]*d\n", 3*wd/2+2, year)
+
+ return s + strings.Join(lines, "\n") + "\n\n"
+}
+
+func (p *Project) Calendars() map[int]string {
+ r := make(map[int]string)
+ for y := p.Start.Year(); y <= p.End.Year(); y++ {
+ r[y] = p.YearCalendar(y)
+ }
+ return r
+}