Fix external calendar timezone conversion and styling
- Add comprehensive Windows timezone support for global external calendars - Map Windows timezone names (e.g. "Mountain Standard Time") to IANA zones (e.g. "America/Denver") - Support 60+ timezone mappings across North America, Europe, Asia, Asia Pacific, Africa, South America - Add chrono-tz dependency for proper timezone handling - Fix external calendar event colors by setting calendar_path for color lookup - Add visual distinction for external calendar events with dashed borders and calendar emoji - Update timezone parsing to extract TZID parameters from iCalendar DTSTART/DTEND properties - Pass external calendar data through component hierarchy for color matching 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
@@ -135,8 +135,13 @@ fn convert_ical_to_vevent(ical_event: IcalEvent) -> Result<VEvent, Box<dyn std::
|
||||
dtstart = Some(chrono::TimeZone::from_utc_datetime(&Utc, &date.and_hms_opt(12, 0, 0).unwrap()));
|
||||
}
|
||||
} else {
|
||||
// Parse datetime - could be various formats
|
||||
if let Some(dt) = parse_datetime(&value) {
|
||||
// Extract timezone info from parameters
|
||||
let tzid = property.params.as_ref()
|
||||
.and_then(|params| params.iter().find(|(k, _)| k == "TZID"))
|
||||
.and_then(|(_, v)| v.first().cloned());
|
||||
|
||||
// Parse datetime with timezone information
|
||||
if let Some(dt) = parse_datetime_with_tz(&value, tzid.as_deref()) {
|
||||
dtstart = Some(dt);
|
||||
}
|
||||
}
|
||||
@@ -150,7 +155,13 @@ fn convert_ical_to_vevent(ical_event: IcalEvent) -> Result<VEvent, Box<dyn std::
|
||||
dtend = Some(chrono::TimeZone::from_utc_datetime(&Utc, &date.and_hms_opt(12, 0, 0).unwrap()));
|
||||
}
|
||||
} else {
|
||||
if let Some(dt) = parse_datetime(&value) {
|
||||
// Extract timezone info from parameters
|
||||
let tzid = property.params.as_ref()
|
||||
.and_then(|params| params.iter().find(|(k, _)| k == "TZID"))
|
||||
.and_then(|(_, v)| v.first().cloned());
|
||||
|
||||
// Parse datetime with timezone information
|
||||
if let Some(dt) = parse_datetime_with_tz(&value, tzid.as_deref()) {
|
||||
dtend = Some(dt);
|
||||
}
|
||||
}
|
||||
@@ -206,18 +217,16 @@ fn convert_ical_to_vevent(ical_event: IcalEvent) -> Result<VEvent, Box<dyn std::
|
||||
Ok(vevent)
|
||||
}
|
||||
|
||||
fn parse_datetime(datetime_str: &str) -> Option<DateTime<Utc>> {
|
||||
fn parse_datetime_with_tz(datetime_str: &str, tzid: Option<&str>) -> Option<DateTime<Utc>> {
|
||||
use chrono::TimeZone;
|
||||
use chrono_tz::Tz;
|
||||
|
||||
// Try various datetime formats commonly found in ICS files
|
||||
|
||||
// Format: 20231201T103000Z (UTC)
|
||||
if let Ok(dt) = DateTime::parse_from_str(datetime_str, "%Y%m%dT%H%M%SZ") {
|
||||
return Some(dt.with_timezone(&Utc));
|
||||
}
|
||||
|
||||
// Format: 20231201T103000 (floating time - assume UTC)
|
||||
if let Ok(dt) = chrono::NaiveDateTime::parse_from_str(datetime_str, "%Y%m%dT%H%M%S") {
|
||||
return Some(chrono::TimeZone::from_utc_datetime(&Utc, &dt));
|
||||
}
|
||||
|
||||
// Format: 20231201T103000-0500 (with timezone offset)
|
||||
if let Ok(dt) = DateTime::parse_from_str(datetime_str, "%Y%m%dT%H%M%S%z") {
|
||||
@@ -229,9 +238,123 @@ fn parse_datetime(datetime_str: &str) -> Option<DateTime<Utc>> {
|
||||
return Some(dt.with_timezone(&Utc));
|
||||
}
|
||||
|
||||
// Format: 2023-12-01T10:30:00 (ISO without timezone)
|
||||
if let Ok(dt) = chrono::NaiveDateTime::parse_from_str(datetime_str, "%Y-%m-%dT%H:%M:%S") {
|
||||
return Some(chrono::TimeZone::from_utc_datetime(&Utc, &dt));
|
||||
// Handle naive datetime with timezone parameter
|
||||
let naive_dt = if let Ok(dt) = chrono::NaiveDateTime::parse_from_str(datetime_str, "%Y%m%dT%H%M%S") {
|
||||
Some(dt)
|
||||
} else if let Ok(dt) = chrono::NaiveDateTime::parse_from_str(datetime_str, "%Y-%m-%dT%H:%M:%S") {
|
||||
Some(dt)
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
if let Some(naive_dt) = naive_dt {
|
||||
// If TZID is provided, try to parse it
|
||||
if let Some(tzid_str) = tzid {
|
||||
// Handle common timezone formats
|
||||
let tz_result = if tzid_str.starts_with("/mozilla.org/") {
|
||||
// Mozilla/Thunderbird format: /mozilla.org/20070129_1/Europe/London
|
||||
tzid_str.split('/').last().and_then(|tz_name| tz_name.parse::<Tz>().ok())
|
||||
} else if tzid_str.contains('/') {
|
||||
// Standard timezone format: America/New_York, Europe/London
|
||||
tzid_str.parse::<Tz>().ok()
|
||||
} else {
|
||||
// Try common abbreviations and Windows timezone names
|
||||
match tzid_str {
|
||||
// Standard abbreviations
|
||||
"EST" => Some(Tz::America__New_York),
|
||||
"PST" => Some(Tz::America__Los_Angeles),
|
||||
"MST" => Some(Tz::America__Denver),
|
||||
"CST" => Some(Tz::America__Chicago),
|
||||
|
||||
// North America - Windows timezone names to IANA mapping
|
||||
"Mountain Standard Time" => Some(Tz::America__Denver),
|
||||
"Eastern Standard Time" => Some(Tz::America__New_York),
|
||||
"Central Standard Time" => Some(Tz::America__Chicago),
|
||||
"Pacific Standard Time" => Some(Tz::America__Los_Angeles),
|
||||
"Mountain Daylight Time" => Some(Tz::America__Denver),
|
||||
"Eastern Daylight Time" => Some(Tz::America__New_York),
|
||||
"Central Daylight Time" => Some(Tz::America__Chicago),
|
||||
"Pacific Daylight Time" => Some(Tz::America__Los_Angeles),
|
||||
"Hawaiian Standard Time" => Some(Tz::Pacific__Honolulu),
|
||||
"Alaskan Standard Time" => Some(Tz::America__Anchorage),
|
||||
"Alaskan Daylight Time" => Some(Tz::America__Anchorage),
|
||||
"Atlantic Standard Time" => Some(Tz::America__Halifax),
|
||||
"Newfoundland Standard Time" => Some(Tz::America__St_Johns),
|
||||
|
||||
// Europe
|
||||
"GMT Standard Time" => Some(Tz::Europe__London),
|
||||
"Greenwich Standard Time" => Some(Tz::UTC),
|
||||
"W. Europe Standard Time" => Some(Tz::Europe__Berlin),
|
||||
"Central Europe Standard Time" => Some(Tz::Europe__Warsaw),
|
||||
"Romance Standard Time" => Some(Tz::Europe__Paris),
|
||||
"Central European Standard Time" => Some(Tz::Europe__Belgrade),
|
||||
"E. Europe Standard Time" => Some(Tz::Europe__Bucharest),
|
||||
"FLE Standard Time" => Some(Tz::Europe__Helsinki),
|
||||
"GTB Standard Time" => Some(Tz::Europe__Athens),
|
||||
"Russian Standard Time" => Some(Tz::Europe__Moscow),
|
||||
"Turkey Standard Time" => Some(Tz::Europe__Istanbul),
|
||||
|
||||
// Asia
|
||||
"China Standard Time" => Some(Tz::Asia__Shanghai),
|
||||
"Tokyo Standard Time" => Some(Tz::Asia__Tokyo),
|
||||
"Korea Standard Time" => Some(Tz::Asia__Seoul),
|
||||
"Singapore Standard Time" => Some(Tz::Asia__Singapore),
|
||||
"India Standard Time" => Some(Tz::Asia__Kolkata),
|
||||
"Pakistan Standard Time" => Some(Tz::Asia__Karachi),
|
||||
"Bangladesh Standard Time" => Some(Tz::Asia__Dhaka),
|
||||
"Thailand Standard Time" => Some(Tz::Asia__Bangkok),
|
||||
"SE Asia Standard Time" => Some(Tz::Asia__Bangkok),
|
||||
"Myanmar Standard Time" => Some(Tz::Asia__Yangon),
|
||||
"Sri Lanka Standard Time" => Some(Tz::Asia__Colombo),
|
||||
"Nepal Standard Time" => Some(Tz::Asia__Kathmandu),
|
||||
"Central Asia Standard Time" => Some(Tz::Asia__Almaty),
|
||||
"West Asia Standard Time" => Some(Tz::Asia__Tashkent),
|
||||
"Afghanistan Standard Time" => Some(Tz::Asia__Kabul),
|
||||
"Iran Standard Time" => Some(Tz::Asia__Tehran),
|
||||
"Arabian Standard Time" => Some(Tz::Asia__Dubai),
|
||||
"Arab Standard Time" => Some(Tz::Asia__Riyadh),
|
||||
"Israel Standard Time" => Some(Tz::Asia__Jerusalem),
|
||||
"Jordan Standard Time" => Some(Tz::Asia__Amman),
|
||||
"Syria Standard Time" => Some(Tz::Asia__Damascus),
|
||||
"Middle East Standard Time" => Some(Tz::Asia__Beirut),
|
||||
"Egypt Standard Time" => Some(Tz::Africa__Cairo),
|
||||
"South Africa Standard Time" => Some(Tz::Africa__Johannesburg),
|
||||
"E. Africa Standard Time" => Some(Tz::Africa__Nairobi),
|
||||
"W. Central Africa Standard Time" => Some(Tz::Africa__Lagos),
|
||||
|
||||
// Asia Pacific
|
||||
"AUS Eastern Standard Time" => Some(Tz::Australia__Sydney),
|
||||
"AUS Central Standard Time" => Some(Tz::Australia__Darwin),
|
||||
"W. Australia Standard Time" => Some(Tz::Australia__Perth),
|
||||
"Tasmania Standard Time" => Some(Tz::Australia__Hobart),
|
||||
"New Zealand Standard Time" => Some(Tz::Pacific__Auckland),
|
||||
"Fiji Standard Time" => Some(Tz::Pacific__Fiji),
|
||||
"Tonga Standard Time" => Some(Tz::Pacific__Tongatapu),
|
||||
|
||||
// South America
|
||||
"Argentina Standard Time" => Some(Tz::America__Buenos_Aires),
|
||||
"E. South America Standard Time" => Some(Tz::America__Sao_Paulo),
|
||||
"SA Eastern Standard Time" => Some(Tz::America__Cayenne),
|
||||
"SA Pacific Standard Time" => Some(Tz::America__Bogota),
|
||||
"SA Western Standard Time" => Some(Tz::America__La_Paz),
|
||||
"Pacific SA Standard Time" => Some(Tz::America__Santiago),
|
||||
"Venezuela Standard Time" => Some(Tz::America__Caracas),
|
||||
"Montevideo Standard Time" => Some(Tz::America__Montevideo),
|
||||
|
||||
// Try parsing as IANA name
|
||||
_ => tzid_str.parse::<Tz>().ok()
|
||||
}
|
||||
};
|
||||
|
||||
if let Some(tz) = tz_result {
|
||||
if let Some(dt_with_tz) = tz.from_local_datetime(&naive_dt).single() {
|
||||
return Some(dt_with_tz.with_timezone(&Utc));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// If no timezone info or parsing failed, treat as UTC (safer than local time assumptions)
|
||||
return Some(chrono::TimeZone::from_utc_datetime(&Utc, &naive_dt));
|
||||
}
|
||||
|
||||
None
|
||||
|
||||
Reference in New Issue
Block a user