1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
|
#!/usr/bin/env swift
// cal-events — dump calendar events for a date range as JSON, for tock logging.
//
// Usage: cal-events <from YYYY-MM-DD> [<to YYYY-MM-DD>] (to defaults to from)
// Output: JSON array of {title, start, end, calendar, allDay} with ISO-8601
// local-time start/end. Non-all-day events only. All event calendars included;
// filter downstream (the day tool defaults to the work calendar).
import EventKit
import Foundation
let args = CommandLine.arguments
guard args.count >= 2 else {
fputs("usage: cal-events <from YYYY-MM-DD> [to YYYY-MM-DD]\n", stderr)
exit(2)
}
let dateOnly = DateFormatter()
dateOnly.dateFormat = "yyyy-MM-dd"
dateOnly.timeZone = TimeZone.current
guard let fromDate = dateOnly.date(from: args[1]) else {
fputs("bad from-date: \(args[1])\n", stderr); exit(2)
}
let toDate = args.count >= 3 ? (dateOnly.date(from: args[2]) ?? fromDate) : fromDate
let cal = Calendar.current
let start = cal.startOfDay(for: fromDate)
let end = cal.date(byAdding: .day, value: 1, to: cal.startOfDay(for: toDate))!
let store = EKEventStore()
let sema = DispatchSemaphore(value: 0)
var output = "[]"
store.requestFullAccessToEvents { granted, _ in
defer { sema.signal() }
guard granted else {
fputs("Calendar access denied — grant in System Settings > Privacy > Calendars\n", stderr)
return
}
let iso = ISO8601DateFormatter()
iso.timeZone = TimeZone.current
iso.formatOptions = [.withInternetDateTime]
let pred = store.predicateForEvents(withStart: start, end: end, calendars: nil)
let events = store.events(matching: pred)
.filter { !$0.isAllDay }
.sorted { $0.startDate < $1.startDate }
func esc(_ s: String) -> String {
var r = s.replacingOccurrences(of: "\\", with: "\\\\")
r = r.replacingOccurrences(of: "\"", with: "\\\"")
r = r.replacingOccurrences(of: "\n", with: " ")
return r
}
let items = events.map { e -> String in
let title = esc(e.title ?? "(no title)")
let calT = esc(e.calendar.title)
return " {\"title\": \"\(title)\", \"start\": \"\(iso.string(from: e.startDate))\", " +
"\"end\": \"\(iso.string(from: e.endDate))\", \"calendar\": \"\(calT)\"}"
}
output = items.isEmpty ? "[]" : "[\n" + items.joined(separator: ",\n") + "\n]"
}
sema.wait()
print(output)
|