Trying very hard not to miss calendar events
0
fork

Configure Feed

Select the types of activity you want to include in your feed.

More properties for events

Co-authored-by: Claude <noreply@anthropic.com>

+110 -20
+1
Cargo.toml
··· 11 11 glib-sys = "0.20" 12 12 gio-sys = "0.20" 13 13 libc = "0.2" 14 + chrono = "0.4" 14 15 15 16 [build-dependencies] 16 17 pkg-config = "0.3"
+80 -18
src/eds.rs
··· 1 1 // Safe Rust abstractions over Evolution Data Server C APIs 2 2 3 3 use crate::ffi; 4 + use chrono::{DateTime, Local, NaiveDate, TimeZone}; 4 5 use std::ffi::{CStr, CString}; 5 6 use std::fmt; 6 7 use std::os::raw::c_char; ··· 241 242 } 242 243 } 243 244 245 + // Event time representation 246 + #[derive(Debug, Clone)] 247 + pub enum EventTime { 248 + DateTime(DateTime<Local>), 249 + Date(NaiveDate), 250 + } 251 + 252 + impl EventTime { 253 + pub fn format(&self, fmt: &str) -> String { 254 + match self { 255 + EventTime::DateTime(dt) => dt.format(fmt).to_string(), 256 + EventTime::Date(date) => date.format(fmt).to_string(), 257 + } 258 + } 259 + } 260 + 244 261 // Event 245 262 pub struct Event { 246 263 pub summary: String, 247 - pub start_time: i64, 248 - pub end_time: i64, 264 + pub description: Option<String>, 265 + pub location: Option<String>, 266 + pub start: EventTime, 267 + pub end: EventTime, 268 + pub uid: String, 249 269 } 250 270 251 271 impl Event { ··· 258 278 "(No title)".to_string() 259 279 }; 260 280 281 + let description_ptr = ffi::i_cal_component_get_description(comp); 282 + let description = if !description_ptr.is_null() { 283 + let desc = CStr::from_ptr(description_ptr) 284 + .to_string_lossy() 285 + .into_owned(); 286 + if desc.is_empty() { 287 + None 288 + } else { 289 + Some(desc) 290 + } 291 + } else { 292 + None 293 + }; 294 + 295 + let location_ptr = ffi::i_cal_component_get_location(comp); 296 + let location = if !location_ptr.is_null() { 297 + let loc = CStr::from_ptr(location_ptr).to_string_lossy().into_owned(); 298 + if loc.is_empty() { 299 + None 300 + } else { 301 + Some(loc) 302 + } 303 + } else { 304 + None 305 + }; 306 + 307 + let uid_ptr = ffi::i_cal_component_get_uid(comp); 308 + let uid = if !uid_ptr.is_null() { 309 + CStr::from_ptr(uid_ptr).to_string_lossy().into_owned() 310 + } else { 311 + String::new() 312 + }; 313 + 261 314 let start = ffi::i_cal_component_get_dtstart(comp); 262 315 let end = ffi::i_cal_component_get_dtend(comp); 263 316 264 - let start_time = ffi::i_cal_time_as_timet(start); 265 - let end_time = ffi::i_cal_time_as_timet(end); 317 + let is_all_day = ffi::i_cal_time_is_date(start) != 0; 318 + 319 + let start_time_t = ffi::i_cal_time_as_timet(start); 320 + let end_time_t = ffi::i_cal_time_as_timet(end); 321 + 322 + let (start_event_time, end_event_time) = if is_all_day { 323 + // For all-day events, use just the date 324 + let start_dt = Local.timestamp_opt(start_time_t, 0).unwrap(); 325 + let end_dt = Local.timestamp_opt(end_time_t, 0).unwrap(); 326 + ( 327 + EventTime::Date(start_dt.date_naive()), 328 + EventTime::Date(end_dt.date_naive()), 329 + ) 330 + } else { 331 + // For timed events, use full datetime 332 + ( 333 + EventTime::DateTime(Local.timestamp_opt(start_time_t, 0).unwrap()), 334 + EventTime::DateTime(Local.timestamp_opt(end_time_t, 0).unwrap()), 335 + ) 336 + }; 266 337 267 338 ffi::g_object_unref(start as *mut std::os::raw::c_void); 268 339 ffi::g_object_unref(end as *mut std::os::raw::c_void); 269 340 270 341 Some(Event { 271 342 summary, 272 - start_time, 273 - end_time, 343 + description, 344 + location, 345 + start: start_event_time, 346 + end: end_event_time, 347 + uid, 274 348 }) 275 - } 276 - } 277 - 278 - pub fn format_time(timestamp: i64) -> String { 279 - unsafe { 280 - let tm = libc::localtime(&timestamp); 281 - let mut buf = vec![0u8; 64]; 282 - let fmt = CString::new("%Y-%m-%d %H:%M").unwrap(); 283 - libc::strftime(buf.as_mut_ptr() as *mut c_char, buf.len(), fmt.as_ptr(), tm); 284 - CStr::from_ptr(buf.as_ptr() as *const c_char) 285 - .to_string_lossy() 286 - .into_owned() 287 349 } 288 350 } 289 351 }
+4
src/ffi.rs
··· 95 95 #[link(name = "ical-glib")] 96 96 extern "C" { 97 97 pub fn i_cal_component_get_summary(comp: *mut ICalComponent) -> *const c_char; 98 + pub fn i_cal_component_get_description(comp: *mut ICalComponent) -> *const c_char; 99 + pub fn i_cal_component_get_location(comp: *mut ICalComponent) -> *const c_char; 100 + pub fn i_cal_component_get_uid(comp: *mut ICalComponent) -> *const c_char; 98 101 pub fn i_cal_component_get_dtstart(comp: *mut ICalComponent) -> *mut ICalTime; 99 102 pub fn i_cal_component_get_dtend(comp: *mut ICalComponent) -> *mut ICalTime; 100 103 pub fn i_cal_time_as_timet(tt: *mut ICalTime) -> libc::time_t; 104 + pub fn i_cal_time_is_date(tt: *mut ICalTime) -> c_int; 101 105 } 102 106 103 107 // Helper for GObject unrefs
+25 -2
src/main.rs
··· 95 95 }; 96 96 97 97 for event in events { 98 - let start_str = eds::Event::format_time(event.start_time); 99 - let end_str = eds::Event::format_time(event.end_time); 98 + let (start_str, end_str) = match (&event.start, &event.end) { 99 + (eds::EventTime::Date(start_date), eds::EventTime::Date(end_date)) => { 100 + // All-day event 101 + let start = start_date.format("%Y-%m-%d").to_string(); 102 + let end = if start_date == end_date { 103 + "(all day)".to_string() 104 + } else { 105 + format!("to {}", end_date.format("%Y-%m-%d")) 106 + }; 107 + (start, end) 108 + } 109 + (eds::EventTime::DateTime(start_dt), eds::EventTime::DateTime(end_dt)) => { 110 + // Timed event 111 + let start = start_dt.format("%Y-%m-%d %H:%M").to_string(); 112 + let end = end_dt.format("%H:%M").to_string(); 113 + (start, end) 114 + } 115 + _ => { 116 + // Mixed types - shouldn't happen but handle gracefully 117 + ( 118 + event.start.format("%Y-%m-%d %H:%M"), 119 + event.end.format("%H:%M"), 120 + ) 121 + } 122 + }; 100 123 101 124 println!( 102 125 "[{}] {} | {} - {}",