Fix all-day event date display bug: events created 9/4-9/6 now show correctly instead of 9/3-9/5
All checks were successful
Build and Push Docker Image / docker (push) Successful in 1m9s
All checks were successful
Build and Push Docker Image / docker (push) Successful in 1m9s
- Backend: Store all-day events at noon UTC instead of midnight to avoid timezone boundary issues - Backend: Remove local timezone conversion for all-day events in series handler - Frontend: Skip timezone conversion when extracting dates from all-day events for display - Frontend: Extract dates directly from UTC for all-day events in event_spans_date function The issue was that timezone conversion of UTC midnight could shift dates backward in western timezones. Now all-day events use noon UTC storage and pure date extraction without timezone conversion. 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
@@ -852,10 +852,11 @@ fn parse_event_datetime(
|
|||||||
.map_err(|_| format!("Invalid date format: {}. Expected YYYY-MM-DD", date_str))?;
|
.map_err(|_| format!("Invalid date format: {}. Expected YYYY-MM-DD", date_str))?;
|
||||||
|
|
||||||
if all_day {
|
if all_day {
|
||||||
// For all-day events, use midnight UTC
|
// For all-day events, use noon UTC to avoid timezone boundary issues
|
||||||
|
// This ensures the date remains correct when converted to any local timezone
|
||||||
let datetime = date
|
let datetime = date
|
||||||
.and_hms_opt(0, 0, 0)
|
.and_hms_opt(12, 0, 0)
|
||||||
.ok_or_else(|| "Failed to create midnight datetime".to_string())?;
|
.ok_or_else(|| "Failed to create noon datetime".to_string())?;
|
||||||
Ok(Utc.from_utc_datetime(&datetime))
|
Ok(Utc.from_utc_datetime(&datetime))
|
||||||
} else {
|
} else {
|
||||||
// Parse the time
|
// Parse the time
|
||||||
|
|||||||
@@ -397,8 +397,9 @@ pub async fn update_event_series(
|
|||||||
};
|
};
|
||||||
|
|
||||||
let (start_datetime, end_datetime) = if request.all_day {
|
let (start_datetime, end_datetime) = if request.all_day {
|
||||||
|
// For all-day events, use noon UTC to avoid timezone boundary issues
|
||||||
let start_dt = start_date
|
let start_dt = start_date
|
||||||
.and_hms_opt(0, 0, 0)
|
.and_hms_opt(12, 0, 0)
|
||||||
.ok_or_else(|| ApiError::BadRequest("Invalid start date".to_string()))?;
|
.ok_or_else(|| ApiError::BadRequest("Invalid start date".to_string()))?;
|
||||||
|
|
||||||
// For all-day events, also preserve the original date pattern
|
// For all-day events, also preserve the original date pattern
|
||||||
@@ -414,20 +415,13 @@ pub async fn update_event_series(
|
|||||||
};
|
};
|
||||||
|
|
||||||
let end_dt = end_date
|
let end_dt = end_date
|
||||||
.and_hms_opt(23, 59, 59)
|
.and_hms_opt(12, 0, 0)
|
||||||
.ok_or_else(|| ApiError::BadRequest("Invalid end date".to_string()))?;
|
.ok_or_else(|| ApiError::BadRequest("Invalid end date".to_string()))?;
|
||||||
|
|
||||||
// Convert from local time to UTC
|
// For all-day events, use UTC directly (no local conversion needed)
|
||||||
let start_local = chrono::Local.from_local_datetime(&start_dt)
|
|
||||||
.single()
|
|
||||||
.ok_or_else(|| ApiError::BadRequest("Ambiguous start datetime".to_string()))?;
|
|
||||||
let end_local = chrono::Local.from_local_datetime(&end_dt)
|
|
||||||
.single()
|
|
||||||
.ok_or_else(|| ApiError::BadRequest("Ambiguous end datetime".to_string()))?;
|
|
||||||
|
|
||||||
(
|
(
|
||||||
start_local.with_timezone(&chrono::Utc),
|
chrono::Utc.from_utc_datetime(&start_dt),
|
||||||
end_local.with_timezone(&chrono::Utc),
|
chrono::Utc.from_utc_datetime(&end_dt),
|
||||||
)
|
)
|
||||||
} else {
|
} else {
|
||||||
let start_time = if !request.start_time.is_empty() {
|
let start_time = if !request.start_time.is_empty() {
|
||||||
|
|||||||
@@ -1320,11 +1320,23 @@ fn calculate_event_layout(events: &[VEvent], date: NaiveDate, time_increment: u3
|
|||||||
|
|
||||||
// Check if an all-day event spans the given date
|
// Check if an all-day event spans the given date
|
||||||
fn event_spans_date(event: &VEvent, date: NaiveDate) -> bool {
|
fn event_spans_date(event: &VEvent, date: NaiveDate) -> bool {
|
||||||
let start_date = event.dtstart.with_timezone(&Local).date_naive();
|
let start_date = if event.all_day {
|
||||||
|
// For all-day events, extract date directly from UTC without timezone conversion
|
||||||
|
// since all-day events are stored at noon UTC to avoid timezone boundary issues
|
||||||
|
event.dtstart.date_naive()
|
||||||
|
} else {
|
||||||
|
event.dtstart.with_timezone(&Local).date_naive()
|
||||||
|
};
|
||||||
|
|
||||||
let end_date = if let Some(dtend) = event.dtend {
|
let end_date = if let Some(dtend) = event.dtend {
|
||||||
// For all-day events, dtend is often set to the day after the last day
|
if event.all_day {
|
||||||
// So we need to subtract a day to get the actual last day of the event
|
// For all-day events, dtend is set to the day after the last day (RFC 5545)
|
||||||
dtend.with_timezone(&Local).date_naive() - chrono::Duration::days(1)
|
// Extract date directly from UTC and subtract a day to get actual last day
|
||||||
|
dtend.date_naive() - chrono::Duration::days(1)
|
||||||
|
} else {
|
||||||
|
// For timed events, use timezone conversion
|
||||||
|
dtend.with_timezone(&Local).date_naive()
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
// Single day event
|
// Single day event
|
||||||
start_date
|
start_date
|
||||||
|
|||||||
Reference in New Issue
Block a user