summaryrefslogtreecommitdiff
path: root/cmd/generate/calendar.go
blob: 29c3318c07c91561429045b90a33e0760d0806df (plain)
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
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
package main

import (
	"time"
)

//////////////////////////////////////////////////////////////////////

type Date struct {
	Year  int
	Month time.Month
	Day   int
}

func DateOf(t time.Time) Date {
	y, m, d := t.Date()
	return Date{Year: y, Month: m, Day: d}
}

func (d Date) Time() time.Time {
	return time.Date(d.Year, d.Month, d.Day, 0, 0, 0, 0, time.Local)
}

func (d Date) AddDays(delta int) Date {
	return DateOf(d.Time().AddDate(0, 0, delta))
}

func (d Date) Weekday() time.Weekday {
	return d.Time().Weekday()
}

func (a Date) Cmp(b Date) int {
	switch {
	case a.Year < b.Year:
		return -1
	case a.Year > b.Year:
		return 1
	}
	switch {
	case a.Month < b.Month:
		return -1
	case a.Month > b.Month:
		return 1
	}
	switch {
	case a.Day < b.Day:
		return -1
	case a.Day > b.Day:
		return 1
	}
	return 0
}

//////////////////////////////////////////////////////////////////////

type CalendarDay[T any] struct {
	Date
	Data T
}

//////////////////////////////////////////////////////////////////////

// keyed by time.Weekday
type CalendarWeek[T any] [7]CalendarDay[T]

//////////////////////////////////////////////////////////////////////

// must be sorted, must be non-sparse
type Calendar[T any] []CalendarWeek[T]

func (c Calendar[T]) NumWeekdaysInMonth(weekday time.Weekday, target Date) int {
	num := 0
	for _, w := range c {
		if w[weekday].Date == (Date{}) {
			continue
		}
		switch {
		case w[weekday].Year == target.Year:
			switch {
			case w[weekday].Month == target.Month:
				num++
			case w[weekday].Month > target.Month:
				return num
			}
		case w[weekday].Year > target.Year:
			return num
		}
	}
	return num
}

//////////////////////////////////////////////////////////////////////

func BuildCalendar[T any](things []T, dateOfThing func(T) Date) Calendar[T] {
	if len(things) == 0 {
		return nil
	}

	newestDate := DateOf(time.Now().Local())

	oldestDate := dateOfThing(things[0])
	byDate := make(map[Date]T, len(things))
	for _, thing := range things {
		date := dateOfThing(thing)
		if oldestDate.Cmp(date) > 0 {
			oldestDate = date
		}
		byDate[date] = thing
	}

	var ret Calendar[T]
	for date := oldestDate; date.Cmp(newestDate) <= 0; date = date.AddDays(1) {
		if len(ret) == 0 || date.Weekday() == 0 {
			ret = append(ret, CalendarWeek[T]{})
		}
		ret[len(ret)-1][date.Weekday()] = CalendarDay[T]{
			Date: date,
			Data: byDate[date],
		}
	}
	return ret
}