From c607aa135ce1bd24fcc92ea711eeea72b574ce2b Mon Sep 17 00:00:00 2001 From: Benjamin Slingo Date: Mon, 8 Sep 2025 00:18:47 -0400 Subject: [PATCH] Fix datetime parsing bug in event submission - Fix timezone detection logic to only consider dashes after 'T' as timezone indicators - Reorder multipart datetime parsing to try flexible formats before strict RFC3339 - Resolves 400 "Invalid RFC3339 datetime" error for format like "2025-10-07T00:13" - Event submissions now work correctly with local church time format --- src/utils/datetime.rs | 8 +++++++- src/utils/multipart_helpers.rs | 28 ++++++++++++++-------------- 2 files changed, 21 insertions(+), 15 deletions(-) diff --git a/src/utils/datetime.rs b/src/utils/datetime.rs index b477be4..a70abb2 100644 --- a/src/utils/datetime.rs +++ b/src/utils/datetime.rs @@ -67,7 +67,13 @@ pub fn parse_datetime_with_timezone( ) -> Result { let timezone = timezone.unwrap_or(DEFAULT_CHURCH_TIMEZONE); - if datetime_str.ends_with('Z') || datetime_str.contains('+') || datetime_str.contains('-') { + // Check if this looks like RFC3339 with timezone info (ends with Z, contains +, or has timezone offset after T) + let has_timezone = datetime_str.ends_with('Z') || + datetime_str.contains('+') || + (datetime_str.contains('T') && + datetime_str.split('T').nth(1).map_or(false, |time_part| time_part.contains('-'))); + + if has_timezone { let dt = DateTime::parse_from_rfc3339(datetime_str) .map_err(|_| ApiError::BadRequest("Invalid RFC3339 datetime".to_string()))?; DateTimeWithTimezone::new(dt.with_timezone(&Utc), timezone) diff --git a/src/utils/multipart_helpers.rs b/src/utils/multipart_helpers.rs index 501ba9c..3727e8f 100644 --- a/src/utils/multipart_helpers.rs +++ b/src/utils/multipart_helpers.rs @@ -71,20 +71,20 @@ impl MultipartProcessor { pub fn get_datetime(&self, field_name: &str) -> Result> { let datetime_str = self.get_required_string(field_name)?; - // First try the shared timezone-aware parsing function - if let Ok(utc_time) = crate::utils::datetime::parse_event_datetime_to_utc(&datetime_str) { - return Ok(utc_time); + // Try parsing as RFC3339 first (already has timezone info) + if let Ok(dt) = DateTime::parse_from_rfc3339(&datetime_str) { + return Ok(dt.to_utc()); } - // Fallback to legacy formats for backward compatibility - // These will now be treated as EST/EDT times and converted to UTC + // Try flexible local formats for backward compatibility + // These will be treated as EST/EDT times and converted to UTC let formats = [ - "%Y-%m-%dT%H:%M", - "%Y-%m-%dT%H:%M:%S", - "%Y-%m-%d %H:%M", - "%Y-%m-%d %H:%M:%S", - "%m/%d/%Y %H:%M", - "%m/%d/%Y %I:%M %p", + "%Y-%m-%dT%H:%M", // 2025-10-08T00:09 + "%Y-%m-%dT%H:%M:%S", // 2025-10-08T00:09:00 + "%Y-%m-%d %H:%M", // 2025-10-08 00:09 + "%Y-%m-%d %H:%M:%S", // 2025-10-08 00:09:00 + "%m/%d/%Y %H:%M", // 10/08/2025 00:09 + "%m/%d/%Y %I:%M %p", // 10/08/2025 12:09 AM ]; for format in &formats { @@ -95,9 +95,9 @@ impl MultipartProcessor { } } - // Try parsing as RFC3339 (already has timezone info) - if let Ok(dt) = DateTime::parse_from_rfc3339(&datetime_str) { - return Ok(dt.to_utc()); + // Last resort: try the shared timezone-aware parsing function + if let Ok(utc_time) = crate::utils::datetime::parse_event_datetime_to_utc(&datetime_str) { + return Ok(utc_time); } Err(ApiError::ValidationError(format!(