From efdc675fa7fa9bcad84e3395f81f3f734abfa9e7 Mon Sep 17 00:00:00 2001 From: TowyTowy <85077986+TowyTowy@users.noreply.github.com> Date: Sat, 11 Jul 2026 17:28:59 +0200 Subject: [PATCH] fix(caldav): compute VTODO DURATION components correctly (#3155) --- pkg/caldav/caldav.go | 12 +++++++----- pkg/caldav/caldav_test.go | 20 ++++++++++++++++++++ 2 files changed, 27 insertions(+), 5 deletions(-) diff --git a/pkg/caldav/caldav.go b/pkg/caldav/caldav.go index a624d1bc2..c4be562a9 100644 --- a/pkg/caldav/caldav.go +++ b/pkg/caldav/caldav.go @@ -119,12 +119,14 @@ COLOR:` + color } func formatDuration(duration time.Duration) string { - seconds := duration.Seconds() - duration.Minutes()*60 - minutes := duration.Minutes() - duration.Hours()*60 + seconds := int64(duration.Seconds()) + hours := seconds / 3600 + minutes := (seconds % 3600) / 60 + seconds %= 60 - return strconv.FormatFloat(duration.Hours(), 'f', 0, 64) + `H` + - strconv.FormatFloat(minutes, 'f', 0, 64) + `M` + - strconv.FormatFloat(seconds, 'f', 0, 64) + `S` + return strconv.FormatInt(hours, 10) + `H` + + strconv.FormatInt(minutes, 10) + `M` + + strconv.FormatInt(seconds, 10) + `S` } func getRruleFromInterval(interval int64) (freq string, newInterval int64) { diff --git a/pkg/caldav/caldav_test.go b/pkg/caldav/caldav_test.go index 91c851c7c..920b1e003 100644 --- a/pkg/caldav/caldav_test.go +++ b/pkg/caldav/caldav_test.go @@ -526,6 +526,26 @@ func TestGetCaldavColor(t *testing.T) { } } +func Test_formatDuration(t *testing.T) { + tests := []struct { + name string + in time.Duration + want string + }{ + {"whole hours", time.Hour, "1H0M0S"}, + {"hours and minutes", time.Hour + 30*time.Minute, "1H30M0S"}, + {"minutes only", 30 * time.Minute, "0H30M0S"}, + {"minutes only, not a round hour", 45 * time.Minute, "0H45M0S"}, + {"hours, minutes and seconds", 2*time.Hour + 15*time.Minute + 30*time.Second, "2H15M30S"}, + {"seconds only", 30 * time.Second, "0H0M30S"}, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + assert.Equal(t, tt.want, formatDuration(tt.in)) + }) + } +} + func TestEscapeICalText(t *testing.T) { tests := []struct { name string