Command-line and Emacs Calendar Client
1type format = [ `Text | `Json | `Csv | `Ics | `Entries | `Sexp ] 2 3let format_date date = 4 let y, m, d = Ptime.to_date date in 5 let cal_date = CalendarLib.Date.make y m d in 6 let weekday = 7 match CalendarLib.Date.day_of_week cal_date with 8 | CalendarLib.Date.Mon -> "Mon" 9 | CalendarLib.Date.Tue -> "Tue" 10 | CalendarLib.Date.Wed -> "Wed" 11 | CalendarLib.Date.Thu -> "Thu" 12 | CalendarLib.Date.Fri -> "Fri" 13 | CalendarLib.Date.Sat -> "Sat" 14 | CalendarLib.Date.Sun -> "Sun" 15 in 16 Printf.sprintf "%04d-%02d-%02d %s" y m d weekday 17 18let format_time date = 19 let _, ((h, m, _), _) = Ptime.to_date_time date in 20 Printf.sprintf "%02d:%02d" h m 21 22let format_datetime date = 23 Printf.sprintf "%s %s" (format_date date) (format_time date) 24 25let next_day day ~next = 26 let y1, m1, d1 = Ptime.to_date day in 27 let y2, m2, d2 = Ptime.to_date next in 28 y1 == y2 && m1 == m2 && d1 == d2 - 1 29 30(* exosed from icalendar *) 31 32let weekday_strings = 33 [ 34 (`Monday, "MO"); 35 (`Tuesday, "TU"); 36 (`Wednesday, "WE"); 37 (`Thursday, "TH"); 38 (`Friday, "FR"); 39 (`Saturday, "SA"); 40 (`Sunday, "SU"); 41 ] 42 43let freq_strings = 44 [ 45 (`Daily, "DAILY"); 46 (`Hourly, "HOURLY"); 47 (`Minutely, "MINUTELY"); 48 (`Monthly, "MONTHLY"); 49 (`Secondly, "SECONDLY"); 50 (`Weekly, "WEEKLY"); 51 (`Yearly, "YEARLY"); 52 ] 53 54let date_to_str (y, m, d) = Printf.sprintf "%04d%02d%02d" y m d 55 56let datetime_to_str ptime utc = 57 let date, ((hh, mm, ss), _) = Ptime.to_date_time ptime in 58 Printf.sprintf "%sT%02d%02d%02d%s" (date_to_str date) hh mm ss 59 (if utc then "Z" else "") 60 61let timestamp_to_ics ts buf = 62 Buffer.add_string buf 63 @@ 64 match ts with 65 | `Utc ts -> datetime_to_str ts true 66 | `Local ts -> datetime_to_str ts false 67 | `With_tzid (ts, _str) -> (* TODO *) datetime_to_str ts false 68 69let recurs_to_ics (freq, count_or_until, interval, l) buf = 70 let write_rulepart key value = 71 Buffer.add_string buf key; 72 Buffer.add_char buf '='; 73 Buffer.add_string buf value 74 in 75 let int_list l = String.concat "," @@ List.map string_of_int l in 76 let recur_to_ics = function 77 | `Byminute byminlist -> write_rulepart "BYMINUTE" (int_list byminlist) 78 | `Byday bywdaylist -> 79 let wday (weeknumber, weekday) = 80 (if weeknumber = 0 then "" else string_of_int weeknumber) 81 ^ List.assoc weekday weekday_strings 82 in 83 write_rulepart "BYDAY" (String.concat "," @@ List.map wday bywdaylist) 84 | `Byhour byhrlist -> write_rulepart "BYHOUR" (int_list byhrlist) 85 | `Bymonth bymolist -> write_rulepart "BYMONTH" (int_list bymolist) 86 | `Bymonthday bymodaylist -> 87 write_rulepart "BYMONTHDAY" (int_list bymodaylist) 88 | `Bysecond byseclist -> write_rulepart "BYSECOND" (int_list byseclist) 89 | `Bysetposday bysplist -> write_rulepart "BYSETPOS" (int_list bysplist) 90 | `Byweek bywknolist -> write_rulepart "BYWEEKNO" (int_list bywknolist) 91 | `Byyearday byyrdaylist -> 92 write_rulepart "BYYEARDAY" (int_list byyrdaylist) 93 | `Weekday weekday -> 94 write_rulepart "WKST" (List.assoc weekday weekday_strings) 95 in 96 write_rulepart "FREQ" (List.assoc freq freq_strings); 97 (match count_or_until with 98 | None -> () 99 | Some x -> ( 100 Buffer.add_char buf ';'; 101 match x with 102 | `Count c -> write_rulepart "COUNT" (string_of_int c) 103 | `Until enddate -> 104 (* TODO cleanup *) 105 Buffer.add_string buf "UNTIL="; 106 timestamp_to_ics enddate buf)); 107 (match interval with 108 | None -> () 109 | Some i -> 110 Buffer.add_char buf ';'; 111 write_rulepart "INTERVAL" (string_of_int i)); 112 List.iter 113 (fun recur -> 114 Buffer.add_char buf ';'; 115 recur_to_ics recur) 116 l 117 118let format_alt ~format ~start ~end_ event = 119 let open Event in 120 match format with 121 | `Text -> 122 let id = get_id event in 123 let start_date = " " ^ format_date start in 124 let start_time = 125 match get_day_event event with 126 | true -> "" 127 | false -> " " ^ format_time start 128 in 129 let end_date, end_time = 130 match end_ with 131 | None -> ("", "") 132 | Some end_ -> ( 133 match (get_day_event event, next_day start ~next:end_) with 134 | true, true -> ("", "") 135 | true, _ -> (" - " ^ format_date end_, "") 136 | false, true -> ("", " - " ^ format_time end_) 137 | false, _ -> (" - " ^ format_date end_, " " ^ format_time end_)) 138 in 139 let summary = 140 match get_summary event with 141 | Some summary when summary <> "" -> " " ^ summary 142 | _ -> "" 143 in 144 let location = 145 match get_location event with 146 | Some loc when loc <> "" -> " @" ^ loc 147 | _ -> "" 148 in 149 Printf.sprintf "%-45s%s%s%s%s%s%s" id start_date start_time end_date 150 end_time summary location 151 | `Json -> 152 let open Yojson.Safe in 153 let json = 154 `Assoc 155 [ 156 ("id", `String (get_id event)); 157 ( "summary", 158 match get_summary event with 159 | Some summary -> `String summary 160 | None -> `Null ); 161 ("start", `String (format_datetime start)); 162 ( "end", 163 match end_ with 164 | Some e -> `String (format_datetime e) 165 | None -> `Null ); 166 ( "location", 167 match get_location event with 168 | Some loc -> `String loc 169 | None -> `Null ); 170 ( "description", 171 match get_description event with 172 | Some desc -> `String desc 173 | None -> `Null ); 174 ( "calendar", 175 match get_collection event with 176 | Collection.Col cal -> `String cal ); 177 ] 178 in 179 to_string json 180 | `Csv -> 181 let summary = 182 match get_summary event with Some summary -> summary | None -> "" 183 in 184 let start = format_datetime start in 185 let end_str = 186 match end_ with Some e -> format_datetime e | None -> "" 187 in 188 let location = 189 match get_location event with Some loc -> loc | None -> "" 190 in 191 let cal_id = 192 match get_collection event with Collection.Col cal -> cal 193 in 194 Printf.sprintf "\"%s\",\"%s\",\"%s\",\"%s\",\"%s\"" summary start end_str 195 location cal_id 196 | `Ics -> 197 let cal_props = [] in 198 let event_ical = Event.to_icalendar event in 199 Icalendar.to_ics ~cr:true (cal_props, [ `Event event_ical ]) 200 | `Entries -> 201 let summary = 202 match get_summary event with Some summary -> summary | None -> "" 203 in 204 let start = format_datetime start in 205 let end_str = 206 match end_ with Some e -> format_datetime e | None -> "" 207 in 208 let location = 209 match get_location event with Some loc -> loc | None -> "" 210 in 211 let description = 212 match get_description event with Some desc -> desc | None -> "" 213 in 214 let rrule = 215 match get_recurrence event with 216 | Some r -> 217 let buf = Buffer.create 128 in 218 recurs_to_ics r buf; 219 Buffer.contents buf 220 | None -> "" 221 in 222 Printf.sprintf "%s: %s\n%s: %s\n%s: %s\n%s: %s\n%s: %s\n%s: %s" "Summary" 223 summary "Start" start "End" end_str "Location" location "Description" 224 description "Reccurence" rrule 225 | `Sexp -> 226 let summary = 227 match get_summary event with Some summary -> summary | None -> "" 228 in 229 let start_date, start_time = 230 let date = start in 231 let y, m, d = Ptime.to_date date in 232 let _, ((h, min, s), _) = Ptime.to_date_time date in 233 let cal_date = CalendarLib.Date.make y m d in 234 let dow = 235 match CalendarLib.Date.day_of_week cal_date with 236 | CalendarLib.Date.Mon -> "monday" 237 | CalendarLib.Date.Tue -> "tuesday" 238 | CalendarLib.Date.Wed -> "wednesday" 239 | CalendarLib.Date.Thu -> "thursday" 240 | CalendarLib.Date.Fri -> "friday" 241 | CalendarLib.Date.Sat -> "saturday" 242 | CalendarLib.Date.Sun -> "sunday" 243 in 244 ( Printf.sprintf "(%04d %02d %02d %s)" y m d dow, 245 Printf.sprintf "(%02d %02d %02d)" h min s ) 246 in 247 let end_str = 248 match end_ with 249 | Some end_date -> 250 let y, m, d = Ptime.to_date end_date in 251 let _, ((h, min, s), _) = Ptime.to_date_time end_date in 252 let cal_date = CalendarLib.Date.make y m d in 253 let dow = 254 match CalendarLib.Date.day_of_week cal_date with 255 | CalendarLib.Date.Mon -> "monday" 256 | CalendarLib.Date.Tue -> "tuesday" 257 | CalendarLib.Date.Wed -> "wednesday" 258 | CalendarLib.Date.Thu -> "thursday" 259 | CalendarLib.Date.Fri -> "friday" 260 | CalendarLib.Date.Sat -> "saturday" 261 | CalendarLib.Date.Sun -> "sunday" 262 in 263 Printf.sprintf "((%04d %02d %02d %s) (%02d %02d %02d))" y m d dow h 264 min s 265 | None -> "nil" 266 in 267 let location = 268 match get_location event with 269 | Some loc -> Printf.sprintf "\"%s\"" (String.escaped loc) 270 | None -> "nil" 271 in 272 let description = 273 match get_description event with 274 | Some desc -> Printf.sprintf "\"%s\"" (String.escaped desc) 275 | None -> "nil" 276 in 277 let calendar = 278 match get_collection event with 279 | Collection.Col cal -> Printf.sprintf "\"%s\"" (String.escaped cal) 280 in 281 let id = get_id event in 282 Printf.sprintf 283 "((:id \"%s\" :summary \"%s\" :start (%s %s) :end %s :location %s \ 284 :description %s :calendar %s))" 285 (String.escaped id) (String.escaped summary) start_date start_time 286 end_str location description calendar 287 288let format_event ?(format = `Text) event = 289 format_alt ~format ~start:(Event.get_start event) ~end_:(Event.get_end event) 290 event 291 292let format_instance ?(format = `Text) instance = 293 let open Recur in 294 format_alt ~format ~start:instance.start ~end_:instance.end_ instance.event 295 296let format_events ?(format = `Text) events = 297 match format with 298 | `Json -> 299 let json_events = 300 List.map 301 (fun e -> Yojson.Safe.from_string (format_event ~format:`Json e)) 302 events 303 in 304 Yojson.Safe.to_string (`List json_events) 305 | `Csv -> 306 "\"Summary\",\"Start\",\"End\",\"Location\",\"Calendar\"\n" 307 ^ String.concat "\n" (List.map (format_event ~format:`Csv) events) 308 | `Sexp -> 309 "(" 310 ^ String.concat "\n " 311 (List.map (fun e -> format_event ~format:`Sexp e) events) 312 ^ ")" 313 | _ -> String.concat "\n" (List.map (fun e -> format_event ~format e) events) 314 315let format_instances ?(format = `Text) instances = 316 match format with 317 | `Json -> 318 let json_instances = 319 List.map 320 (fun e -> Yojson.Safe.from_string (format_instance ~format:`Json e)) 321 instances 322 in 323 Yojson.Safe.to_string (`List json_instances) 324 | `Csv -> 325 "\"Summary\",\"Start\",\"End\",\"Location\",\"Calendar\"\n" 326 ^ String.concat "\n" (List.map (format_instance ~format:`Csv) instances) 327 | `Sexp -> 328 "(" 329 ^ String.concat "\n " 330 (List.map (fun e -> format_instance ~format:`Sexp e) instances) 331 ^ ")" 332 | _ -> 333 String.concat "\n" 334 (List.map (fun e -> format_instance ~format e) instances)