From 0609a99839d26bfb29835c177c7315284b006345 Mon Sep 17 00:00:00 2001 From: Connor Johnstone Date: Wed, 3 Sep 2025 16:17:32 -0400 Subject: [PATCH] Fix timezone bug in event creation MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Events were appearing 4 hours earlier than selected time due to incorrect timezone handling in backend. The issue was treating frontend local time as if it was already in UTC. - Fix parse_event_datetime() in events.rs to properly convert local time to UTC - Fix all datetime conversions in series.rs to use Local timezone conversion - Replace Utc.from_utc_datetime() with proper Local.from_local_datetime() - Add timezone conversion using with_timezone(&Utc) for accurate UTC storage Now when user selects 5:00 AM, it correctly stores as UTC equivalent and displays back at 5:00 AM local time. 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude --- backend/src/handlers/events.rs | 10 ++++--- backend/src/handlers/series.rs | 48 ++++++++++++++++++++++++++++------ 2 files changed, 47 insertions(+), 11 deletions(-) diff --git a/backend/src/handlers/events.rs b/backend/src/handlers/events.rs index 0290d6d..cb19856 100644 --- a/backend/src/handlers/events.rs +++ b/backend/src/handlers/events.rs @@ -845,7 +845,7 @@ fn parse_event_datetime( time_str: &str, all_day: bool, ) -> Result, String> { - use chrono::{NaiveDate, NaiveDateTime, NaiveTime, TimeZone, Utc}; + use chrono::{Local, NaiveDate, NaiveDateTime, NaiveTime, TimeZone, Utc}; // Parse the date let date = NaiveDate::parse_from_str(date_str, "%Y-%m-%d") @@ -865,7 +865,11 @@ fn parse_event_datetime( // Combine date and time let datetime = NaiveDateTime::new(date, time); - // Assume local time and convert to UTC (in a real app, you'd want timezone support) - Ok(Utc.from_utc_datetime(&datetime)) + // Treat the datetime as local time and convert to UTC + let local_datetime = Local.from_local_datetime(&datetime) + .single() + .ok_or_else(|| "Ambiguous local datetime".to_string())?; + + Ok(local_datetime.with_timezone(&Utc)) } } diff --git a/backend/src/handlers/series.rs b/backend/src/handlers/series.rs index 92082b5..d8911eb 100644 --- a/backend/src/handlers/series.rs +++ b/backend/src/handlers/series.rs @@ -130,9 +130,17 @@ pub async fn create_event_series( .and_hms_opt(23, 59, 59) .ok_or_else(|| ApiError::BadRequest("Invalid end date".to_string()))?; + // Convert from local time to UTC + 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()))?; + ( - chrono::Utc.from_utc_datetime(&start_dt), - chrono::Utc.from_utc_datetime(&end_dt), + start_local.with_timezone(&chrono::Utc), + end_local.with_timezone(&chrono::Utc), ) } else { // Parse times for timed events @@ -163,9 +171,17 @@ pub async fn create_event_series( start_date.and_time(end_time) }; + // Convert from local time to UTC + 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()))?; + ( - chrono::Utc.from_utc_datetime(&start_dt), - chrono::Utc.from_utc_datetime(&end_dt), + start_local.with_timezone(&chrono::Utc), + end_local.with_timezone(&chrono::Utc), ) }; @@ -401,9 +417,17 @@ pub async fn update_event_series( .and_hms_opt(23, 59, 59) .ok_or_else(|| ApiError::BadRequest("Invalid end date".to_string()))?; + // Convert from local time to UTC + 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()))?; + ( - chrono::Utc.from_utc_datetime(&start_dt), - chrono::Utc.from_utc_datetime(&end_dt), + start_local.with_timezone(&chrono::Utc), + end_local.with_timezone(&chrono::Utc), ) } else { let start_time = if !request.start_time.is_empty() { @@ -438,9 +462,17 @@ pub async fn update_event_series( (chrono::Utc.from_utc_datetime(&start_dt) + original_duration).naive_utc() }; + // Convert from local time to UTC + 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()))?; + ( - chrono::Utc.from_utc_datetime(&start_dt), - chrono::Utc.from_utc_datetime(&end_dt), + start_local.with_timezone(&chrono::Utc), + end_local.with_timezone(&chrono::Utc), ) };