Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions Sources/RemindCore/DateParsing.swift
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,15 @@ public enum DateParsing {
return formatter.string(from: date)
}

public static func formatDisplayAllDay(_ date: Date, calendar: Calendar = .current) -> String {
let formatter = DateFormatter()
formatter.locale = Locale.current
formatter.timeZone = calendar.timeZone
formatter.dateStyle = .medium
formatter.timeStyle = .none
return formatter.string(from: date)
}

private static func parseRelativeDate(_ input: String, now: Date, calendar: Calendar) -> Date? {
switch input {
case "today":
Expand Down
48 changes: 38 additions & 10 deletions Sources/RemindCore/EventKitStore.swift
Original file line number Diff line number Diff line change
Expand Up @@ -101,7 +101,7 @@ public actor RemindersStore {
reminder.calendar = calendar
reminder.priority = draft.priority.eventKitValue
if let dueDate = draft.dueDate {
reminder.dueDateComponents = calendarComponents(from: dueDate)
reminder.dueDateComponents = calendarComponents(from: dueDate, isAllDay: draft.isAllDay)
}
try eventStore.save(reminder, commit: true)
return ReminderItem(
Expand All @@ -113,7 +113,8 @@ public actor RemindersStore {
priority: ReminderPriority(eventKitValue: Int(reminder.priority)),
dueDate: date(from: reminder.dueDateComponents),
listID: reminder.calendar.calendarIdentifier,
listName: reminder.calendar.title
listName: reminder.calendar.title,
isAllDay: isAllDay(components: reminder.dueDateComponents)
)
}

Expand All @@ -128,10 +129,16 @@ public actor RemindersStore {
}
if let dueDateUpdate = update.dueDate {
if let dueDate = dueDateUpdate {
reminder.dueDateComponents = calendarComponents(from: dueDate)
let isAllDay = update.isAllDay ?? false
reminder.dueDateComponents = calendarComponents(from: dueDate, isAllDay: isAllDay)
} else {
reminder.dueDateComponents = nil
}
} else if let isAllDay = update.isAllDay, let existingComponents = reminder.dueDateComponents {
// Update existing due date to change all-day status
if let existingDate = calendar.date(from: existingComponents) {
reminder.dueDateComponents = calendarComponents(from: existingDate, isAllDay: isAllDay)
}
}
if let priority = update.priority {
reminder.priority = priority.eventKitValue
Expand All @@ -154,7 +161,8 @@ public actor RemindersStore {
priority: ReminderPriority(eventKitValue: Int(reminder.priority)),
dueDate: date(from: reminder.dueDateComponents),
listID: reminder.calendar.calendarIdentifier,
listName: reminder.calendar.title
listName: reminder.calendar.title,
isAllDay: isAllDay(components: reminder.dueDateComponents)
)
}

Expand All @@ -174,7 +182,8 @@ public actor RemindersStore {
priority: ReminderPriority(eventKitValue: Int(reminder.priority)),
dueDate: date(from: reminder.dueDateComponents),
listID: reminder.calendar.calendarIdentifier,
listName: reminder.calendar.title
listName: reminder.calendar.title,
isAllDay: isAllDay(components: reminder.dueDateComponents)
)
)
}
Expand Down Expand Up @@ -214,6 +223,7 @@ public actor RemindersStore {
let dueDateComponents: DateComponents?
let listID: String
let listName: String
let isAllDay: Bool
}

let reminderData = await withCheckedContinuation { (continuation: CheckedContinuation<[ReminderData], Never>) in
Expand All @@ -229,7 +239,8 @@ public actor RemindersStore {
priority: Int(reminder.priority),
dueDateComponents: reminder.dueDateComponents,
listID: reminder.calendar.calendarIdentifier,
listName: reminder.calendar.title
listName: reminder.calendar.title,
isAllDay: Self.checkIsAllDay(components: reminder.dueDateComponents)
)
}
continuation.resume(returning: data)
Expand All @@ -246,7 +257,8 @@ public actor RemindersStore {
priority: ReminderPriority(eventKitValue: data.priority),
dueDate: date(from: data.dueDateComponents),
listID: data.listID,
listName: data.listName
listName: data.listName,
isAllDay: data.isAllDay
)
}
}
Expand All @@ -266,15 +278,30 @@ public actor RemindersStore {
return calendar
}

