diff --git a/pkg/caldav/caldav.go b/pkg/caldav/caldav.go
index 830c73f94..309997538 100644
--- a/pkg/caldav/caldav.go
+++ b/pkg/caldav/caldav.go
@@ -62,7 +62,7 @@ type Todo struct {
type Alarm struct {
Time time.Time
Duration time.Duration
- RelativeTo string
+ RelativeTo models.ReminderRelation
Description string
}
@@ -204,13 +204,13 @@ func ParseAlarms(alarms []Alarm, taskDescription string) (caldavalarms string) {
caldavalarms += `
BEGIN:VALARM`
switch a.RelativeTo {
- case "due_date":
+ case models.ReminderRelationDueDate:
caldavalarms += `
TRIGGER:` + makeCalDavDuration(a.Duration)
- case "start_date":
+ case models.ReminderRelationStartDate:
caldavalarms += `
TRIGGER;RELATED=START:` + makeCalDavDuration(a.Duration)
- case "end_date":
+ case models.ReminderRelationEndDate:
caldavalarms += `
TRIGGER;RELATED=END:` + makeCalDavDuration(a.Duration)
default:
diff --git a/pkg/caldav/parsing.go b/pkg/caldav/parsing.go
index 38a4d0238..004d50cbb 100644
--- a/pkg/caldav/parsing.go
+++ b/pkg/caldav/parsing.go
@@ -18,13 +18,13 @@ package caldav
import (
"errors"
- "regexp"
"strconv"
"strings"
"time"
"code.vikunja.io/api/pkg/log"
"code.vikunja.io/api/pkg/models"
+ "code.vikunja.io/api/pkg/utils"
ics "github.com/arran4/golang-ical"
)
@@ -45,7 +45,7 @@ func GetCaldavTodosForTasks(project *models.ProjectWithTasksAndBuckets, projectT
alarms = append(alarms, Alarm{
Time: reminder.Reminder,
Duration: time.Duration(reminder.RelativePeriod) * time.Second,
- RelativeTo: string(reminder.RelativeTo),
+ RelativeTo: reminder.RelativeTo,
})
}
@@ -165,7 +165,7 @@ func parseVAlarm(vAlarm *ics.VAlarm, reminders []*models.TaskReminder) []*models
Reminder: caldavTimeToTimestamp(property.Value)})
}
case len(property.ICalParameters["RELATED"]) > 0:
- duration := parseDuration(property.Value)
+ duration := utils.ParseISO8601Duration(property.Value)
switch property.ICalParameters["RELATED"][0] {
case "START":
// Example: TRIGGER;RELATED=START:-P2D
@@ -179,7 +179,7 @@ func parseVAlarm(vAlarm *ics.VAlarm, reminders []*models.TaskReminder) []*models
RelativeTo: models.ReminderRelationEndDate})
}
default:
- duration := parseDuration(property.Value)
+ duration := utils.ParseISO8601Duration(property.Value)
// Example: TRIGGER:-PT60M
reminders = append(reminders, &models.TaskReminder{
RelativePeriod: int64(duration.Seconds()),
@@ -213,37 +213,3 @@ func caldavTimeToTimestamp(tstring string) time.Time {
}
return t
}
-
-var durationRegex = regexp.MustCompile(`([-+])?P([\d\.]+Y)?([\d\.]+M)?([\d\.]+D)?T?([\d\.]+H)?([\d\.]+M)?([\d\.]+?S)?`)
-
-// ParseDuration converts a ISO8601 duration into a time.Duration
-func parseDuration(str string) time.Duration {
- matches := durationRegex.FindStringSubmatch(str)
-
- if len(matches) == 0 {
- return 0
- }
-
- years := parseDurationPart(matches[2], time.Hour*24*365)
- months := parseDurationPart(matches[3], time.Hour*24*30)
- days := parseDurationPart(matches[4], time.Hour*24)
- hours := parseDurationPart(matches[5], time.Hour)
- minutes := parseDurationPart(matches[6], time.Second*60)
- seconds := parseDurationPart(matches[7], time.Second)
-
- duration := years + months + days + hours + minutes + seconds
-
- if matches[1] == "-" {
- return -duration
- }
- return duration
-}
-
-func parseDurationPart(value string, unit time.Duration) time.Duration {
- if len(value) != 0 {
- if parsed, err := strconv.ParseFloat(value[:len(value)-1], 64); err == nil {
- return time.Duration(float64(unit) * parsed)
- }
- }
- return 0
-}
diff --git a/pkg/modules/migration/ticktick/ticktick.go b/pkg/modules/migration/ticktick/ticktick.go
index c31c8817a..861408d77 100644
--- a/pkg/modules/migration/ticktick/ticktick.go
+++ b/pkg/modules/migration/ticktick/ticktick.go
@@ -20,9 +20,7 @@ import (
"encoding/csv"
"errors"
"io"
- "regexp"
"sort"
- "strconv"
"strings"
"time"
@@ -30,6 +28,7 @@ import (
"code.vikunja.io/api/pkg/models"
"code.vikunja.io/api/pkg/modules/migration"
"code.vikunja.io/api/pkg/user"
+ "code.vikunja.io/api/pkg/utils"
"github.com/gocarina/gocsv"
)
@@ -75,36 +74,6 @@ func (date *tickTickTime) UnmarshalCSV(csv string) (err error) {
return err
}
-// Copied from https://stackoverflow.com/a/57617885
-var durationRegex = regexp.MustCompile(`P([\d\.]+Y)?([\d\.]+M)?([\d\.]+D)?T?([\d\.]+H)?([\d\.]+M)?([\d\.]+?S)?`)
-
-// ParseDuration converts a ISO8601 duration into a time.Duration
-func parseDuration(str string) time.Duration {
- matches := durationRegex.FindStringSubmatch(str)
-
- if len(matches) == 0 {
- return 0
- }
-
- years := parseDurationPart(matches[1], time.Hour*24*365)
- months := parseDurationPart(matches[2], time.Hour*24*30)
- days := parseDurationPart(matches[3], time.Hour*24)
- hours := parseDurationPart(matches[4], time.Hour)
- minutes := parseDurationPart(matches[5], time.Second*60)
- seconds := parseDurationPart(matches[6], time.Second)
-
- return years + months + days + hours + minutes + seconds
-}
-
-func parseDurationPart(value string, unit time.Duration) time.Duration {
- if len(value) != 0 {
- if parsed, err := strconv.ParseFloat(value[:len(value)-1], 64); err == nil {
- return time.Duration(float64(unit) * parsed)
- }
- }
- return 0
-}
-
func convertTickTickToVikunja(tasks []*tickTickTask) (result []*models.NamespaceWithProjectsAndTasks) {
namespace := &models.NamespaceWithProjectsAndTasks{
Namespace: models.Namespace{
@@ -231,7 +200,7 @@ func (m *Migrator) Migrate(user *user.User, file io.ReaderAt, size int64) error
task.IsChecklist = true
}
- reminder := parseDuration(task.ReminderDuration)
+ reminder := utils.ParseISO8601Duration(task.ReminderDuration)
if reminder > 0 {
task.Reminder = reminder
}
diff --git a/pkg/utils/duration.go b/pkg/utils/duration.go
new file mode 100644
index 000000000..de394497f
--- /dev/null
+++ b/pkg/utils/duration.go
@@ -0,0 +1,57 @@
+// Vikunja is a to-do list application to facilitate your life.
+// Copyright 2018-2021 Vikunja and contributors. All rights reserved.
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU Affero General Public Licensee as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU Affero General Public Licensee for more details.
+//
+// You should have received a copy of the GNU Affero General Public Licensee
+// along with this program. If not, see .
+
+package utils
+
+import (
+ "regexp"
+ "strconv"
+ "time"
+)
+
+// ParseISO8601Duration converts a ISO8601 duration into a time.Duration
+func ParseISO8601Duration(str string) time.Duration {
+ matches := durationRegex.FindStringSubmatch(str)
+
+ if len(matches) == 0 {
+ return 0
+ }
+
+ years := parseDurationPart(matches[2], time.Hour*24*365)
+ months := parseDurationPart(matches[3], time.Hour*24*30)
+ days := parseDurationPart(matches[4], time.Hour*24)
+ hours := parseDurationPart(matches[5], time.Hour)
+ minutes := parseDurationPart(matches[6], time.Second*60)
+ seconds := parseDurationPart(matches[7], time.Second)
+
+ duration := years + months + days + hours + minutes + seconds
+
+ if matches[1] == "-" {
+ return -duration
+ }
+ return duration
+}
+
+var durationRegex = regexp.MustCompile(`([-+])?P([\d\.]+Y)?([\d\.]+M)?([\d\.]+D)?T?([\d\.]+H)?([\d\.]+M)?([\d\.]+?S)?`)
+
+func parseDurationPart(value string, unit time.Duration) time.Duration {
+ if len(value) != 0 {
+ if parsed, err := strconv.ParseFloat(value[:len(value)-1], 64); err == nil {
+ return time.Duration(float64(unit) * parsed)
+ }
+ }
+ return 0
+}
diff --git a/pkg/utils/duration_test.go b/pkg/utils/duration_test.go
new file mode 100644
index 000000000..1af2e905d
--- /dev/null
+++ b/pkg/utils/duration_test.go
@@ -0,0 +1,39 @@
+// Vikunja is a to-do list application to facilitate your life.
+// Copyright 2018-2021 Vikunja and contributors. All rights reserved.
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU Affero General Public Licensee as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU Affero General Public Licensee for more details.
+//
+// You should have received a copy of the GNU Affero General Public Licensee
+// along with this program. If not, see .
+
+package utils
+
+import (
+ "testing"
+ "time"
+
+ "github.com/stretchr/testify/assert"
+)
+
+func TestParseISO8601Duration(t *testing.T) {
+ t.Run("full example", func(t *testing.T) {
+ dur := ParseISO8601Duration("P1DT1H1M1S")
+ expected, _ := time.ParseDuration("25h1m1s")
+
+ assert.Equal(t, expected, dur)
+ })
+ t.Run("negative duration", func(t *testing.T) {
+ dur := ParseISO8601Duration("-P1DT1H1M1S")
+ expected, _ := time.ParseDuration("-25h1m1s")
+
+ assert.Equal(t, expected, dur)
+ })
+}