Skip to content

Commit

Permalink
Restructured code a bit
Browse files Browse the repository at this point in the history
  • Loading branch information
pantierra committed Jan 20, 2024
1 parent 8e3e693 commit 8bd8760
Show file tree
Hide file tree
Showing 4 changed files with 117 additions and 100 deletions.
2 changes: 1 addition & 1 deletion cmd/calendar-bot/calendar-bot.go
Original file line number Diff line number Diff line change
Expand Up @@ -96,7 +96,7 @@ func main() {
infoLog.Printf("Scheduling notifications for %s", notifyTime.Format("15:04"))
s.Every(1).Day().At(notifyTime).Do(func() {
infoLog.Println("Start Notification")
cal, err := calendar.NewCalendar(conf.Calendar, infoLog)
cal, err := calendar.ImportCalendar(conf.Calendar, infoLog)
if err != nil {
errLog.Printf("Could not read calendar info from %s\n", conf.Calendar)
}
Expand Down
2 changes: 1 addition & 1 deletion internal/message/message.go
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ type TemplatedMessage struct {
txtTemplate *template.Template
}

func NewTemplatedMessage(htmlTemplate, txtTemplate string, events []calendar.EventData, tz *time.Location) (TemplatedMessage, error) {
func NewTemplatedMessage(htmlTemplate, txtTemplate string, events []calendar.Event, tz *time.Location) (TemplatedMessage, error) {
msg := TemplatedMessage{}
for _, evt := range events {
event := Event{
Expand Down
183 changes: 100 additions & 83 deletions pkg/calendar/calendar.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,17 +8,16 @@ import (
"time"

"github.com/emersion/go-ical"
// "github.com/teambition/rrule-go"
)

type Calendar struct {
url string
tz *time.Location
*ical.Calendar
*log.Logger
type IcalData struct {
url string
tz *time.Location
parsed *ical.Calendar
logger *log.Logger
}

type EventData struct {
type Event struct {
UID string
Start time.Time
End time.Time
Expand All @@ -27,146 +26,164 @@ type EventData struct {
Description string
}

func NewCalendar(url string, l *log.Logger) (Calendar, error) {
calendar := Calendar{url: url, tz: time.Local}
// Obtains an iCal from a given URL
func ImportCalendar(url string, l *log.Logger) (IcalData, error) {
data := IcalData{url: url, tz: time.Local, logger: l}

// Download the ICS file
resp, err := http.Get(url)
if err != nil {
return calendar, err
return data, err
}
defer resp.Body.Close()

// Parse the ICS file
parser := ical.NewDecoder(resp.Body)

cal, err := parser.Decode()
parsedData, err := parser.Decode()
if err != nil {
return calendar, err
return data, err
}
calendar.Calendar = cal
calendar.Logger = l
return calendar, nil

data.parsed = parsedData
return data, nil
}

func (cal Calendar) GetEventsOn(date time.Time) ([]EventData, error) {
events := make([]ical.Event, 0)
todayStart := GetDateWithoutTime(date)
// Assebles a list of all events on a given date
func (data IcalData) GetEventsOn(date time.Time) ([]Event, error) {
events := make([]Event, 0)
todayStart := time.Date(date.Year(), date.Month(), date.Day(), 0, 0, 0, 0, time.Local)
todayEnd := todayStart.Add(24 * time.Hour)
for _, event := range cal.Events() {
start, err := event.DateTimeStart(cal.tz)
if err != nil {
return []EventData{}, err
}
end, err := event.DateTimeEnd(cal.tz)

for _, e := range data.parsed.Events() {

start, err := e.DateTimeStart(data.tz)
if err != nil {
return []EventData{}, err
}
// regular event
if (start.After(todayStart) || start.Local() == todayStart.Local()) && start.Before(todayEnd) || (start.Before(todayStart) && end.After(todayEnd)) {
events = append(events, event)
continue
return nil, err
}
// recurring event
reccurenceSet, err := event.RecurrenceSet(cal.tz)

end, err := e.DateTimeEnd(data.tz)
if err != nil {
cal.Printf("could not get recurrence set: %s\n", err)
continue
}
if reccurenceSet == nil {
// no recurrence
continue
return nil, err
}
if GetDateWithoutTime(reccurenceSet.After(todayStart, true)).Local() == GetDateWithoutTime(date).Local() {

// Checks whether event happens on the given date
if data.isSingleEventOnDate(start, end, todayStart, todayEnd) ||
data.isRecurringEventOnDate(e, date) {

event, err := data.convertIcal(e, date)
if err != nil {
return nil, err
}
events = append(events, event)
}
}

// check for doubles via uid
uids := make(map[string]struct{})
dedupedEvents := make([]ical.Event, 0)
for _, e := range events {
uid := e.Props.Get(ical.PropUID).Value
if _, ok := uids[uid]; !ok {
dedupedEvents = append(dedupedEvents, e)
uids[uid] = struct{}{}
}
// Post-processing
removeDuplicates(&events)
sortEvents(events)

return events, nil
}

// Checks whether the event is a single, standard event on the given date
func (data IcalData) isSingleEventOnDate(start, end, todayStart, todayEnd time.Time) bool {
isRegularEvent := (start.After(todayStart) || start.Equal(todayStart)) && start.Before(todayEnd)
isSpanningEvent := start.Before(todayStart) && end.After(todayEnd)
return isRegularEvent || isSpanningEvent
}

// Checks whether an recurring event happens on a given date
func (data IcalData) isRecurringEventOnDate(event ical.Event, date time.Time) bool {
recurrenceSet, err := event.RecurrenceSet(data.tz)
if err != nil {
data.logger.Printf("could not get recurrence set: %s\n", err)
return false
}
if recurrenceSet == nil {
return false
}

// Convert ical.Events to EventData
eventDatas := make([]EventData, 0)
for _, event := range dedupedEvents {
eventData, err := cal.ConvertToEventData(event, date)
if err != nil {
return nil, err
todayStart := time.Date(date.Year(), date.Month(), date.Day(), 0, 0, 0, 0, time.Local)
recurrenceDate := time.Date(recurrenceSet.After(todayStart, true).Year(), recurrenceSet.After(todayStart, true).Month(), recurrenceSet.After(todayStart, true).Day(), 0, 0, 0, 0, time.Local)
return recurrenceDate.Equal(todayStart)
}

// Removes duplicate events from the list
func removeDuplicates(events *[]Event) {
uids := make(map[string]struct{})
dedupedEvents := make([]Event, 0)

for _, event := range *events {
uid := event.UID
if _, exists := uids[uid]; !exists {
dedupedEvents = append(dedupedEvents, event)
uids[uid] = struct{}{}
}
eventDatas = append(eventDatas, eventData)
}
*events = dedupedEvents
}

// sort events
sort.SliceStable(eventDatas, func(i, j int) bool {
start1 := eventDatas[i].Start
start2 := eventDatas[j].Start
return start1.Before(start2)
// Sorts events by start time
func sortEvents(events []Event) {
sort.SliceStable(events, func(i, j int) bool {
return events[i].Start.Before(events[j].Start)
})

return eventDatas, nil
}

func GetDateWithoutTime(date time.Time) time.Time {
return time.Date(date.Year(), date.Month(), date.Day(), 0, 0, 0, 0, time.Local)
}
func (cal Calendar) ConvertToEventData(icalEvent ical.Event, d time.Time) (EventData, error) {
eventData := EventData{}
// Builds the object that is used to represent an event
func (data IcalData) convertIcal(icalEvent ical.Event, d time.Time) (Event, error) {
event := Event{}

// Handle UID
uidProp := icalEvent.Props.Get(ical.PropUID)
if uidProp != nil {
eventData.UID = uidProp.Value
event.UID = uidProp.Value
} else {
return eventData, fmt.Errorf("UID is missing for event %s", icalEvent.Name)
return event, fmt.Errorf("UID is missing for event %s", icalEvent.Name)
}

// Handle DTSTART
startProp := icalEvent.Props.Get(ical.PropDateTimeStart)
if startProp == nil {
return eventData, fmt.Errorf("DTSTART is missing for event %s", icalEvent.Name)
return event, fmt.Errorf("DTSTART is missing for event %s", icalEvent.Name)
}
eventStart, err := startProp.DateTime(cal.tz)
eventStart, err := startProp.DateTime(data.tz)
if err != nil {
return eventData, err
return event, err
}
eventData.Start = time.Date(d.Year(), d.Month(), d.Day(), eventStart.Hour(), eventStart.Minute(), 0, 0, d.Location())
event.Start = time.Date(d.Year(), d.Month(), d.Day(), eventStart.Hour(), eventStart.Minute(), 0, 0, d.Location())

// Handle DTEND
endProp := icalEvent.Props.Get(ical.PropDateTimeEnd)
if endProp != nil {
eventEnd, err := endProp.DateTime(cal.tz)
eventEnd, err := endProp.DateTime(data.tz)
if err != nil {
return eventData, err
return event, err
}
// Calculate the difference in days and adjust eventData.End
// Calculate the difference in days and adjust Event.End
daysDiff := int(eventEnd.Sub(eventStart).Hours() / 24)
eventData.End = time.Date(d.Year(), d.Month(), d.Day()+daysDiff, eventEnd.Hour(), eventEnd.Minute(), 0, 0, d.Location())
event.End = time.Date(d.Year(), d.Month(), d.Day()+daysDiff, eventEnd.Hour(), eventEnd.Minute(), 0, 0, d.Location())
}

// Handle SUMMARY
summaryProp := icalEvent.Props.Get(ical.PropSummary)
if summaryProp != nil {
eventData.Summary = summaryProp.Value
event.Summary = summaryProp.Value
}

// Handle LOCATION
locationProp := icalEvent.Props.Get(ical.PropLocation)
if locationProp != nil {
eventData.Location = locationProp.Value
event.Location = locationProp.Value
}

// Handle DESCRIPTION
descriptionProp := icalEvent.Props.Get(ical.PropDescription)
if descriptionProp != nil {
eventData.Description = descriptionProp.Value
event.Description = descriptionProp.Value
} else {
eventData.Description = ""
event.Description = ""
}

return eventData, nil
return event, nil
}
30 changes: 15 additions & 15 deletions pkg/calendar/calendar_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,8 @@ const (
xHainDump_1 = "testData.txt"
)

func NewWantedCalendarEvent(uid string, summary string, start time.Time, end time.Time) EventData {
return EventData{
func NewWantedCalendarEvent(uid string, summary string, start time.Time, end time.Time) Event {
return Event{
UID: uid,
Summary: summary,
Start: start,
Expand All @@ -36,7 +36,7 @@ func TestCalendar_GetEventsOn(t *testing.T) {
name string
testDataFile io.ReadCloser
args args
want []EventData
want []Event
wantErr bool
}{
{
Expand All @@ -46,7 +46,7 @@ func TestCalendar_GetEventsOn(t *testing.T) {
date: time.Date(2023, 2, 23, 0, 0, 0, 0, time.Local),
},
wantErr: false,
want: []EventData{
want: []Event{
NewWantedCalendarEvent(
"dae1e4eb-7213-4620-bc9f-1bdb8a023af9",
"Workshop - Learn PCB design with KiCad",
Expand All @@ -62,7 +62,7 @@ func TestCalendar_GetEventsOn(t *testing.T) {
date: time.Date(2023, 3, 2, 0, 0, 0, 0, time.Local),
},
wantErr: false,
want: []EventData{
want: []Event{
NewWantedCalendarEvent(
"66adcfc4-6827-45a2-a5b4-655923d5dd62",
"How to use the latest AIs in your daily workflow - for... everything?",
Expand All @@ -78,7 +78,7 @@ func TestCalendar_GetEventsOn(t *testing.T) {
date: time.Date(2023, 3, 21, 0, 0, 0, 0, time.Local),
},
wantErr: false,
want: []EventData{
want: []Event{
NewWantedCalendarEvent(
"5fb7f276-54d6-4c30-a993-92cfe962e41b",
"Gespräch unter Bäumen (mit Elisa Filevich)",
Expand All @@ -94,7 +94,7 @@ func TestCalendar_GetEventsOn(t *testing.T) {
date: time.Date(2023, 2, 24, 0, 0, 0, 0, time.Local),
},
wantErr: false,
want: []EventData{
want: []Event{
NewWantedCalendarEvent(
"1ec26b84-60e1-437d-a455-db6404dff879",
"Drones' night",
Expand All @@ -110,7 +110,7 @@ func TestCalendar_GetEventsOn(t *testing.T) {
date: time.Date(2023, 2, 27, 0, 0, 0, 0, time.Local),
},
wantErr: false,
want: []EventData{
want: []Event{
NewWantedCalendarEvent(
"c9158eec-083a-4798-9860-99c4a83cce0f",
"offener Montag",
Expand All @@ -126,7 +126,7 @@ func TestCalendar_GetEventsOn(t *testing.T) {
date: time.Date(2023, 2, 8, 0, 0, 0, 0, time.Local),
},
wantErr: false,
want: []EventData{
want: []Event{
NewWantedCalendarEvent(
"3591c731-0e27-4902-9ae0-8748d46841f3",
"XMPP-Meetup",
Expand All @@ -148,7 +148,7 @@ func TestCalendar_GetEventsOn(t *testing.T) {
date: time.Date(2023, 1, 22, 0, 0, 0, 0, time.Local),
},
wantErr: false,
want: []EventData{
want: []Event{
NewWantedCalendarEvent(
"290b69b7-aaaf-47d4-88d1-d42366e36163",
"Kindernachmittag",
Expand All @@ -173,11 +173,11 @@ func TestCalendar_GetEventsOn(t *testing.T) {
t.Fatalf("could not open test data file: %s", tt.testDataFile)
}

cal := Calendar{
url: "file",
tz: time.Local,
Logger: log.New(os.Stdout, "[TEST] ", log.Ldate|log.Ltime|log.Lmsgprefix|log.Lshortfile),
Calendar: calendar,
cal := IcalData{
url: "file",
tz: time.Local,
logger: log.New(os.Stdout, "[TEST] ", log.Ldate|log.Ltime|log.Lmsgprefix|log.Lshortfile),
parsed: calendar,
}
got, err := cal.GetEventsOn(tt.args.date)
if (err != nil) != tt.wantErr {
Expand Down

0 comments on commit 8bd8760

Please sign in to comment.