Skip to content

Bug: relative dates use elapsed time instead of calendar days #101

@zengen-dermot

Description

@zengen-dermot

Bug

When displaying due dates with relative formatting, the tool calculates based on elapsed time rather than calendar day boundaries. This causes confusing output where a task due in 2 calendar days shows as "in 1 day".

Example

  • Now: Thursday, January 8, 2026 at 9:00am
  • Task due: Saturday, January 10, 2026 at 8:00am

Expected: "in 2 days" (Thursday → Friday → Saturday)
Actual: "in 1 day" (~47 hours elapsed ÷ 24 ≈ 1.9, displayed as 1)

Reproduction

# On Thursday morning:
reminders add To-Dos "Saturday morning meeting" --due-date "2026-01-10 08:00"
reminders show To-Dos
# Shows "in 1 day" instead of "in 2 days"

Location

Sources/RemindersLibrary/Reminders.swift, lines 7-11:

private let dateFormatter = RelativeDateTimeFormatter()
private func formattedDueDate(from reminder: EKReminder) -> String? {
    return reminder.dueDateComponents?.date.map {
        dateFormatter.localizedString(for: $0, relativeTo: Date())
    }
}

RelativeDateTimeFormatter.localizedString(for:relativeTo:) uses elapsed time rather than calendar day boundaries.

Suggested fix

Use calendar day comparison instead:

private func formattedDueDate(from reminder: EKReminder) -> String? {
    guard let dueDate = reminder.dueDateComponents?.date else { return nil }
    let calendar = Calendar.current
    let today = calendar.startOfDay(for: Date())
    let dueDay = calendar.startOfDay(for: dueDate)
    let days = calendar.dateComponents([.day], from: today, to: dueDay).day ?? 0

    switch days {
    case ..<0: return "\(abs(days)) day\(abs(days) == 1 ? "" : "s") ago"
    case 0: return "today"
    case 1: return "tomorrow"
    default: return "in \(days) days"
    }
}

This matches how users think about dates: "due in 2 days" means the day after tomorrow, regardless of the specific hour.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions