-
Notifications
You must be signed in to change notification settings - Fork 1
/
calendar.go
150 lines (131 loc) · 4.33 KB
/
calendar.go
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
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
package server
import (
"context"
"errors"
"time"
ics "github.com/arran4/golang-ical"
"github.com/brackendawson/webcal-proxy/cache"
)
// Event is not an exhaustive view of event components
type Event struct {
StartTime, EndTime time.Time
Summary, Location, Description string
}
type Day struct {
time.Time
// Events are the events in this day, or nil if the day has no events, or if
// []Day was made with a nil downstream.
Events []Event
}
func appendDay(ctx context.Context, s []Day, target time.Time, downstream *ics.Calendar, days ...time.Time) []Day {
for _, d := range days {
thisDay := Day{
Time: time.Date(d.Year(), d.Month(), d.Day(), 0, 0, 0, 0, d.Location()),
}
if downstream == nil {
s = append(s, thisDay)
continue
}
for _, event := range downstream.Events() {
var (
newEvent Event
err error
)
if newEvent.StartTime, err = event.GetStartAt(); err != nil {
log(ctx).Warnf("Invalid event start time: %s", err)
continue
}
// Date only events (no time) are parsed to midnight on time.Local,
// this must be normalised to the target time zone for inequalities
// to work.
if newEvent.StartTime.Location() == time.Local {
newEvent.StartTime = setLocation(newEvent.StartTime, target.Location())
}
newEvent.StartTime = newEvent.StartTime.In(target.Location())
if newEvent.StartTime.After(thisDay.AddDate(0, 0, 1)) ||
newEvent.StartTime.Equal(thisDay.AddDate(0, 0, 1)) {
continue
}
if newEvent.EndTime, err = event.GetEndAt(); err != nil {
if errors.Is(err, ics.ErrorPropertyNotFound) {
log(ctx).Debugf("No event end time: %s", err) // TODO contribute a defined error here
continue
}
log(ctx).Warnf("Invalid event end time: %s", err) // TODO contribute a defined error here
continue
}
if newEvent.EndTime.Location() == time.Local {
newEvent.EndTime = setLocation(newEvent.EndTime, target.Location())
}
newEvent.EndTime = newEvent.EndTime.In(target.Location())
if !newEvent.EndTime.IsZero() &&
newEvent.EndTime.Before(thisDay.Time) ||
newEvent.EndTime.Equal(thisDay.Time) {
continue
}
if summary := event.GetProperty(ics.ComponentPropertySummary); summary != nil {
newEvent.Summary = summary.Value
}
if location := event.GetProperty(ics.ComponentPropertyLocation); location != nil {
newEvent.Location = location.Value
}
if description := event.GetProperty(ics.ComponentPropertyDescription); description != nil {
newEvent.Description = description.Value
}
thisDay.Events = append(thisDay.Events, newEvent)
}
s = append(s, thisDay)
}
return s
}
// SameDate returns true if as has the same calendar date as c, regardless of
// location.
func (d Day) SameDate(as time.Time) bool {
return d.Day() == as.Day() &&
d.Month() == as.Month() &&
d.Year() == as.Year()
}
// setLocation changes a time.Time's location without changing the clock time.
func setLocation(t time.Time, l *time.Location) time.Time {
return time.Date(t.Year(), t.Month(), t.Day(), t.Hour(), t.Minute(),
t.Second(), t.Nanosecond(), l)
}
type Month struct {
View
// Target is the date the user wishes to view, it may have a day, time, or
// location set.
Target time.Time
// Now is the user's current time, it may have a day, time, or location set.
Now time.Time
// Days are the days in the Target Month plus the days before and after the
// target month to fill incomplete leading and trailing weeks.
Days []Day
// Cache is always the unfiltered upstream ICS or nil.
Cache *cache.Webcal
// URL is the new webcal:// link for the User.
URL string
// Error is the error to show to the user or empty string.
Error string
}
func newMonth(ctx context.Context, view View, target, today time.Time, downstream *ics.Calendar) Month {
cal := Month{
View: view,
Target: target,
Now: today,
}
float := target.AddDate(0, 0, -target.Day()+1)
endOfMonth := float.AddDate(0, 1, 0)
float = float.AddDate(0, 0, -mondayIndexWeekday(float.Weekday()))
for float.Before(endOfMonth) {
cal.Days = appendDay(ctx, cal.Days, target, downstream, float)
float = float.AddDate(0, 0, 1)
}
for float.Weekday() != time.Monday {
cal.Days = appendDay(ctx, cal.Days, target, downstream, float)
float = float.AddDate(0, 0, 1)
}
return cal
}
func mondayIndexWeekday(d time.Weekday) int {
return ((int(d)-1)%7 + 7) % 7
}