use chrono::{ DateTime, Utc, }; use serde::{ Deserialize, Serialize, }; /// Represents a message in the iMessage database #[derive(Debug, Clone, Serialize, Deserialize)] pub struct Message { pub rowid: i64, pub guid: String, pub text: Option, pub service: Option, pub handle_id: i64, pub date: Option>, pub date_read: Option>, pub date_delivered: Option>, pub is_from_me: bool, pub is_read: bool, pub is_delivered: bool, pub is_sent: bool, pub is_emote: bool, pub is_audio_message: bool, pub cache_has_attachments: bool, pub associated_message_guid: Option, pub associated_message_type: i64, pub thread_originator_guid: Option, pub reply_to_guid: Option, pub is_spam: bool, } /// Represents a chat/conversation #[derive(Debug, Clone, Serialize, Deserialize)] pub struct Chat { pub rowid: i64, pub guid: String, pub chat_identifier: Option, pub service_name: Option, pub display_name: Option, pub group_id: Option, pub room_name: Option, pub is_archived: bool, pub is_filtered: bool, pub last_read_message_timestamp: Option>, } /// Helper function to convert Apple's Cocoa timestamp (seconds since /// 2001-01-01) to DateTime pub fn apple_timestamp_to_datetime(timestamp: i64) -> DateTime { // Apple's Cocoa timestamps are in nanoseconds since 2001-01-01 00:00:00 UTC // Convert to Unix timestamp (seconds since 1970-01-01 00:00:00 UTC) const APPLE_EPOCH_OFFSET: i64 = 978307200; // Seconds between 1970-01-01 and 2001-01-01 let seconds = timestamp / 1_000_000_000 + APPLE_EPOCH_OFFSET; let nanos = (timestamp % 1_000_000_000) as u32; DateTime::from_timestamp(seconds, nanos) .unwrap_or_else(|| DateTime::from_timestamp(0, 0).unwrap()) } /// Helper function to convert DateTime to Apple's Cocoa timestamp pub fn datetime_to_apple_timestamp(dt: DateTime) -> i64 { const APPLE_EPOCH_OFFSET: i64 = 978307200; let unix_timestamp = dt.timestamp(); let nanos = dt.timestamp_subsec_nanos() as i64; (unix_timestamp - APPLE_EPOCH_OFFSET) * 1_000_000_000 + nanos } #[cfg(test)] mod tests { use chrono::{ Datelike, TimeZone, Timelike, }; use super::*; #[test] fn test_apple_timestamp_to_datetime_zero() { let dt = apple_timestamp_to_datetime(0); assert_eq!(dt.year(), 2001); assert_eq!(dt.month(), 1); assert_eq!(dt.day(), 1); assert_eq!(dt.hour(), 0); assert_eq!(dt.minute(), 0); assert_eq!(dt.second(), 0); } #[test] fn test_apple_timestamp_to_datetime_known_value() { let timestamp = 694224000000000000i64; let dt = apple_timestamp_to_datetime(timestamp); assert_eq!(dt.year(), 2023); assert_eq!(dt.month(), 1); assert_eq!(dt.day(), 1); } #[test] fn test_apple_timestamp_roundtrip() { let original = 694224000000000000i64; let dt = apple_timestamp_to_datetime(original); let converted_back = datetime_to_apple_timestamp(dt); assert_eq!(original, converted_back); } #[test] fn test_datetime_to_apple_timestamp_epoch() { let dt = Utc.with_ymd_and_hms(2001, 1, 1, 0, 0, 0).unwrap(); let timestamp = datetime_to_apple_timestamp(dt); assert_eq!(timestamp, 0); } #[test] fn test_negative_apple_timestamp() { let timestamp = -31536000000000000i64; let dt = apple_timestamp_to_datetime(timestamp); assert_eq!(dt.year(), 2000); } }