Skip to repository content

tenant.openagents/omega

No repository description is available.

OpenAgents Git authority 2026-07-28T03:55:40.134Z Public web read
NIP-34 coordinate30617:7649603503856e5148d571eac2766b288a8ff1e9e35d380337a1d2b0015b4f92:omega
MaintainersHidden in public view
References2 branches · 1 tag
Read-only clonegit clone https://openagents.com/git/tenant.openagents/omega.git
Browse files

time_format.rs

1162 lines · 40.1 KB · rust
1use time::{OffsetDateTime, UtcOffset};
2
3/// The formatting style for a timestamp.
4#[derive(Debug, Clone, Copy, PartialEq, Eq)]
5pub enum TimestampFormat {
6    /// Formats the timestamp as an absolute time, e.g. "2021-12-31 3:00AM".
7    Absolute,
8    /// Formats the timestamp as an absolute time.
9    /// If the message is from today or yesterday the date will be replaced with "Today at x" or "Yesterday at x" respectively.
10    /// E.g. "Today at 12:00 PM", "Yesterday at 11:00 AM", "2021-12-31 3:00AM".
11    EnhancedAbsolute,
12    /// Formats the timestamp as an absolute time, using month name, day of month, year. e.g. "Feb. 24, 2024".
13    MediumAbsolute,
14    /// Formats the timestamp as a relative time, e.g. "just now", "1 minute ago", "2 hours ago", "2 months ago".
15    Relative,
16}
17
18/// Formats a timestamp, which respects the user's date and time preferences/custom format.
19pub fn format_localized_timestamp(
20    timestamp: OffsetDateTime,
21    reference: OffsetDateTime,
22    timezone: UtcOffset,
23    format: TimestampFormat,
24) -> String {
25    let timestamp_local = timestamp.to_offset(timezone);
26    let reference_local = reference.to_offset(timezone);
27    format_local_timestamp(timestamp_local, reference_local, format)
28}
29
30/// Formats a timestamp, which respects the user's date and time preferences/custom format.
31pub fn format_local_timestamp(
32    timestamp: OffsetDateTime,
33    reference: OffsetDateTime,
34    format: TimestampFormat,
35) -> String {
36    match format {
37        TimestampFormat::Absolute => format_absolute_timestamp(timestamp, reference, false),
38        TimestampFormat::EnhancedAbsolute => format_absolute_timestamp(timestamp, reference, true),
39        TimestampFormat::MediumAbsolute => format_absolute_timestamp_medium(timestamp, reference),
40        TimestampFormat::Relative => format_relative_time(timestamp, reference)
41            .unwrap_or_else(|| format_relative_date(timestamp, reference)),
42    }
43}
44
45/// Formats the date component of a timestamp
46pub fn format_date(
47    timestamp: OffsetDateTime,
48    reference: OffsetDateTime,
49    enhanced_formatting: bool,
50) -> String {
51    format_absolute_date(timestamp, reference, enhanced_formatting)
52}
53
54/// Formats the time component of a timestamp
55pub fn format_time(timestamp: OffsetDateTime) -> String {
56    format_absolute_time(timestamp)
57}
58
59/// Formats the date component of a timestamp in medium style
60pub fn format_date_medium(
61    timestamp: OffsetDateTime,
62    reference: OffsetDateTime,
63    enhanced_formatting: bool,
64) -> String {
65    format_absolute_date_medium(timestamp, reference, enhanced_formatting)
66}
67
68fn format_absolute_date(
69    timestamp: OffsetDateTime,
70    reference: OffsetDateTime,
71    #[allow(unused_variables)] enhanced_date_formatting: bool,
72) -> String {
73    #[cfg(target_os = "macos")]
74    {
75        if !enhanced_date_formatting {
76            return macos::format_date(&timestamp);
77        }
78
79        let timestamp_date = timestamp.date();
80        let reference_date = reference.date();
81        if timestamp_date == reference_date {
82            "Today".to_string()
83        } else if reference_date.previous_day() == Some(timestamp_date) {
84            "Yesterday".to_string()
85        } else {
86            macos::format_date(&timestamp)
87        }
88    }
89    #[cfg(target_os = "windows")]
90    {
91        if !enhanced_date_formatting {
92            return windows::format_date(&timestamp);
93        }
94
95        let timestamp_date = timestamp.date();
96        let reference_date = reference.date();
97        if timestamp_date == reference_date {
98            "Today".to_string()
99        } else if reference_date.previous_day() == Some(timestamp_date) {
100            "Yesterday".to_string()
101        } else {
102            windows::format_date(&timestamp)
103        }
104    }
105    #[cfg(not(any(target_os = "macos", target_os = "windows")))]
106    {
107        // todo(linux) respect user's date/time preferences
108        let current_locale = CURRENT_LOCALE
109            .get_or_init(|| sys_locale::get_locale().unwrap_or_else(|| String::from("en-US")));
110        format_timestamp_naive_date(
111            timestamp,
112            reference,
113            is_12_hour_time_by_locale(current_locale.as_str()),
114        )
115    }
116}
117
118fn format_absolute_time(timestamp: OffsetDateTime) -> String {
119    #[cfg(target_os = "macos")]
120    {
121        macos::format_time(&timestamp)
122    }
123    #[cfg(target_os = "windows")]
124    {
125        windows::format_time(&timestamp)
126    }
127    #[cfg(not(any(target_os = "macos", target_os = "windows")))]
128    {
129        // todo(linux) respect user's date/time preferences
130        let current_locale = CURRENT_LOCALE
131            .get_or_init(|| sys_locale::get_locale().unwrap_or_else(|| String::from("en-US")));
132        format_timestamp_naive_time(
133            timestamp,
134            is_12_hour_time_by_locale(current_locale.as_str()),
135        )
136    }
137}
138
139fn format_absolute_timestamp(
140    timestamp: OffsetDateTime,
141    reference: OffsetDateTime,
142    #[allow(unused_variables)] enhanced_date_formatting: bool,
143) -> String {
144    #[cfg(any(target_os = "macos", target_os = "windows"))]
145    {
146        if !enhanced_date_formatting {
147            return format!(
148                "{} {}",
149                format_absolute_date(timestamp, reference, enhanced_date_formatting),
150                format_absolute_time(timestamp)
151            );
152        }
153
154        let timestamp_date = timestamp.date();
155        let reference_date = reference.date();
156        if timestamp_date == reference_date {
157            format!("Today at {}", format_absolute_time(timestamp))
158        } else if reference_date.previous_day() == Some(timestamp_date) {
159            format!("Yesterday at {}", format_absolute_time(timestamp))
160        } else {
161            format!(
162                "{} {}",
163                format_absolute_date(timestamp, reference, enhanced_date_formatting),
164                format_absolute_time(timestamp)
165            )
166        }
167    }
168    #[cfg(not(any(target_os = "macos", target_os = "windows")))]
169    {
170        // todo(linux) respect user's date/time preferences
171        format_timestamp_fallback(timestamp, reference)
172    }
173}
174
175fn format_absolute_date_medium(
176    timestamp: OffsetDateTime,
177    reference: OffsetDateTime,
178    enhanced_formatting: bool,
179) -> String {
180    #[cfg(target_os = "macos")]
181    {
182        if !enhanced_formatting {
183            return macos::format_date_medium(&timestamp);
184        }
185
186        let timestamp_date = timestamp.date();
187        let reference_date = reference.date();
188        if timestamp_date == reference_date {
189            "Today".to_string()
190        } else if reference_date.previous_day() == Some(timestamp_date) {
191            "Yesterday".to_string()
192        } else {
193            macos::format_date_medium(&timestamp)
194        }
195    }
196    #[cfg(target_os = "windows")]
197    {
198        if !enhanced_formatting {
199            return windows::format_date_medium(&timestamp);
200        }
201
202        let timestamp_date = timestamp.date();
203        let reference_date = reference.date();
204        if timestamp_date == reference_date {
205            "Today".to_string()
206        } else if reference_date.previous_day() == Some(timestamp_date) {
207            "Yesterday".to_string()
208        } else {
209            windows::format_date_medium(&timestamp)
210        }
211    }
212    #[cfg(not(any(target_os = "macos", target_os = "windows")))]
213    {
214        // todo(linux) respect user's date/time preferences
215        let current_locale = CURRENT_LOCALE
216            .get_or_init(|| sys_locale::get_locale().unwrap_or_else(|| String::from("en-US")));
217        if !enhanced_formatting {
218            return format_timestamp_naive_date_medium(
219                timestamp,
220                is_12_hour_time_by_locale(current_locale.as_str()),
221            );
222        }
223
224        let timestamp_date = timestamp.date();
225        let reference_date = reference.date();
226        if timestamp_date == reference_date {
227            "Today".to_string()
228        } else if reference_date.previous_day() == Some(timestamp_date) {
229            "Yesterday".to_string()
230        } else {
231            format_timestamp_naive_date_medium(
232                timestamp,
233                is_12_hour_time_by_locale(current_locale.as_str()),
234            )
235        }
236    }
237}
238
239fn format_absolute_timestamp_medium(
240    timestamp: OffsetDateTime,
241    reference: OffsetDateTime,
242) -> String {
243    #[cfg(target_os = "macos")]
244    {
245        format_absolute_date_medium(timestamp, reference, false)
246    }
247    #[cfg(target_os = "windows")]
248    {
249        format_absolute_date_medium(timestamp, reference, false)
250    }
251    #[cfg(not(any(target_os = "macos", target_os = "windows")))]
252    {
253        // todo(linux) respect user's date/time preferences
254        // todo(windows) respect user's date/time preferences
255        format_timestamp_fallback(timestamp, reference)
256    }
257}
258
259fn format_relative_time(timestamp: OffsetDateTime, reference: OffsetDateTime) -> Option<String> {
260    let difference = reference - timestamp;
261    let minutes = difference.whole_minutes();
262    match minutes {
263        0 => Some("Just now".to_string()),
264        1 => Some("1 minute ago".to_string()),
265        2..=59 => Some(format!("{} minutes ago", minutes)),
266        _ => {
267            let hours = difference.whole_hours();
268            match hours {
269                1 => Some("1 hour ago".to_string()),
270                2..=23 => Some(format!("{} hours ago", hours)),
271                _ => None,
272            }
273        }
274    }
275}
276
277fn format_relative_date(timestamp: OffsetDateTime, reference: OffsetDateTime) -> String {
278    let timestamp_date = timestamp.date();
279    let reference_date = reference.date();
280    let difference = reference_date - timestamp_date;
281    let days = difference.whole_days();
282    match days {
283        0 => "Today".to_string(),
284        1 => "Yesterday".to_string(),
285        2..=6 => format!("{} days ago", days),
286        _ => {
287            let weeks = difference.whole_weeks();
288            match weeks {
289                1 => "1 week ago".to_string(),
290                2..=4 => format!("{} weeks ago", weeks),
291                _ => {
292                    let month_diff = calculate_month_difference(timestamp, reference);
293                    match month_diff {
294                        0..=1 => "1 month ago".to_string(),
295                        2..=11 => format!("{} months ago", month_diff),
296                        // Match git's `show_date_relative` behavior: for dates under 5 years old,
297                        // include both years and months so, for example, 22 months is shown as
298                        // "1 year, 10 months ago" instead of being collapsed to "1 year ago".
299                        12..60 => format_compound_year_month(month_diff),
300                        // Beyond 5 years, round to the nearest year.
301                        months => {
302                            let years = (months + 6) / 12;
303                            format!("{years} years ago")
304                        }
305                    }
306                }
307            }
308        }
309    }
310}
311
312fn format_compound_year_month(month_diff: usize) -> String {
313    let years = month_diff / 12;
314    let months = month_diff % 12;
315    let year_unit = if years == 1 { "year" } else { "years" };
316    if months == 0 {
317        format!("{years} {year_unit} ago")
318    } else {
319        let month_unit = if months == 1 { "month" } else { "months" };
320        format!("{years} {year_unit}, {months} {month_unit} ago")
321    }
322}
323
324/// Calculates the difference in months between two timestamps.
325/// The reference timestamp should always be greater than the timestamp.
326fn calculate_month_difference(timestamp: OffsetDateTime, reference: OffsetDateTime) -> usize {
327    let timestamp_year = timestamp.year();
328    let reference_year = reference.year();
329    let timestamp_month: u8 = timestamp.month().into();
330    let reference_month: u8 = reference.month().into();
331
332    let month_diff = if reference_month >= timestamp_month {
333        reference_month as usize - timestamp_month as usize
334    } else {
335        12 - timestamp_month as usize + reference_month as usize
336    };
337
338    let year_diff = (reference_year - timestamp_year) as usize;
339    if year_diff == 0 {
340        reference_month as usize - timestamp_month as usize
341    } else if month_diff == 0 {
342        year_diff * 12
343    } else if timestamp_month > reference_month {
344        (year_diff - 1) * 12 + month_diff
345    } else {
346        year_diff * 12 + month_diff
347    }
348}
349
350/// Formats a timestamp, which is either in 12-hour or 24-hour time format.
351/// Note:
352/// This function does not respect the user's date and time preferences.
353/// This should only be used as a fallback mechanism when the OS time formatting fails.
354fn format_timestamp_naive_time(timestamp_local: OffsetDateTime, is_12_hour_time: bool) -> String {
355    let timestamp_local_hour = timestamp_local.hour();
356    let timestamp_local_minute = timestamp_local.minute();
357
358    let (hour, meridiem) = if is_12_hour_time {
359        let meridiem = if timestamp_local_hour >= 12 {
360            "PM"
361        } else {
362            "AM"
363        };
364
365        let hour_12 = match timestamp_local_hour {
366            0 => 12,                              // Midnight
367            13..=23 => timestamp_local_hour - 12, // PM hours
368            _ => timestamp_local_hour,            // AM hours
369        };
370
371        (hour_12, Some(meridiem))
372    } else {
373        (timestamp_local_hour, None)
374    };
375
376    match meridiem {
377        Some(meridiem) => format!("{}:{:02} {}", hour, timestamp_local_minute, meridiem),
378        None => format!("{:02}:{:02}", hour, timestamp_local_minute),
379    }
380}
381
382#[cfg(not(target_os = "macos"))]
383fn format_timestamp_naive_date(
384    timestamp_local: OffsetDateTime,
385    reference_local: OffsetDateTime,
386    is_12_hour_time: bool,
387) -> String {
388    let reference_local_date = reference_local.date();
389    let timestamp_local_date = timestamp_local.date();
390
391    if timestamp_local_date == reference_local_date {
392        "Today".to_string()
393    } else if reference_local_date.previous_day() == Some(timestamp_local_date) {
394        "Yesterday".to_string()
395    } else {
396        match is_12_hour_time {
397            true => format!(
398                "{:02}/{:02}/{}",
399                timestamp_local_date.month() as u32,
400                timestamp_local_date.day(),
401                timestamp_local_date.year()
402            ),
403            false => format!(
404                "{:02}/{:02}/{}",
405                timestamp_local_date.day(),
406                timestamp_local_date.month() as u32,
407                timestamp_local_date.year()
408            ),
409        }
410    }
411}
412
413#[cfg(not(any(target_os = "macos", target_os = "windows")))]
414fn format_timestamp_naive_date_medium(
415    timestamp_local: OffsetDateTime,
416    is_12_hour_time: bool,
417) -> String {
418    let timestamp_local_date = timestamp_local.date();
419
420    match is_12_hour_time {
421        true => format!(
422            "{:02}/{:02}/{}",
423            timestamp_local_date.month() as u32,
424            timestamp_local_date.day(),
425            timestamp_local_date.year()
426        ),
427        false => format!(
428            "{:02}/{:02}/{}",
429            timestamp_local_date.day(),
430            timestamp_local_date.month() as u32,
431            timestamp_local_date.year()
432        ),
433    }
434}
435
436pub fn format_timestamp_naive(
437    timestamp_local: OffsetDateTime,
438    reference_local: OffsetDateTime,
439    is_12_hour_time: bool,
440) -> String {
441    let formatted_time = format_timestamp_naive_time(timestamp_local, is_12_hour_time);
442    let reference_local_date = reference_local.date();
443    let timestamp_local_date = timestamp_local.date();
444
445    if timestamp_local_date == reference_local_date {
446        format!("Today at {}", formatted_time)
447    } else if reference_local_date.previous_day() == Some(timestamp_local_date) {
448        format!("Yesterday at {}", formatted_time)
449    } else {
450        let formatted_date = match is_12_hour_time {
451            true => format!(
452                "{:02}/{:02}/{}",
453                timestamp_local_date.month() as u32,
454                timestamp_local_date.day(),
455                timestamp_local_date.year()
456            ),
457            false => format!(
458                "{:02}/{:02}/{}",
459                timestamp_local_date.day(),
460                timestamp_local_date.month() as u32,
461                timestamp_local_date.year()
462            ),
463        };
464        format!("{} {}", formatted_date, formatted_time)
465    }
466}
467
468#[cfg(not(any(target_os = "macos", target_os = "windows")))]
469static CURRENT_LOCALE: std::sync::OnceLock<String> = std::sync::OnceLock::new();
470
471#[cfg(not(any(target_os = "macos", target_os = "windows")))]
472fn format_timestamp_fallback(timestamp: OffsetDateTime, reference: OffsetDateTime) -> String {
473    let current_locale = CURRENT_LOCALE
474        .get_or_init(|| sys_locale::get_locale().unwrap_or_else(|| String::from("en-US")));
475
476    let is_12_hour_time = is_12_hour_time_by_locale(current_locale.as_str());
477    format_timestamp_naive(timestamp, reference, is_12_hour_time)
478}
479
480/// Returns `true` if the locale is recognized as a 12-hour time locale.
481#[cfg(not(any(target_os = "macos", target_os = "windows")))]
482fn is_12_hour_time_by_locale(locale: &str) -> bool {
483    [
484        "es-MX", "es-CO", "es-SV", "es-NI",
485        "es-HN", // Mexico, Colombia, El Salvador, Nicaragua, Honduras
486        "en-US", "en-CA", "en-AU", "en-NZ", // U.S, Canada, Australia, New Zealand
487        "ar-SA", "ar-EG", "ar-JO", // Saudi Arabia, Egypt, Jordan
488        "en-IN", "hi-IN", // India, Hindu
489        "en-PK", "ur-PK", // Pakistan, Urdu
490        "en-PH", "fil-PH", // Philippines, Filipino
491        "bn-BD", "ccp-BD", // Bangladesh, Chakma
492        "en-IE", "ga-IE", // Ireland, Irish
493        "en-MY", "ms-MY", // Malaysia, Malay
494    ]
495    .contains(&locale)
496}
497
498#[cfg(target_os = "macos")]
499mod macos {
500    use core_foundation::base::TCFType;
501    use core_foundation::date::CFAbsoluteTime;
502    use core_foundation::string::CFString;
503    use core_foundation_sys::date_formatter::CFDateFormatterCreateStringWithAbsoluteTime;
504    use core_foundation_sys::date_formatter::CFDateFormatterRef;
505    use core_foundation_sys::locale::CFLocaleRef;
506    use core_foundation_sys::{
507        base::kCFAllocatorDefault,
508        date_formatter::{
509            CFDateFormatterCreate, kCFDateFormatterMediumStyle, kCFDateFormatterNoStyle,
510            kCFDateFormatterShortStyle,
511        },
512        locale::CFLocaleCopyCurrent,
513    };
514
515    pub fn format_time(timestamp: &time::OffsetDateTime) -> String {
516        format_with_date_formatter(timestamp, TIME_FORMATTER.with(|f| *f))
517    }
518
519    pub fn format_date(timestamp: &time::OffsetDateTime) -> String {
520        format_with_date_formatter(timestamp, DATE_FORMATTER.with(|f| *f))
521    }
522
523    pub fn format_date_medium(timestamp: &time::OffsetDateTime) -> String {
524        format_with_date_formatter(timestamp, MEDIUM_DATE_FORMATTER.with(|f| *f))
525    }
526
527    fn format_with_date_formatter(
528        timestamp: &time::OffsetDateTime,
529        fmt: CFDateFormatterRef,
530    ) -> String {
531        const UNIX_TO_CF_ABSOLUTE_TIME_OFFSET: i64 = 978307200;
532        // Convert timestamp to macOS absolute time
533        let timestamp_macos = timestamp.unix_timestamp() - UNIX_TO_CF_ABSOLUTE_TIME_OFFSET;
534        let cf_absolute_time = timestamp_macos as CFAbsoluteTime;
535        unsafe {
536            let s = CFDateFormatterCreateStringWithAbsoluteTime(
537                kCFAllocatorDefault,
538                fmt,
539                cf_absolute_time,
540            );
541            CFString::wrap_under_create_rule(s).to_string()
542        }
543    }
544
545    thread_local! {
546        static CURRENT_LOCALE: CFLocaleRef = unsafe { CFLocaleCopyCurrent() };
547        static TIME_FORMATTER: CFDateFormatterRef = unsafe {
548            CFDateFormatterCreate(
549                kCFAllocatorDefault,
550                CURRENT_LOCALE.with(|locale| *locale),
551                kCFDateFormatterNoStyle,
552                kCFDateFormatterShortStyle,
553            )
554        };
555        static DATE_FORMATTER: CFDateFormatterRef = unsafe {
556            CFDateFormatterCreate(
557                kCFAllocatorDefault,
558                CURRENT_LOCALE.with(|locale| *locale),
559                kCFDateFormatterShortStyle,
560                kCFDateFormatterNoStyle,
561            )
562        };
563
564        static MEDIUM_DATE_FORMATTER: CFDateFormatterRef = unsafe {
565            CFDateFormatterCreate(
566                kCFAllocatorDefault,
567                CURRENT_LOCALE.with(|locale| *locale),
568                kCFDateFormatterMediumStyle,
569                kCFDateFormatterNoStyle,
570            )
571        };
572    }
573}
574
575#[cfg(target_os = "windows")]
576mod windows {
577    use windows::Globalization::DateTimeFormatting::DateTimeFormatter;
578
579    pub fn format_time(timestamp: &time::OffsetDateTime) -> String {
580        format_with_formatter(DateTimeFormatter::ShortTime(), timestamp, true)
581    }
582
583    pub fn format_date(timestamp: &time::OffsetDateTime) -> String {
584        format_with_formatter(DateTimeFormatter::ShortDate(), timestamp, false)
585    }
586
587    pub fn format_date_medium(timestamp: &time::OffsetDateTime) -> String {
588        format_with_formatter(
589            DateTimeFormatter::CreateDateTimeFormatter(windows::core::h!(
590                "month.abbreviated day year.full"
591            )),
592            timestamp,
593            false,
594        )
595    }
596
597    fn format_with_formatter(
598        formatter: windows::core::Result<DateTimeFormatter>,
599        timestamp: &time::OffsetDateTime,
600        is_time: bool,
601    ) -> String {
602        formatter
603            .and_then(|formatter| formatter.Format(to_winrt_datetime(timestamp)))
604            .map(|hstring| hstring.to_string())
605            .unwrap_or_else(|_| {
606                if is_time {
607                    super::format_timestamp_naive_time(*timestamp, true)
608                } else {
609                    super::format_timestamp_naive_date(*timestamp, *timestamp, true)
610                }
611            })
612    }
613
614    fn to_winrt_datetime(timestamp: &time::OffsetDateTime) -> windows::Foundation::DateTime {
615        // DateTime uses 100-nanosecond intervals since January 1, 1601 (UTC).
616        const WINDOWS_EPOCH: time::OffsetDateTime = time::macros::datetime!(1601-01-01 0:00 UTC);
617        let duration_since_winrt_epoch = *timestamp - WINDOWS_EPOCH;
618        let universal_time = duration_since_winrt_epoch.whole_nanoseconds() / 100;
619
620        windows::Foundation::DateTime {
621            UniversalTime: universal_time as i64,
622        }
623    }
624}
625
626#[cfg(test)]
627mod tests {
628    use super::*;
629
630    #[test]
631    fn test_format_date() {
632        let reference = create_offset_datetime(1990, 4, 12, 10, 30, 0);
633
634        // Test with same date (today)
635        let timestamp_today = create_offset_datetime(1990, 4, 12, 9, 30, 0);
636        assert_eq!(format_date(timestamp_today, reference, true), "Today");
637
638        // Test with previous day (yesterday)
639        let timestamp_yesterday = create_offset_datetime(1990, 4, 11, 9, 30, 0);
640        assert_eq!(
641            format_date(timestamp_yesterday, reference, true),
642            "Yesterday"
643        );
644
645        // Test with other date
646        let timestamp_other = create_offset_datetime(1990, 4, 10, 9, 30, 0);
647        let result = format_date(timestamp_other, reference, true);
648        assert!(!result.is_empty());
649        assert_ne!(result, "Today");
650        assert_ne!(result, "Yesterday");
651    }
652
653    #[test]
654    fn test_format_time() {
655        let timestamp = create_offset_datetime(1990, 4, 12, 9, 30, 0);
656
657        // We can't assert the exact output as it depends on the platform and locale
658        // But we can at least confirm it doesn't panic and returns a non-empty string
659        let result = format_time(timestamp);
660        assert!(!result.is_empty());
661    }
662
663    #[test]
664    fn test_format_date_medium() {
665        let reference = create_offset_datetime(1990, 4, 12, 10, 30, 0);
666        let timestamp = create_offset_datetime(1990, 4, 12, 9, 30, 0);
667
668        // Test with enhanced formatting (today)
669        let result_enhanced = format_date_medium(timestamp, reference, true);
670        assert_eq!(result_enhanced, "Today");
671
672        // Test with standard formatting
673        let result_standard = format_date_medium(timestamp, reference, false);
674        assert!(!result_standard.is_empty());
675
676        // Test yesterday with enhanced formatting
677        let timestamp_yesterday = create_offset_datetime(1990, 4, 11, 9, 30, 0);
678        let result_yesterday = format_date_medium(timestamp_yesterday, reference, true);
679        assert_eq!(result_yesterday, "Yesterday");
680
681        // Test other date with enhanced formatting
682        let timestamp_other = create_offset_datetime(1990, 4, 10, 9, 30, 0);
683        let result_other = format_date_medium(timestamp_other, reference, true);
684        assert!(!result_other.is_empty());
685        assert_ne!(result_other, "Today");
686        assert_ne!(result_other, "Yesterday");
687    }
688
689    #[test]
690    fn test_format_absolute_time() {
691        let timestamp = create_offset_datetime(1990, 4, 12, 9, 30, 0);
692
693        // We can't assert the exact output as it depends on the platform and locale
694        // But we can at least confirm it doesn't panic and returns a non-empty string
695        let result = format_absolute_time(timestamp);
696        assert!(!result.is_empty());
697    }
698
699    #[test]
700    fn test_format_absolute_date() {
701        let reference = create_offset_datetime(1990, 4, 12, 10, 30, 0);
702
703        // Test with same date (today)
704        let timestamp_today = create_offset_datetime(1990, 4, 12, 9, 30, 0);
705        assert_eq!(
706            format_absolute_date(timestamp_today, reference, true),
707            "Today"
708        );
709
710        // Test with previous day (yesterday)
711        let timestamp_yesterday = create_offset_datetime(1990, 4, 11, 9, 30, 0);
712        assert_eq!(
713            format_absolute_date(timestamp_yesterday, reference, true),
714            "Yesterday"
715        );
716
717        // Test with other date
718        let timestamp_other = create_offset_datetime(1990, 4, 10, 9, 30, 0);
719        let result = format_absolute_date(timestamp_other, reference, true);
720        assert!(!result.is_empty());
721        assert_ne!(result, "Today");
722        assert_ne!(result, "Yesterday");
723    }
724
725    #[test]
726    fn test_format_absolute_date_medium() {
727        let reference = create_offset_datetime(1990, 4, 12, 10, 30, 0);
728        let timestamp = create_offset_datetime(1990, 4, 12, 9, 30, 0);
729
730        // Test with enhanced formatting (today)
731        let result_enhanced = format_absolute_date_medium(timestamp, reference, true);
732        assert_eq!(result_enhanced, "Today");
733
734        // Test with standard formatting
735        let result_standard = format_absolute_date_medium(timestamp, reference, false);
736        assert!(!result_standard.is_empty());
737
738        // Test yesterday with enhanced formatting
739        let timestamp_yesterday = create_offset_datetime(1990, 4, 11, 9, 30, 0);
740        let result_yesterday = format_absolute_date_medium(timestamp_yesterday, reference, true);
741        assert_eq!(result_yesterday, "Yesterday");
742    }
743
744    #[test]
745    fn test_format_timestamp_naive_time() {
746        let timestamp = create_offset_datetime(1990, 4, 12, 9, 30, 0);
747        assert_eq!(format_timestamp_naive_time(timestamp, true), "9:30 AM");
748        assert_eq!(format_timestamp_naive_time(timestamp, false), "09:30");
749
750        let timestamp_pm = create_offset_datetime(1990, 4, 12, 15, 45, 0);
751        assert_eq!(format_timestamp_naive_time(timestamp_pm, true), "3:45 PM");
752        assert_eq!(format_timestamp_naive_time(timestamp_pm, false), "15:45");
753    }
754
755    #[test]
756    fn test_format_24_hour_time() {
757        let reference = create_offset_datetime(1990, 4, 12, 16, 45, 0);
758        let timestamp = create_offset_datetime(1990, 4, 12, 15, 30, 0);
759
760        assert_eq!(
761            format_timestamp_naive(timestamp, reference, false),
762            "Today at 15:30"
763        );
764    }
765
766    #[test]
767    fn test_format_today() {
768        let reference = create_offset_datetime(1990, 4, 12, 16, 45, 0);
769        let timestamp = create_offset_datetime(1990, 4, 12, 15, 30, 0);
770
771        assert_eq!(
772            format_timestamp_naive(timestamp, reference, true),
773            "Today at 3:30 PM"
774        );
775    }
776
777    #[test]
778    fn test_format_yesterday() {
779        let reference = create_offset_datetime(1990, 4, 12, 10, 30, 0);
780        let timestamp = create_offset_datetime(1990, 4, 11, 9, 0, 0);
781
782        assert_eq!(
783            format_timestamp_naive(timestamp, reference, true),
784            "Yesterday at 9:00 AM"
785        );
786    }
787
788    #[test]
789    fn test_format_yesterday_less_than_24_hours_ago() {
790        let reference = create_offset_datetime(1990, 4, 12, 19, 59, 0);
791        let timestamp = create_offset_datetime(1990, 4, 11, 20, 0, 0);
792
793        assert_eq!(
794            format_timestamp_naive(timestamp, reference, true),
795            "Yesterday at 8:00 PM"
796        );
797    }
798
799    #[test]
800    fn test_format_yesterday_more_than_24_hours_ago() {
801        let reference = create_offset_datetime(1990, 4, 12, 19, 59, 0);
802        let timestamp = create_offset_datetime(1990, 4, 11, 18, 0, 0);
803
804        assert_eq!(
805            format_timestamp_naive(timestamp, reference, true),
806            "Yesterday at 6:00 PM"
807        );
808    }
809
810    #[test]
811    fn test_format_yesterday_over_midnight() {
812        let reference = create_offset_datetime(1990, 4, 12, 0, 5, 0);
813        let timestamp = create_offset_datetime(1990, 4, 11, 23, 55, 0);
814
815        assert_eq!(
816            format_timestamp_naive(timestamp, reference, true),
817            "Yesterday at 11:55 PM"
818        );
819    }
820
821    #[test]
822    fn test_format_yesterday_over_month() {
823        let reference = create_offset_datetime(1990, 4, 2, 9, 0, 0);
824        let timestamp = create_offset_datetime(1990, 4, 1, 20, 0, 0);
825
826        assert_eq!(
827            format_timestamp_naive(timestamp, reference, true),
828            "Yesterday at 8:00 PM"
829        );
830    }
831
832    #[test]
833    fn test_format_before_yesterday() {
834        let reference = create_offset_datetime(1990, 4, 12, 10, 30, 0);
835        let timestamp = create_offset_datetime(1990, 4, 10, 20, 20, 0);
836
837        assert_eq!(
838            format_timestamp_naive(timestamp, reference, true),
839            "04/10/1990 8:20 PM"
840        );
841    }
842
843    #[test]
844    fn test_relative_format_minutes() {
845        let reference = create_offset_datetime(1990, 4, 12, 23, 0, 0);
846        let mut current_timestamp = reference;
847
848        let mut next_minute = || {
849            current_timestamp = if current_timestamp.minute() == 0 {
850                current_timestamp
851                    .replace_hour(current_timestamp.hour() - 1)
852                    .unwrap()
853                    .replace_minute(59)
854                    .unwrap()
855            } else {
856                current_timestamp
857                    .replace_minute(current_timestamp.minute() - 1)
858                    .unwrap()
859            };
860            current_timestamp
861        };
862
863        assert_eq!(
864            format_relative_time(reference, reference),
865            Some("Just now".to_string())
866        );
867
868        assert_eq!(
869            format_relative_time(next_minute(), reference),
870            Some("1 minute ago".to_string())
871        );
872
873        for i in 2..=59 {
874            assert_eq!(
875                format_relative_time(next_minute(), reference),
876                Some(format!("{} minutes ago", i))
877            );
878        }
879
880        assert_eq!(
881            format_relative_time(next_minute(), reference),
882            Some("1 hour ago".to_string())
883        );
884    }
885
886    #[test]
887    fn test_relative_format_hours() {
888        let reference = create_offset_datetime(1990, 4, 12, 23, 0, 0);
889        let mut current_timestamp = reference;
890
891        let mut next_hour = || {
892            current_timestamp = if current_timestamp.hour() == 0 {
893                let date = current_timestamp.date().previous_day().unwrap();
894                current_timestamp.replace_date(date)
895            } else {
896                current_timestamp
897                    .replace_hour(current_timestamp.hour() - 1)
898                    .unwrap()
899            };
900            current_timestamp
901        };
902
903        assert_eq!(
904            format_relative_time(next_hour(), reference),
905            Some("1 hour ago".to_string())
906        );
907
908        for i in 2..=23 {
909            assert_eq!(
910                format_relative_time(next_hour(), reference),
911                Some(format!("{} hours ago", i))
912            );
913        }
914
915        assert_eq!(format_relative_time(next_hour(), reference), None);
916    }
917
918    #[test]
919    fn test_relative_format_days() {
920        let reference = create_offset_datetime(1990, 4, 12, 23, 0, 0);
921        let mut current_timestamp = reference;
922
923        let mut next_day = || {
924            let date = current_timestamp.date().previous_day().unwrap();
925            current_timestamp = current_timestamp.replace_date(date);
926            current_timestamp
927        };
928
929        assert_eq!(
930            format_relative_date(reference, reference),
931            "Today".to_string()
932        );
933
934        assert_eq!(
935            format_relative_date(next_day(), reference),
936            "Yesterday".to_string()
937        );
938
939        for i in 2..=6 {
940            assert_eq!(
941                format_relative_date(next_day(), reference),
942                format!("{} days ago", i)
943            );
944        }
945
946        assert_eq!(format_relative_date(next_day(), reference), "1 week ago");
947    }
948
949    #[test]
950    fn test_relative_format_weeks() {
951        let reference = create_offset_datetime(1990, 4, 12, 23, 0, 0);
952        let mut current_timestamp = reference;
953
954        let mut next_week = || {
955            for _ in 0..7 {
956                let date = current_timestamp.date().previous_day().unwrap();
957                current_timestamp = current_timestamp.replace_date(date);
958            }
959            current_timestamp
960        };
961
962        assert_eq!(
963            format_relative_date(next_week(), reference),
964            "1 week ago".to_string()
965        );
966
967        for i in 2..=4 {
968            assert_eq!(
969                format_relative_date(next_week(), reference),
970                format!("{} weeks ago", i)
971            );
972        }
973
974        assert_eq!(format_relative_date(next_week(), reference), "1 month ago");
975    }
976
977    #[test]
978    fn test_relative_format_months() {
979        let reference = create_offset_datetime(1990, 4, 12, 23, 0, 0);
980        let mut current_timestamp = reference;
981
982        let mut next_month = || {
983            if current_timestamp.month() == time::Month::January {
984                current_timestamp = current_timestamp
985                    .replace_month(time::Month::December)
986                    .unwrap()
987                    .replace_year(current_timestamp.year() - 1)
988                    .unwrap();
989            } else {
990                current_timestamp = current_timestamp
991                    .replace_month(current_timestamp.month().previous())
992                    .unwrap();
993            }
994            current_timestamp
995        };
996
997        assert_eq!(
998            format_relative_date(next_month(), reference),
999            "4 weeks ago".to_string()
1000        );
1001
1002        for i in 2..=11 {
1003            assert_eq!(
1004                format_relative_date(next_month(), reference),
1005                format!("{} months ago", i)
1006            );
1007        }
1008
1009        assert_eq!(format_relative_date(next_month(), reference), "1 year ago");
1010    }
1011
1012    #[test]
1013    fn test_relative_format_years() {
1014        let reference = create_offset_datetime(1990, 4, 12, 23, 0, 0);
1015
1016        // 12 months (exactly 1 year, no remainder)
1017        assert_eq!(
1018            format_relative_date(create_offset_datetime(1989, 4, 12, 23, 0, 0), reference),
1019            "1 year ago"
1020        );
1021
1022        // 13 months
1023        assert_eq!(
1024            format_relative_date(create_offset_datetime(1989, 3, 12, 23, 0, 0), reference),
1025            "1 year, 1 month ago"
1026        );
1027
1028        // 22 months (regression test for issue #57907)
1029        assert_eq!(
1030            format_relative_date(create_offset_datetime(1988, 6, 12, 23, 0, 0), reference),
1031            "1 year, 10 months ago"
1032        );
1033
1034        // 23 months
1035        assert_eq!(
1036            format_relative_date(create_offset_datetime(1988, 5, 12, 23, 0, 0), reference),
1037            "1 year, 11 months ago"
1038        );
1039
1040        // 24 months (exactly 2 years, no remainder)
1041        assert_eq!(
1042            format_relative_date(create_offset_datetime(1988, 4, 12, 23, 0, 0), reference),
1043            "2 years ago"
1044        );
1045
1046        // 25 months
1047        assert_eq!(
1048            format_relative_date(create_offset_datetime(1988, 3, 12, 23, 0, 0), reference),
1049            "2 years, 1 month ago"
1050        );
1051
1052        // 35 months
1053        assert_eq!(
1054            format_relative_date(create_offset_datetime(1987, 5, 12, 23, 0, 0), reference),
1055            "2 years, 11 months ago"
1056        );
1057
1058        // 36 months (exactly 3 years, no remainder)
1059        assert_eq!(
1060            format_relative_date(create_offset_datetime(1987, 4, 12, 23, 0, 0), reference),
1061            "3 years ago"
1062        );
1063
1064        // 37 months
1065        assert_eq!(
1066            format_relative_date(create_offset_datetime(1987, 3, 12, 23, 0, 0), reference),
1067            "3 years, 1 month ago"
1068        );
1069
1070        // 59 months (just under 5-year compound cutoff)
1071        assert_eq!(
1072            format_relative_date(create_offset_datetime(1985, 5, 12, 23, 0, 0), reference),
1073            "4 years, 11 months ago"
1074        );
1075
1076        // 60 months (5 years exactly; switches to year-only)
1077        assert_eq!(
1078            format_relative_date(create_offset_datetime(1985, 4, 12, 23, 0, 0), reference),
1079            "5 years ago"
1080        );
1081
1082        // 65 months (5 years + 5 months → rounds down to 5 years)
1083        assert_eq!(
1084            format_relative_date(create_offset_datetime(1984, 11, 12, 23, 0, 0), reference),
1085            "5 years ago"
1086        );
1087
1088        // 66 months (5 years + 6 months → rounds up to 6 years)
1089        assert_eq!(
1090            format_relative_date(create_offset_datetime(1984, 10, 12, 23, 0, 0), reference),
1091            "6 years ago"
1092        );
1093
1094        // 120 months
1095        assert_eq!(
1096            format_relative_date(create_offset_datetime(1980, 4, 12, 23, 0, 0), reference),
1097            "10 years ago"
1098        );
1099    }
1100
1101    #[test]
1102    fn test_calculate_month_difference() {
1103        let reference = create_offset_datetime(1990, 4, 12, 23, 0, 0);
1104
1105        assert_eq!(calculate_month_difference(reference, reference), 0);
1106
1107        assert_eq!(
1108            calculate_month_difference(create_offset_datetime(1990, 1, 12, 23, 0, 0), reference),
1109            3
1110        );
1111
1112        assert_eq!(
1113            calculate_month_difference(create_offset_datetime(1989, 11, 12, 23, 0, 0), reference),
1114            5
1115        );
1116
1117        assert_eq!(
1118            calculate_month_difference(create_offset_datetime(1989, 4, 12, 23, 0, 0), reference),
1119            12
1120        );
1121
1122        assert_eq!(
1123            calculate_month_difference(create_offset_datetime(1989, 3, 12, 23, 0, 0), reference),
1124            13
1125        );
1126
1127        assert_eq!(
1128            calculate_month_difference(create_offset_datetime(1987, 5, 12, 23, 0, 0), reference),
1129            35
1130        );
1131
1132        assert_eq!(
1133            calculate_month_difference(create_offset_datetime(1987, 4, 12, 23, 0, 0), reference),
1134            36
1135        );
1136
1137        assert_eq!(
1138            calculate_month_difference(create_offset_datetime(1987, 3, 12, 23, 0, 0), reference),
1139            37
1140        );
1141    }
1142
1143    fn test_timezone() -> UtcOffset {
1144        UtcOffset::from_hms(0, 0, 0).expect("Valid timezone offset")
1145    }
1146
1147    fn create_offset_datetime(
1148        year: i32,
1149        month: u8,
1150        day: u8,
1151        hour: u8,
1152        minute: u8,
1153        second: u8,
1154    ) -> OffsetDateTime {
1155        let date = time::Date::from_calendar_date(year, time::Month::try_from(month).unwrap(), day)
1156            .unwrap();
1157        let time = time::Time::from_hms(hour, minute, second).unwrap();
1158        let date = date.with_time(time).assume_utc(); // Assume UTC for simplicity
1159        date.to_offset(test_timezone())
1160    }
1161}
1162
Served at tenant.openagents/omega Member data and write actions are omitted.