feat(tasks): due dates with optional time; alarms at the right moment

Due dates can carry a time (YYYY-MM-DDTHH:MM). Alarms fire at that time,
or 09:00 for all-day tasks, minus the user's lead time, in TZ (default
Europe/Berlin, tzdata embedded). A new due date or reopening a task
re-arms the alarm. Recurrence keeps the time; Markdown export/import
carry it. The UI shows relative labels ("Heute, 14:30"), marks overdue
tasks to the minute and groups by local day instead of the UTC date.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
sandrodachandClaude Opus 5.5 committed 2026-10-29 07:48:37 +01:00
1 parent 300fd6a7be
commit 344f5e4ab2
21 files changed
+391 -50

No files matched your search

+10 -5
View File
@@ -18,12 +18,14 @@ func validRecurrence(r string) bool {
return false
}
// NextDueDate moves a due date (YYYY-MM-DD, or today when empty) one interval forward. Months and years
// keep the day where possible and fall back to the month's last day (31 Jan -> 28/29 Feb).
// NextDueDate moves a due date (YYYY-MM-DD or YYYY-MM-DDTHH:MM, today when empty) one interval forward
// and keeps the time. Months and years keep the day where possible and fall back to the month's last
// day (31 Jan -> 28/29 Feb).
func NextDueDate(due, rule string, today time.Time) string {
base, err := time.Parse("2006-01-02", due)
if err != nil {
base = time.Date(today.Year(), today.Month(), today.Day(), 0, 0, 0, 0, time.UTC)
parsed, hasTime, ok := domain.ParseDue(due, time.UTC)
base := time.Date(today.Year(), today.Month(), today.Day(), 0, 0, 0, 0, time.UTC)
if ok {
base = time.Date(parsed.Year(), parsed.Month(), parsed.Day(), 0, 0, 0, 0, time.UTC)
}
switch rule {
case "daily":
@@ -35,6 +37,9 @@ func NextDueDate(due, rule string, today time.Time) string {
case "yearly":
base = addMonthsClamped(base, 12)
}
if hasTime {
return base.Format("2006-01-02") + parsed.Format("T15:04")
}
return base.Format("2006-01-02")
}