orbital_base_components/form/datetime/
format.rs1use chrono::{FixedOffset, Local, NaiveDate, NaiveDateTime, NaiveTime, TimeZone, Utc};
2
3use super::convert::{ToUnixSeconds, TryFromUnixSeconds};
4use super::orbital_datetime::OrbitalDateTime;
5use super::timezone::DatetimeTimezone;
6
7#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
8pub enum DatetimeFormat {
9 IsoDate,
10 #[default]
11 UsDate,
12 Time24,
13 Time12,
14}
15
16pub fn format_unix(secs: i64, format: DatetimeFormat, tz: DatetimeTimezone) -> String {
17 match tz {
18 DatetimeTimezone::Local => Local
19 .timestamp_opt(secs, 0)
20 .single()
21 .map(|dt| dt.format(fmt_pattern(format)).to_string())
22 .unwrap_or_default(),
23 DatetimeTimezone::Utc => Utc
24 .timestamp_opt(secs, 0)
25 .single()
26 .map(|dt| dt.format(fmt_pattern(format)).to_string())
27 .unwrap_or_default(),
28 DatetimeTimezone::FixedOffset(offset_secs) => FixedOffset::east_opt(offset_secs)
29 .and_then(|offset| offset.timestamp_opt(secs, 0).single())
30 .map(|dt| dt.format(fmt_pattern(format)).to_string())
31 .unwrap_or_default(),
32 }
33}
34
35pub fn parse_to_unix(input: &str, format: DatetimeFormat, tz: DatetimeTimezone) -> Option<i64> {
36 let input = input.trim();
37 if input.is_empty() {
38 return None;
39 }
40
41 let naive = match format {
42 DatetimeFormat::IsoDate => {
43 let date = NaiveDate::parse_from_str(input, "%Y-%m-%d").ok()?;
44 date.and_hms_opt(0, 0, 0)?
45 }
46 DatetimeFormat::UsDate => {
47 let date = NaiveDate::parse_from_str(input, "%m/%d/%Y").ok()?;
48 date.and_hms_opt(0, 0, 0)?
49 }
50 DatetimeFormat::Time24 => {
51 let time = NaiveTime::parse_from_str(input, "%H:%M").ok()?;
52 let epoch = NaiveDate::from_ymd_opt(1970, 1, 1)?;
53 epoch.and_time(time)
54 }
55 DatetimeFormat::Time12 => {
56 let time = NaiveTime::parse_from_str(input, "%I:%M %p").ok()?;
57 let epoch = NaiveDate::from_ymd_opt(1970, 1, 1)?;
58 epoch.and_time(time)
59 }
60 };
61
62 timestamp_for_timezone(naive, tz)
63}
64
65fn timestamp_for_timezone(naive: NaiveDateTime, tz: DatetimeTimezone) -> Option<i64> {
66 match tz {
67 DatetimeTimezone::Local => Local
68 .from_local_datetime(&naive)
69 .single()
70 .map(|dt| dt.timestamp()),
71 DatetimeTimezone::Utc => Some(Utc.from_utc_datetime(&naive).timestamp()),
72 DatetimeTimezone::FixedOffset(offset_secs) => FixedOffset::east_opt(offset_secs)?
73 .from_local_datetime(&naive)
74 .single()
75 .map(|dt| dt.timestamp()),
76 }
77}
78
79pub fn format_datetime(dt: OrbitalDateTime, format: DatetimeFormat) -> String {
81 format_unix(dt.to_unix_seconds(), format, dt.timezone())
82}
83
84pub fn parse_datetime(
86 input: &str,
87 format: DatetimeFormat,
88 timezone: DatetimeTimezone,
89) -> Option<OrbitalDateTime> {
90 parse_to_unix(input, format, timezone)
91 .and_then(|secs| OrbitalDateTime::try_from_unix_seconds(secs, timezone).ok())
92}
93
94fn fmt_pattern(format: DatetimeFormat) -> &'static str {
95 match format {
96 DatetimeFormat::IsoDate => "%Y-%m-%d",
97 DatetimeFormat::UsDate => "%m/%d/%Y",
98 DatetimeFormat::Time24 => "%H:%M",
99 DatetimeFormat::Time12 => "%I:%M %p",
100 }
101}