Fix all-day event parsing with VALUE=DATE format support
- Add special handling for date-only format (%Y%m%d) in parse_datetime_with_tz - Convert NaiveDate to midnight NaiveDateTime for all-day events - Reorder all-day detection to occur before datetime parsing - All-day events now properly display in calendar views 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
@@ -167,8 +167,6 @@ impl CalDAVClient {
|
|||||||
};
|
};
|
||||||
|
|
||||||
let basic_auth = self.config.get_basic_auth();
|
let basic_auth = self.config.get_basic_auth();
|
||||||
println!("🔑 REPORT Basic Auth: Basic {}", basic_auth);
|
|
||||||
println!("🌐 REPORT URL: {}", url);
|
|
||||||
|
|
||||||
let response = self
|
let response = self
|
||||||
.http_client
|
.http_client
|
||||||
@@ -349,6 +347,13 @@ impl CalDAVClient {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
full_prop.push_str(&format!(":{}", prop_value));
|
full_prop.push_str(&format!(":{}", prop_value));
|
||||||
|
|
||||||
|
// Debug logging for DTSTART properties specifically
|
||||||
|
if prop_name == "DTSTART" {
|
||||||
|
println!("🔍 Raw DTSTART property: name='{}', value='{}', params={:?}, full_prop='{}'",
|
||||||
|
property.name, prop_value, property.params, full_prop);
|
||||||
|
}
|
||||||
|
|
||||||
full_properties.insert(prop_name, full_prop);
|
full_properties.insert(prop_name, full_prop);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -358,6 +363,21 @@ impl CalDAVClient {
|
|||||||
.ok_or_else(|| CalDAVError::ParseError("Missing UID field".to_string()))?
|
.ok_or_else(|| CalDAVError::ParseError("Missing UID field".to_string()))?
|
||||||
.clone();
|
.clone();
|
||||||
|
|
||||||
|
// Determine if it's an all-day event FIRST by checking for VALUE=DATE parameter per RFC 5545
|
||||||
|
let empty_string = String::new();
|
||||||
|
let dtstart_raw = full_properties.get("DTSTART").unwrap_or(&empty_string);
|
||||||
|
let dtstart_value = properties.get("DTSTART").unwrap_or(&empty_string);
|
||||||
|
let all_day = dtstart_raw.contains("VALUE=DATE") || (!dtstart_value.contains("T") && dtstart_value.len() == 8);
|
||||||
|
|
||||||
|
// Debug logging for DTSTART parsing
|
||||||
|
if all_day {
|
||||||
|
println!("✅ ALL-DAY EVENT FOUND: DTSTART='{}' -> all_day={}", dtstart_raw, all_day);
|
||||||
|
} else {
|
||||||
|
println!("🔍 Backend: DTSTART='{}' (len={}, has_T={}, has_VALUE_DATE={}) -> all_day={}",
|
||||||
|
dtstart_raw, dtstart_raw.len(), dtstart_raw.contains("T"),
|
||||||
|
dtstart_raw.contains("VALUE=DATE"), all_day);
|
||||||
|
}
|
||||||
|
|
||||||
// Parse start time (required)
|
// Parse start time (required)
|
||||||
let start_prop = properties
|
let start_prop = properties
|
||||||
.get("DTSTART")
|
.get("DTSTART")
|
||||||
@@ -375,11 +395,6 @@ impl CalDAVClient {
|
|||||||
(None, None)
|
(None, None)
|
||||||
};
|
};
|
||||||
|
|
||||||
// Determine if it's an all-day event by checking for VALUE=DATE parameter
|
|
||||||
let empty_string = String::new();
|
|
||||||
let dtstart_raw = properties.get("DTSTART").unwrap_or(&empty_string);
|
|
||||||
let all_day = dtstart_raw.contains("VALUE=DATE") || (!dtstart_raw.contains("T") && dtstart_raw.len() == 8);
|
|
||||||
|
|
||||||
// Parse status
|
// Parse status
|
||||||
let status = properties
|
let status = properties
|
||||||
.get("STATUS")
|
.get("STATUS")
|
||||||
@@ -471,6 +486,14 @@ impl CalDAVClient {
|
|||||||
vevent.exdate = exdate.into_iter().map(|dt| dt.naive_utc()).collect();
|
vevent.exdate = exdate.into_iter().map(|dt| dt.naive_utc()).collect();
|
||||||
vevent.exdate_tzid = None; // TODO: Parse timezone info for EXDATE
|
vevent.exdate_tzid = None; // TODO: Parse timezone info for EXDATE
|
||||||
vevent.all_day = all_day;
|
vevent.all_day = all_day;
|
||||||
|
if all_day {
|
||||||
|
println!("📅 Backend: Successfully created all-day VEvent '{}' with start={}, end={:?}, all_day={}",
|
||||||
|
vevent.summary.as_ref().unwrap_or(&"Untitled".to_string()),
|
||||||
|
vevent.dtstart,
|
||||||
|
vevent.dtend,
|
||||||
|
vevent.all_day
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
// Parse alarms
|
// Parse alarms
|
||||||
vevent.alarms = self.parse_valarms(&event)?;
|
vevent.alarms = self.parse_valarms(&event)?;
|
||||||
@@ -582,11 +605,9 @@ impl CalDAVClient {
|
|||||||
pub async fn discover_calendars(&self) -> Result<Vec<String>, CalDAVError> {
|
pub async fn discover_calendars(&self) -> Result<Vec<String>, CalDAVError> {
|
||||||
// First, try to discover user calendars if we have a calendar path in config
|
// First, try to discover user calendars if we have a calendar path in config
|
||||||
if let Some(calendar_path) = &self.config.calendar_path {
|
if let Some(calendar_path) = &self.config.calendar_path {
|
||||||
println!("Using configured calendar path: {}", calendar_path);
|
|
||||||
return Ok(vec![calendar_path.clone()]);
|
return Ok(vec![calendar_path.clone()]);
|
||||||
}
|
}
|
||||||
|
|
||||||
println!("No calendar path configured, discovering calendars...");
|
|
||||||
|
|
||||||
// Try different common CalDAV discovery paths
|
// Try different common CalDAV discovery paths
|
||||||
// Note: paths should be relative to the server URL base
|
// Note: paths should be relative to the server URL base
|
||||||
@@ -599,20 +620,16 @@ impl CalDAVClient {
|
|||||||
let mut has_valid_caldav_response = false;
|
let mut has_valid_caldav_response = false;
|
||||||
|
|
||||||
for path in discovery_paths {
|
for path in discovery_paths {
|
||||||
println!("Trying discovery path: {}", path);
|
|
||||||
match self.discover_calendars_at_path(&path).await {
|
match self.discover_calendars_at_path(&path).await {
|
||||||
Ok(calendars) => {
|
Ok(calendars) => {
|
||||||
println!("Found {} calendar(s) at {}", calendars.len(), path);
|
|
||||||
has_valid_caldav_response = true;
|
has_valid_caldav_response = true;
|
||||||
all_calendars.extend(calendars);
|
all_calendars.extend(calendars);
|
||||||
}
|
}
|
||||||
Err(CalDAVError::ServerError(status)) => {
|
Err(CalDAVError::ServerError(status)) => {
|
||||||
// HTTP error - this might be expected for some paths, continue trying
|
// HTTP error - this might be expected for some paths, continue trying
|
||||||
println!("Discovery path {} returned HTTP {}, trying next path", path, status);
|
|
||||||
}
|
}
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
// Network or other error - this suggests the server isn't reachable or isn't CalDAV
|
// Network or other error - this suggests the server isn't reachable or isn't CalDAV
|
||||||
println!("Discovery failed for path {}: {:?}", path, e);
|
|
||||||
return Err(e);
|
return Err(e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -786,14 +803,24 @@ impl CalDAVClient {
|
|||||||
return Ok((dt.naive_utc(), Some("UTC".to_string())));
|
return Ok((dt.naive_utc(), Some("UTC".to_string())));
|
||||||
}
|
}
|
||||||
|
|
||||||
// Try parsing as naive datetime
|
// Special handling for date-only format (all-day events)
|
||||||
if let Ok(naive_dt) = chrono::NaiveDateTime::parse_from_str(datetime_part, format) {
|
if *format == "%Y%m%d" {
|
||||||
// Per RFC 5545: if no TZID parameter is provided, treat as UTC
|
if let Ok(date) = chrono::NaiveDate::parse_from_str(datetime_part, format) {
|
||||||
let tz = timezone_id.unwrap_or_else(|| "UTC".to_string());
|
// Convert date to midnight datetime for all-day events
|
||||||
|
let naive_dt = date.and_hms_opt(0, 0, 0).unwrap();
|
||||||
// If it's UTC, the naive time is already correct
|
let tz = timezone_id.unwrap_or_else(|| "UTC".to_string());
|
||||||
// If it's a local timezone, we store the naive time and the timezone ID
|
return Ok((naive_dt, Some(tz)));
|
||||||
return Ok((naive_dt, Some(tz)));
|
}
|
||||||
|
} else {
|
||||||
|
// Try parsing as naive datetime for time-based formats
|
||||||
|
if let Ok(naive_dt) = chrono::NaiveDateTime::parse_from_str(datetime_part, format) {
|
||||||
|
// Per RFC 5545: if no TZID parameter is provided, treat as UTC
|
||||||
|
let tz = timezone_id.unwrap_or_else(|| "UTC".to_string());
|
||||||
|
|
||||||
|
// If it's UTC, the naive time is already correct
|
||||||
|
// If it's a local timezone, we store the naive time and the timezone ID
|
||||||
|
return Ok((naive_dt, Some(tz)));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user