use crate::models::ClientEvent; use serde::{Deserialize, Serialize}; #[derive(Debug, Clone, Serialize, Deserialize)] pub struct FormattedEvent { pub formatted_time: String, pub formatted_date_time: String, pub is_multi_day: bool, pub formatted_date_range: String, } /// Format time range for display pub fn format_time_range(start_time: &str, end_time: &str) -> String { format!("{} - {}", start_time, end_time) } /// Check if event appears to be multi-day based on date format pub fn is_multi_day_event(date: &str) -> bool { date.contains(" - ") } /// Format date and time for display, handling multi-day events pub fn format_date_time(date: &str, start_time: &str, end_time: &str) -> String { if is_multi_day_event(date) { // For multi-day events, integrate times with their respective dates let components: Vec<&str> = date.split(" - ").collect(); if components.len() == 2 { format!("{} at {} - {} at {}", components[0], start_time, components[1], end_time) } else { date.to_string() // Fallback to original date } } else { // Single day events: return just the date (time displayed separately) date.to_string() } } /// Format a client event with all display formatting logic pub fn format_event_for_display(event: &ClientEvent) -> FormattedEvent { let start_time = &event.start_time; let end_time = &event.end_time; // Derive formatted date from start_time since ClientEvent no longer has date field let formatted_date = format_date_from_timestamp(start_time); FormattedEvent { formatted_time: format_time_range(start_time, end_time), formatted_date_time: format_date_time(&formatted_date, start_time, end_time), is_multi_day: is_multi_day_event(&formatted_date), formatted_date_range: formatted_date, } } /// Extract formatted date from ISO timestamp fn format_date_from_timestamp(timestamp: &str) -> String { use chrono::{DateTime, FixedOffset}; if let Ok(dt) = DateTime::parse_from_str(timestamp, "%Y-%m-%dT%H:%M:%S%z") { dt.format("%A, %B %d, %Y").to_string() } else { "Date TBD".to_string() } } /// Format duration in minutes to human readable format pub fn format_duration_minutes(minutes: i64) -> String { if minutes < 60 { format!("{} min", minutes) } else { let hours = minutes / 60; let remaining_minutes = minutes % 60; if remaining_minutes == 0 { format!("{} hr", hours) } else { format!("{} hr {} min", hours, remaining_minutes) } } } /// Format spots remaining for events pub fn format_spots_remaining(current: Option, max: Option) -> Option { match (current, max) { (Some(current), Some(max)) => { let remaining = max.saturating_sub(current); if remaining == 0 { Some("Event Full".to_string()) } else { Some(format!("{} spots remaining", remaining)) } } _ => None, } } /// Check if event registration is full pub fn is_event_full(current: Option, max: Option) -> bool { match (current, max) { (Some(current), Some(max)) => current >= max, _ => false, } } #[cfg(test)] mod tests { use super::*; #[test] fn test_format_time_range() { assert_eq!(format_time_range("9:00 AM", "5:00 PM"), "9:00 AM - 5:00 PM"); assert_eq!(format_time_range("", ""), " - "); } #[test] fn test_is_multi_day_event() { assert!(is_multi_day_event("Saturday, Aug 30, 2025 - Sunday, Aug 31, 2025")); assert!(!is_multi_day_event("Saturday, Aug 30, 2025")); assert!(!is_multi_day_event("")); } #[test] fn test_format_date_time() { // Single day event let result = format_date_time("Saturday, Aug 30, 2025", "6:00 PM", "8:00 PM"); assert_eq!(result, "Saturday, Aug 30, 2025"); // Multi-day event let result = format_date_time( "Saturday, Aug 30, 2025 - Sunday, Aug 31, 2025", "6:00 PM", "6:00 AM" ); assert_eq!(result, "Saturday, Aug 30, 2025 at 6:00 PM - Sunday, Aug 31, 2025 at 6:00 AM"); } #[test] fn test_format_duration_minutes() { assert_eq!(format_duration_minutes(30), "30 min"); assert_eq!(format_duration_minutes(60), "1 hr"); assert_eq!(format_duration_minutes(90), "1 hr 30 min"); assert_eq!(format_duration_minutes(120), "2 hr"); } #[test] fn test_format_spots_remaining() { assert_eq!(format_spots_remaining(Some(8), Some(10)), Some("2 spots remaining".to_string())); assert_eq!(format_spots_remaining(Some(10), Some(10)), Some("Event Full".to_string())); assert_eq!(format_spots_remaining(None, Some(10)), None); assert_eq!(format_spots_remaining(Some(5), None), None); } #[test] fn test_is_event_full() { assert!(is_event_full(Some(10), Some(10))); assert!(is_event_full(Some(11), Some(10))); // Over capacity assert!(!is_event_full(Some(9), Some(10))); assert!(!is_event_full(None, Some(10))); assert!(!is_event_full(Some(5), None)); } }