fix(caldav): Incoming tasks do not get correct time zone (#1455)
Dates from tasks.org may be formatted like DUE;TZID=Europe/Berlin:20230402T150000 After this fix the parameter TZID is no longer ignored and the Vikunja task gets a DueDate of 13:00 UTC, which corresponds to 15:00 in Europe/Berlin. Before this fix, the time was parsed to 15:00 UTC. Resolves https://kolaente.dev/vikunja/api/issues/1453 Co-authored-by: ce72 <christoph.ernst72@googlemail.com> Reviewed-on: https://kolaente.dev/vikunja/api/pulls/1455 Reviewed-by: konrad <k@knt.li> Co-authored-by: cernst <ce72@noreply.kolaente.de> Co-committed-by: cernst <ce72@noreply.kolaente.de>
This commit is contained in:
parent
f45648a6f7
commit
1cffef6908
@ -22,6 +22,7 @@ import (
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"code.vikunja.io/api/pkg/config"
|
||||
"code.vikunja.io/api/pkg/log"
|
||||
"code.vikunja.io/api/pkg/models"
|
||||
"code.vikunja.io/api/pkg/utils"
|
||||
@ -88,15 +89,15 @@ func ParseTaskFromVTODO(content string) (vTask *models.Task, err error) {
|
||||
return nil, errors.New("VTODO element not found")
|
||||
}
|
||||
// We put the vTodo details in a map to be able to handle them more easily
|
||||
task := make(map[string]string)
|
||||
task := make(map[string]ics.IANAProperty)
|
||||
for _, c := range vTodo.UnknownPropertiesIANAProperties() {
|
||||
task[c.IANAToken] = c.Value
|
||||
task[c.IANAToken] = c
|
||||
}
|
||||
|
||||
// Parse the priority
|
||||
var priority int64
|
||||
if _, ok := task["PRIORITY"]; ok {
|
||||
priorityParsed, err := strconv.ParseInt(task["PRIORITY"], 10, 64)
|
||||
priorityParsed, err := strconv.ParseInt(task["PRIORITY"].Value, 10, 64)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@ -105,14 +106,14 @@ func ParseTaskFromVTODO(content string) (vTask *models.Task, err error) {
|
||||
}
|
||||
|
||||
// Parse the enddate
|
||||
duration, _ := time.ParseDuration(task["DURATION"])
|
||||
duration, _ := time.ParseDuration(task["DURATION"].Value)
|
||||
|
||||
description := strings.ReplaceAll(task["DESCRIPTION"], "\\,", ",")
|
||||
description := strings.ReplaceAll(task["DESCRIPTION"].Value, "\\,", ",")
|
||||
description = strings.ReplaceAll(description, "\\n", "\n")
|
||||
|
||||
var labels []*models.Label
|
||||
if val, ok := task["CATEGORIES"]; ok {
|
||||
categories := strings.Split(val, ",")
|
||||
categories := strings.Split(val.Value, ",")
|
||||
labels = make([]*models.Label, 0, len(categories))
|
||||
for _, category := range categories {
|
||||
labels = append(labels, &models.Label{
|
||||
@ -122,8 +123,8 @@ func ParseTaskFromVTODO(content string) (vTask *models.Task, err error) {
|
||||
}
|
||||
|
||||
vTask = &models.Task{
|
||||
UID: task["UID"],
|
||||
Title: task["SUMMARY"],
|
||||
UID: task["UID"].Value,
|
||||
Title: task["SUMMARY"].Value,
|
||||
Description: description,
|
||||
Priority: priority,
|
||||
Labels: labels,
|
||||
@ -133,7 +134,7 @@ func ParseTaskFromVTODO(content string) (vTask *models.Task, err error) {
|
||||
DoneAt: caldavTimeToTimestamp(task["COMPLETED"]),
|
||||
}
|
||||
|
||||
if task["STATUS"] == "COMPLETED" {
|
||||
if task["STATUS"].Value == "COMPLETED" {
|
||||
vTask.Done = true
|
||||
}
|
||||
|
||||
@ -159,7 +160,7 @@ func parseVAlarm(vAlarm *ics.VAlarm, vTask *models.Task) *models.Task {
|
||||
if contains(property.ICalParameters["VALUE"], "DATE-TIME") {
|
||||
// Example: TRIGGER;VALUE=DATE-TIME:20181201T011210Z
|
||||
vTask.Reminders = append(vTask.Reminders, &models.TaskReminder{
|
||||
Reminder: caldavTimeToTimestamp(property.Value),
|
||||
Reminder: caldavTimeToTimestamp(property),
|
||||
})
|
||||
continue
|
||||
}
|
||||
@ -199,7 +200,8 @@ func contains(array []string, str string) bool {
|
||||
}
|
||||
|
||||
// https://tools.ietf.org/html/rfc5545#section-3.3.5
|
||||
func caldavTimeToTimestamp(tstring string) time.Time {
|
||||
func caldavTimeToTimestamp(ianaProperty ics.IANAProperty) time.Time {
|
||||
tstring := ianaProperty.Value
|
||||
if tstring == "" {
|
||||
return time.Time{}
|
||||
}
|
||||
@ -214,7 +216,24 @@ func caldavTimeToTimestamp(tstring string) time.Time {
|
||||
format = `20060102`
|
||||
}
|
||||
|
||||
t, err := time.Parse(format, tstring)
|
||||
var t time.Time
|
||||
var err error
|
||||
tzParameter := ianaProperty.ICalParameters["TZID"]
|
||||
if len(tzParameter) > 0 {
|
||||
loc, err := time.LoadLocation(tzParameter[0])
|
||||
if err != nil {
|
||||
log.Warningf("Error while parsing caldav timezone %s: %s", tzParameter[0], err)
|
||||
} else {
|
||||
t, err = time.ParseInLocation(format, tstring, loc)
|
||||
if err != nil {
|
||||
log.Warningf("Error while parsing caldav time %s to TimeStamp: %s at location %s", tstring, loc, err)
|
||||
} else {
|
||||
t = t.In(config.GetTimeZone())
|
||||
return t
|
||||
}
|
||||
}
|
||||
}
|
||||
t, err = time.Parse(format, tstring)
|
||||
if err != nil {
|
||||
log.Warningf("Error while parsing caldav time %s to TimeStamp: %s", tstring, err)
|
||||
return time.Time{}
|
||||
|
@ -219,6 +219,76 @@ END:VCALENDAR`,
|
||||
Updated: time.Unix(1543626724, 0).In(config.GetTimeZone()),
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "example task from tasks.org app",
|
||||
args: args{content: `BEGIN:VCALENDAR
|
||||
VERSION:2.0
|
||||
PRODID:+//IDN tasks.org//android-130102//EN
|
||||
BEGIN:VTODO
|
||||
DTSTAMP:20230402T074158Z
|
||||
UID:4290517349243274514
|
||||
CREATED:20230402T060451Z
|
||||
LAST-MODIFIED:20230402T074154Z
|
||||
SUMMARY:Test with tasks.org
|
||||
PRIORITY:9
|
||||
CATEGORIES:Vikunja
|
||||
X-APPLE-SORT-ORDER:697384109
|
||||
DUE;TZID=Europe/Berlin:20230402T170001
|
||||
DTSTART;TZID=Europe/Berlin:20230401T090000
|
||||
BEGIN:VALARM
|
||||
TRIGGER;RELATED=END:PT0S
|
||||
ACTION:DISPLAY
|
||||
DESCRIPTION:Default Tasks.org description
|
||||
END:VALARM
|
||||
BEGIN:VALARM
|
||||
TRIGGER;VALUE=DATE-TIME:20230402T100000Z
|
||||
ACTION:DISPLAY
|
||||
DESCRIPTION:Default Tasks.org description
|
||||
END:VALARM
|
||||
END:VTODO
|
||||
BEGIN:VTIMEZONE
|
||||
TZID:Europe/Berlin
|
||||
LAST-MODIFIED:20220816T024022Z
|
||||
BEGIN:DAYLIGHT
|
||||
TZNAME:CEST
|
||||
TZOFFSETFROM:+0100
|
||||
TZOFFSETTO:+0200
|
||||
DTSTART:19810329T020000
|
||||
RRULE:FREQ=YEARLY;BYMONTH=3;BYDAY=-1SU
|
||||
END:DAYLIGHT
|
||||
BEGIN:STANDARD
|
||||
TZNAME:CET
|
||||
TZOFFSETFROM:+0200
|
||||
TZOFFSETTO:+0100
|
||||
DTSTART:19961027T030000
|
||||
RRULE:FREQ=YEARLY;BYMONTH=10;BYDAY=-1SU
|
||||
END:STANDARD
|
||||
END:VTIMEZONE
|
||||
END:VCALENDAR`,
|
||||
},
|
||||
wantVTask: &models.Task{
|
||||
Updated: time.Date(2023, 4, 2, 7, 41, 58, 0, config.GetTimeZone()),
|
||||
UID: "4290517349243274514",
|
||||
Title: "Test with tasks.org",
|
||||
Priority: 1,
|
||||
Labels: []*models.Label{
|
||||
{
|
||||
Title: "Vikunja",
|
||||
},
|
||||
},
|
||||
DueDate: time.Date(2023, 4, 2, 15, 0, 1, 0, config.GetTimeZone()),
|
||||
StartDate: time.Date(2023, 4, 1, 7, 0, 0, 0, config.GetTimeZone()),
|
||||
Reminders: []*models.TaskReminder{
|
||||
{
|
||||
RelativeTo: models.ReminderRelationDueDate,
|
||||
RelativePeriod: 0,
|
||||
},
|
||||
{
|
||||
Reminder: time.Date(2023, 4, 2, 10, 0, 0, 0, config.GetTimeZone()),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
|
Loading…
x
Reference in New Issue
Block a user