private func calendarComponents(from date: Date) -> DateComponents {
calendar.dateComponents([.year, .month, .day, .hour, .minute], from: date)
private func calendarComponents(from date: Date, isAllDay: Bool = false) -> DateComponents {
if isAllDay {
return calendar.dateComponents([.year, .month, .day], from: date)
} else {
return calendar.dateComponents([.year, .month, .day, .hour, .minute], from: date)
}
}

private func date(from components: DateComponents?) -> Date? {
guard let components else { return nil }
return calendar.date(from: components)
}

private func isAllDay(components: DateComponents?) -> Bool {
guard let components else { return false }
// A reminder is all-day if it has date components but no time components
return components.hour == nil && components.minute == nil && components.second == nil
}

private static func checkIsAllDay(components: DateComponents?) -> Bool {
guard let components else { return false }
return components.hour == nil && components.minute == nil && components.second == nil
}

private func item(from reminder: EKReminder) -> ReminderItem {
ReminderItem(
id: reminder.calendarItemIdentifier,
Expand All @@ -285,7 +312,8 @@ public actor RemindersStore {
priority: ReminderPriority(eventKitValue: Int(reminder.priority)),
dueDate: date(from: reminder.dueDateComponents),
listID: reminder.calendar.calendarIdentifier,
listName: reminder.calendar.title
listName: reminder.calendar.title,
isAllDay: isAllDay(components: reminder.dueDateComponents)
)
}
}
14 changes: 11 additions & 3 deletions Sources/RemindCore/Models.swift
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,7 @@ public struct ReminderItem: Identifiable, Codable, Sendable, Equatable {
public let dueDate: Date?
public let listID: String
public let listName: String
public let isAllDay: Bool

public init(
id: String,
Expand All @@ -63,7 +64,8 @@ public struct ReminderItem: Identifiable, Codable, Sendable, Equatable {
priority: ReminderPriority,
dueDate: Date?,
listID: String,
listName: String
listName: String,
isAllDay: Bool = false
) {
self.id = id
self.title = title
Expand All @@ -74,6 +76,7 @@ public struct ReminderItem: Identifiable, Codable, Sendable, Equatable {
self.dueDate = dueDate
self.listID = listID
self.listName = listName
self.isAllDay = isAllDay
}
}

Expand All @@ -82,12 +85,14 @@ public struct ReminderDraft: Sendable {
public let notes: String?
public let dueDate: Date?
public let priority: ReminderPriority
public let isAllDay: Bool

public init(title: String, notes: String?, dueDate: Date?, priority: ReminderPriority) {
public init(title: String, notes: String?, dueDate: Date?, priority: ReminderPriority, isAllDay: Bool = false) {
self.title = title
self.notes = notes
self.dueDate = dueDate
self.priority = priority
self.isAllDay = isAllDay
}
}

Expand All @@ -98,20 +103,23 @@ public struct ReminderUpdate: Sendable {
public let priority: ReminderPriority?
public let listName: String?
public let isCompleted: Bool?
public let isAllDay: Bool?

public init(
title: String? = nil,
notes: String? = nil,
dueDate: Date?? = nil,
priority: ReminderPriority? = nil,
listName: String? = nil,
isCompleted: Bool? = nil
isCompleted: Bool? = nil,
isAllDay: Bool? = nil
) {
self.title = title
self.notes = notes
self.dueDate = dueDate
self.priority = priority
self.listName = listName
self.isCompleted = isCompleted
self.isAllDay = isAllDay
}
}
7 changes: 6 additions & 1 deletion Sources/remindctl/Commands/AddCommand.swift
Original file line number Diff line number Diff line change
Expand Up @@ -24,13 +24,17 @@ enum AddCommand {
help: "none|low|medium|high",
parsing: .singleValue
),
],
flags: [
.make(label: "allDay", names: [.long("all-day")], help: "Create all-day reminder (no specific time)"),
]
)
),
usageExamples: [
"remindctl add \"Buy milk\"",
"remindctl add --title \"Call mom\" --list Personal --due tomorrow",
"remindctl add \"Review docs\" --priority high",
"remindctl add \"Meeting prep\" --due today --all-day",
]
) { values, runtime in
let titleOption = values.option("title")
Expand Down Expand Up @@ -59,6 +63,7 @@ enum AddCommand {

let dueDate = try dueValue.map(CommandHelpers.parseDueDate)
let priority = try priorityValue.map(CommandHelpers.parsePriority) ?? .none
let isAllDay = values.flag("allDay")

let store = RemindersStore()
try await store.requestAccess()
Expand All @@ -73,7 +78,7 @@ enum AddCommand {
throw RemindCoreError.operationFailed("No default list found. Specify --list.")
}

let draft = ReminderDraft(title: title, notes: notes, dueDate: dueDate, priority: priority)
let draft = ReminderDraft(title: title, notes: notes, dueDate: dueDate, priority: priority, isAllDay: isAllDay)
let reminder = try await store.createReminder(draft, listName: targetList)
OutputRenderer.printReminder(reminder, format: runtime.outputFormat)
}
Expand Down
9 changes: 7 additions & 2 deletions Sources/remindctl/OutputFormatting.swift
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ enum OutputRenderer {
static func printReminder(_ reminder: ReminderItem, format: OutputFormat) {
switch format {
case .standard:
let due = reminder.dueDate.map { DateParsing.formatDisplay($0) } ?? "no due date"
let due = formatDueDate(reminder.dueDate, isAllDay: reminder.isAllDay)
Swift.print("✓ \(reminder.title) [\(reminder.listName)] — \(due)")
case .plain:
Swift.print(plainLine(for: reminder))
Expand Down Expand Up @@ -96,12 +96,17 @@ enum OutputRenderer {
}
for (index, reminder) in sorted.enumerated() {
let status = reminder.isCompleted ? "x" : " "
let due = reminder.dueDate.map { DateParsing.formatDisplay($0) } ?? "no due date"
let due = formatDueDate(reminder.dueDate, isAllDay: reminder.isAllDay)
let priority = reminder.priority == .none ? "" : " priority=\(reminder.priority.rawValue)"
Swift.print("[\(index + 1)] [\(status)] \(reminder.title) [\(reminder.listName)] — \(due)\(priority)")
}
}

private static func formatDueDate(_ dueDate: Date?, isAllDay: Bool) -> String {
guard let dueDate else { return "no due date" }
return isAllDay ? DateParsing.formatDisplayAllDay(dueDate) : DateParsing.formatDisplay(dueDate)
}

private static func printRemindersPlain(_ reminders: [ReminderItem]) {
let sorted = ReminderFiltering.sort(reminders)
for reminder in sorted {
Expand Down