church-core/src/utils/formatting.rs
RTSDA 4d6b23beb3
Some checks are pending
iOS UniFFI Build / build-ios (push) Waiting to run
Initial commit: Church Core Rust library
Add church management API library with cross-platform support for iOS, Android, and WASM.
Features include event management, bulletin handling, contact forms, and authentication.
2025-08-16 19:25:01 -04:00

159 lines
5.2 KiB
Rust

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<u32>, max: Option<u32>) -> Option<String> {
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<u32>, max: Option<u32>) -> 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));
}
}