From c5327845ee54ec908483a9de8007c5c570abe627 Mon Sep 17 00:00:00 2001 From: kolaente Date: Sat, 24 Dec 2022 12:19:51 +0100 Subject: [PATCH] feat(caldav): add support for repeating tasks Resolves https://github.com/go-vikunja/api/issues/57#issuecomment-1364373103 --- pkg/caldav/caldav.go | 20 ++++++++--- pkg/caldav/caldav_test.go | 71 +++++++++++++++++++++++++++++++++++++++ pkg/caldav/parsing.go | 21 ++++++++---- pkg/models/tasks.go | 2 +- 4 files changed, 102 insertions(+), 12 deletions(-) diff --git a/pkg/caldav/caldav.go b/pkg/caldav/caldav.go index 7d7b55df81c..998cc27c14c 100644 --- a/pkg/caldav/caldav.go +++ b/pkg/caldav/caldav.go @@ -57,10 +57,12 @@ type Todo struct { RelatedToUID string Color string - Start time.Time - End time.Time - DueDate time.Time - Duration time.Duration + Start time.Time + End time.Time + DueDate time.Time + Duration time.Duration + RepeatAfter int64 + RepeatMode string Created time.Time Updated time.Time // last-mod @@ -225,6 +227,16 @@ CREATED:` + makeCalDavTimeFromTimeStamp(t.Created) PRIORITY:` + strconv.Itoa(mapPriorityToCaldav(t.Priority)) } + if t.RepeatAfter > 0 || t.RepeatMode == "MONTHLY" { + if t.RepeatMode == "MONTHLY" { + caldavtodos += ` +RRULE:FREQ=MONTHLY;BYMONTHDAY=` + t.DueDate.Format("02") // Day of the month + } else { + caldavtodos += ` +RRULE:FREQ=SECONDLY;INTERVAL=` + strconv.FormatInt(t.RepeatAfter, 10) + } + } + caldavtodos += ` LAST-MODIFIED:` + makeCalDavTimeFromTimeStamp(t.Updated) diff --git a/pkg/caldav/caldav_test.go b/pkg/caldav/caldav_test.go index 5701d0fd73c..3e9cfee7e52 100644 --- a/pkg/caldav/caldav_test.go +++ b/pkg/caldav/caldav_test.go @@ -408,6 +408,77 @@ DESCRIPTION:Lorem Ipsum PRIORITY:9 LAST-MODIFIED:00010101T000000Z END:VTODO +END:VCALENDAR`, + }, + { + name: "with repeating monthly", + args: args{ + config: &Config{ + Name: "test", + ProdID: "RandomProdID which is not random", + }, + todos: []*Todo{ + { + Summary: "Todo #1", + Description: "Lorem Ipsum", + UID: "randommduid", + Timestamp: time.Unix(1543626724, 0).In(config.GetTimeZone()), + RepeatMode: "MONTHLY", + DueDate: time.Unix(1543626724, 0).In(config.GetTimeZone()), + }, + }, + }, + wantCaldavtasks: `BEGIN:VCALENDAR +VERSION:2.0 +METHOD:PUBLISH +X-PUBLISHED-TTL:PT4H +X-WR-CALNAME:test +PRODID:-//RandomProdID which is not random//EN +BEGIN:VTODO +UID:randommduid +DTSTAMP:20181201T011204Z +SUMMARY:Todo #1 +DESCRIPTION:Lorem Ipsum +DUE:20181201T011204Z +RRULE:FREQ=MONTHLY;BYMONTHDAY=01 +LAST-MODIFIED:00010101T000000Z +END:VTODO +END:VCALENDAR`, + }, + { + name: "with repeat mode default", + args: args{ + config: &Config{ + Name: "test", + ProdID: "RandomProdID which is not random", + }, + todos: []*Todo{ + { + Summary: "Todo #1", + Description: "Lorem Ipsum", + UID: "randommduid", + Timestamp: time.Unix(1543626724, 0).In(config.GetTimeZone()), + RepeatMode: "DEFAULT", + DueDate: time.Unix(1543626724, 0).In(config.GetTimeZone()), + RepeatAfter: 435, + }, + }, + }, + wantCaldavtasks: `BEGIN:VCALENDAR +VERSION:2.0 +METHOD:PUBLISH +X-PUBLISHED-TTL:PT4H +X-WR-CALNAME:test +PRODID:-//RandomProdID which is not random//EN +BEGIN:VTODO +UID:randommduid +DTSTAMP:20181201T011204Z +SUMMARY:Todo #1 +DESCRIPTION:Lorem Ipsum +DUE:20181201T011204Z +RRULE:FREQ=SECONDLY;INTERVAL=435 +LAST-MODIFIED:00010101T000000Z +END:VTODO END:VCALENDAR`, }, } diff --git a/pkg/caldav/parsing.go b/pkg/caldav/parsing.go index 2136ad2a20c..9c7463d081d 100644 --- a/pkg/caldav/parsing.go +++ b/pkg/caldav/parsing.go @@ -35,6 +35,11 @@ func GetCaldavTodosForTasks(list *models.ListWithTasksAndBuckets, listTasks []*m duration := t.EndDate.Sub(t.StartDate) + repeatMode := "DEFAULT" + if t.RepeatMode == models.TaskRepeatModeMonth { + repeatMode = "MONTHLY" + } + caldavtodos = append(caldavtodos, &Todo{ Timestamp: t.Updated, UID: t.UID, @@ -42,13 +47,15 @@ func GetCaldavTodosForTasks(list *models.ListWithTasksAndBuckets, listTasks []*m Description: t.Description, Completed: t.DoneAt, // Organizer: &t.CreatedBy, // Disabled until we figure out how this works - Priority: t.Priority, - Start: t.StartDate, - End: t.EndDate, - Created: t.Created, - Updated: t.Updated, - DueDate: t.DueDate, - Duration: duration, + Priority: t.Priority, + Start: t.StartDate, + End: t.EndDate, + Created: t.Created, + Updated: t.Updated, + DueDate: t.DueDate, + Duration: duration, + RepeatAfter: t.RepeatAfter, + RepeatMode: repeatMode, }) } diff --git a/pkg/models/tasks.go b/pkg/models/tasks.go index e86e7d6acf7..8d3ed0fdc6d 100644 --- a/pkg/models/tasks.go +++ b/pkg/models/tasks.go @@ -1369,7 +1369,7 @@ func setTaskDatesFromCurrentDateRepeat(oldTask, newTask *Task) { // This helper function updates the reminders, doneAt, start and end dates of the *old* task // and saves the new values in the newTask object. -// We make a few assumtions here: +// We make a few assumptions here: // 1. Everything in oldTask is the truth - we figure out if we update anything at all if oldTask.RepeatAfter has a value > 0 // 2. Because of 1., this functions should not be used to update values other than Done in the same go func updateDone(oldTask *Task, newTask *Task) {