use std::io;
use crate::Timestamp;
#[derive(Debug, Clone, Eq, PartialEq)]
pub enum Format {
UtcDateTime,
UtcMillisDateTime,
UtcNanosDateTime,
UtcDate,
UtcTime,
UtcMillisTime,
UtcNanosTime,
UtcFileName,
UtcRFC2822,
UtcRFC3339,
UtcHTTP,
UtcRFC7231,
LocalDateTime,
LocalMillisDateTime,
LocalNanosDateTime,
LocalDate,
LocalTime,
LocalMillisTime,
LocalNanosTime,
LocalFileName,
LocalRFC2822,
LocalRFC3339,
LocalHTTP,
LocalRFC7231,
TimestampSeconds,
TimestampMilliseconds, TimestampNanoseconds,
}
impl Format {
pub fn is_utc(&self) -> bool {
match &self {
Self::UtcDateTime => true,
Self::UtcMillisDateTime => true,
Self::UtcNanosDateTime => true,
Self::UtcTime => true,
Self::UtcMillisTime => true,
Self::UtcNanosTime => true,
Self::UtcFileName => true,
Self::UtcRFC2822 => true,
Self::UtcRFC3339 => true,
Self::UtcHTTP => true,
Self::UtcRFC7231 => true,
Self::TimestampSeconds => true,
Self::TimestampMilliseconds => true,
_ => false,
}
}
pub fn write<T: io::Write>(&self, out: &mut T, ts: &Timestamp) -> io::Result<()> {
let get_parts = || {
if self.is_utc() { ts.as_utc_parts() } else { ts.as_local_parts() }
};
match self {
Format::UtcDateTime => {
let parts = ts.as_utc_parts();
write!(
out,
"{year}-{month:02}-{day:02} {hour:02}:{mins:02}:{secs:02}",
year = parts.year,
month = parts.month,
day = parts.month_day,
hour = parts.hour,
mins = parts.minutes,
secs = parts.seconds,
)
}
Format::LocalDateTime => {
let parts = ts.as_local_parts();
write!(
out,
"{year}-{month:02}-{day:02} {hour:02}:{mins:02}:{secs:02} {offset_sign}{offset_hours:02}{offset_minutes:02}",
year = parts.year,
month = parts.month,
day = parts.month_day,
hour = parts.hour,
mins = parts.minutes,
secs = parts.seconds,
offset_sign = parts.gmt_offset_sign(),
offset_hours = parts.gmt_offset_hours,
offset_minutes = parts.gmt_offset_minutes,
)
}
Format::UtcMillisDateTime => {
let parts = ts.as_utc_parts();
write!(
out,
"{year}-{month:02}-{day:02} {hour:02}:{mins:02}:{secs:02}.{msecs:03}",
year = parts.year,
month = parts.month,
day = parts.month_day,
hour = parts.hour,
mins = parts.minutes,
secs = parts.seconds,
msecs = parts.milliseconds,
)
}
Format::LocalMillisDateTime => {
let parts = ts.as_local_parts();
write!(
out,
"{year}-{month:02}-{day:02} {hour:02}:{mins:02}:{secs:02}.{msecs:03} {offset_sign}{offset_hours:02}{offset_minutes:02}",
year = parts.year,
month = parts.month,
day = parts.month_day,
hour = parts.hour,
mins = parts.minutes,
secs = parts.seconds,
msecs = parts.milliseconds,
offset_sign = parts.gmt_offset_sign(),
offset_hours = parts.gmt_offset_hours,
offset_minutes = parts.gmt_offset_minutes,
)
}
Format::UtcNanosDateTime => {
let parts = ts.as_utc_parts();
write!(
out,
"{year}-{month:02}-{day:02} {hour:02}:{mins:02}:{secs:02}.{msecs:03}{nsecs:06}",
year = parts.year,
month = parts.month,
day = parts.month_day,
hour = parts.hour,
mins = parts.minutes,
secs = parts.seconds,
msecs = parts.milliseconds,
nsecs = parts.nanoseconds,
)
}
Format::LocalNanosDateTime => {
let parts = ts.as_local_parts();
write!(
out,
"{year}-{month:02}-{day:02} {hour:02}:{mins:02}:{secs:02}.{msecs:03}{nsecs:06} {offset_sign}{offset_hours:02}{offset_minutes:02}",
year = parts.year,
month = parts.month,
day = parts.month_day,
hour = parts.hour,
mins = parts.minutes,
secs = parts.seconds,
msecs = parts.milliseconds,
nsecs = parts.nanoseconds,
offset_sign = parts.gmt_offset_sign(),
offset_hours = parts.gmt_offset_hours,
offset_minutes = parts.gmt_offset_minutes,
)
}
Format::UtcFileName | Format::LocalFileName => {
let parts = get_parts();
write!(
out,
"{year}-{month:02}-{day:02}_{hour:02}-{mins:02}-{secs:02}",
year = parts.year,
month = parts.month,
day = parts.month_day,
hour = parts.hour,
mins = parts.minutes,
secs = parts.seconds,
)
}
Format::UtcDate | Format::LocalDate => {
let parts = get_parts();
write!(out, "{year}-{month:02}-{day:02}", year = parts.year, month = parts.month, day = parts.month_day)
}
Format::UtcTime | Format::LocalTime => {
let parts = get_parts();
write!(out, "{hour:02}:{mins:02}:{secs:02}", hour = parts.hour, mins = parts.minutes, secs = parts.seconds)
}
Format::UtcMillisTime | Format::LocalMillisTime => {
let parts = get_parts();
write!(
out,
"{hour:02}:{mins:02}:{secs:02}.{msecs:03}",
hour = parts.hour,
mins = parts.minutes,
secs = parts.seconds,
msecs = parts.milliseconds,
)
}
Format::UtcNanosTime | Format::LocalNanosTime => {
let parts = get_parts();
write!(
out,
"{hour:02}:{mins:02}:{secs:02}.{msecs:03}{nsecs:06}",
hour = parts.hour,
mins = parts.minutes,
secs = parts.seconds,
msecs = parts.milliseconds,
nsecs = parts.nanoseconds,
)
}
Format::UtcRFC2822 | Format::LocalRFC2822 => {
let parts = get_parts();
write!(
out,
"{day_name}, {day:02} {month_name} {year} {hour:02}:{mins:02}:{secs:02} {offset_sign}{offset_hours:02}{offset_minutes:02}",
day_name = parts.day_name(),
day = parts.month_day,
month_name = parts.month_name(),
year = parts.year,
hour = parts.hour,
mins = parts.minutes,
secs = parts.seconds,
offset_sign = parts.gmt_offset_sign(),
offset_hours = parts.gmt_offset_hours,
offset_minutes = parts.gmt_offset_minutes,
)
}
Format::UtcRFC3339 => {
let parts = ts.as_utc_parts();
write!(
out,
"{year}-{month:02}-{day:02}T{hour:02}:{mins:02}:{secs:02}Z",
year = parts.year,
month = parts.month,
day = parts.month_day,
hour = parts.hour,
mins = parts.minutes,
secs = parts.seconds,
)
}
Format::LocalRFC3339 => {
let parts = ts.as_local_parts();
write!(
out,
"{year}-{month:02}-{day:02}T{hour:02}:{mins:02}:{secs:02}{offset_sign}{offset_hours:02}{offset_minutes:02}",
year = parts.year,
month = parts.month,
day = parts.month_day,
hour = parts.hour,
mins = parts.minutes,
secs = parts.seconds,
offset_sign = parts.gmt_offset_sign(),
offset_hours = parts.gmt_offset_hours,
offset_minutes = parts.gmt_offset_minutes,
)
}
Format::UtcHTTP | Format::UtcRFC7231 | Format::LocalHTTP | Format::LocalRFC7231 => {
let parts = get_parts();
write!(
out,
"{day_name}, {day:02} {month_name} {year} {hour:02}:{mins:02}:{secs:02} {timezone}",
day_name = parts.day_name(),
day = parts.month_day,
month_name = parts.month_name(),
year = parts.year,
hour = parts.hour,
mins = parts.minutes,
secs = parts.seconds,
timezone = parts.timezone,
)
}
Format::TimestampSeconds => write!(out, "{}", ts.as_secs()),
Format::TimestampMilliseconds => write!(out, "{}", ts.as_millis()),
Format::TimestampNanoseconds => write!(out, "{}", ts.as_nanos()),
}
}
pub fn as_string(&self, ts: &Timestamp) -> String {
let mut out = Vec::new();
if let Err(e) = self.write(&mut out, ts) {
panic!("failed to serialize Timestamp: {}", e);
}
match String::from_utf8(out) {
Ok(s) => s,
Err(e) => panic!("failed to convert Timestamp to String: {}", e),
}
}
pub fn as_integer(&self, ts: &Timestamp) -> Option<u128> {
match self {
Format::TimestampSeconds => Some(ts.as_secs() as u128),
Format::TimestampMilliseconds => Some(ts.as_millis()),
Format::TimestampNanoseconds => Some(ts.as_nanos()),
_ => None,
}
}
}
#[cfg(test)]
mod test_format {
use super::*;
use crate::test_helpers;
#[test]
fn timestamp_as_number_string() {
let ts = Timestamp::from_utc_date(2026, 03, 06, 14, 43, 49, 038, 23456).expect("invalid parts");
assert_eq!(Format::TimestampSeconds.as_string(&ts), "1772808229");
assert_eq!(Format::TimestampMilliseconds.as_string(&ts), "1772808229038");
assert_eq!(Format::TimestampNanoseconds.as_string(&ts), "1772808229038023456");
}
#[test]
fn timestamp_as_utc_string() {
let ts = Timestamp::from_utc_date(2026, 03, 06, 14, 43, 49, 038, 23456).expect("invalid parts");
assert_eq!(Format::UtcDateTime.as_string(&ts), "2026-03-06 14:43:49");
assert_eq!(Format::UtcMillisDateTime.as_string(&ts), "2026-03-06 14:43:49.038");
assert_eq!(Format::UtcNanosDateTime.as_string(&ts), "2026-03-06 14:43:49.038023456");
assert_eq!(Format::UtcFileName.as_string(&ts), "2026-03-06_14-43-49");
assert_eq!(Format::UtcDate.as_string(&ts), "2026-03-06");
assert_eq!(Format::UtcTime.as_string(&ts), "14:43:49");
assert_eq!(Format::UtcMillisTime.as_string(&ts), "14:43:49.038");
assert_eq!(Format::UtcNanosTime.as_string(&ts), "14:43:49.038023456");
assert_eq!(Format::UtcRFC2822.as_string(&ts), "Fri, 06 Mar 2026 14:43:49 +0000");
assert_eq!(Format::UtcRFC3339.as_string(&ts), "2026-03-06T14:43:49Z");
assert_eq!(Format::UtcHTTP.as_string(&ts), "Fri, 06 Mar 2026 14:43:49 UTC");
assert_eq!(Format::UtcRFC7231.as_string(&ts), "Fri, 06 Mar 2026 14:43:49 UTC");
}
#[test]
fn timestamp_as_local_string() {
test_helpers::mocks::with_timezone("America/Montevideo", || {
let ts = Timestamp::from_utc_date(2026, 03, 06, 14, 43, 49, 038, 23456).expect("invalid parts");
assert_eq!(Format::LocalDateTime.as_string(&ts), "2026-03-06 11:43:49 -0300");
assert_eq!(Format::LocalMillisDateTime.as_string(&ts), "2026-03-06 11:43:49.038 -0300");
assert_eq!(Format::LocalNanosDateTime.as_string(&ts), "2026-03-06 11:43:49.038023456 -0300");
assert_eq!(Format::LocalFileName.as_string(&ts), "2026-03-06_11-43-49");
assert_eq!(Format::LocalDate.as_string(&ts), "2026-03-06");
assert_eq!(Format::LocalTime.as_string(&ts), "11:43:49");
assert_eq!(Format::LocalMillisTime.as_string(&ts), "11:43:49.038");
assert_eq!(Format::LocalNanosTime.as_string(&ts), "11:43:49.038023456");
assert_eq!(Format::LocalRFC2822.as_string(&ts), "Fri, 06 Mar 2026 11:43:49 -0300");
assert_eq!(Format::LocalRFC3339.as_string(&ts), "2026-03-06T11:43:49-0300");
assert_eq!(Format::LocalHTTP.as_string(&ts), "Fri, 06 Mar 2026 11:43:49 -03");
assert_eq!(Format::LocalRFC7231.as_string(&ts), "Fri, 06 Mar 2026 11:43:49 -03");
});
}
#[test]
fn timestamp_as_integer() {
let ts = Timestamp::from_utc_date(2026, 01, 29, 07, 43, 19, 134, 943903).expect("invalid parts");
assert_eq!(Format::TimestampSeconds.as_integer(&ts), Some(1769672599 as u128));
assert_eq!(Format::TimestampMilliseconds.as_integer(&ts), Some(1769672599134 as u128));
assert_eq!(Format::TimestampNanoseconds.as_integer(&ts), Some(1769672599134943903 as u128));
assert_eq!(Format::UtcDateTime.as_integer(&ts), None);
assert_eq!(Format::UtcNanosDateTime.as_integer(&ts), None);
assert_eq!(Format::UtcTime.as_integer(&ts), None);
assert_eq!(Format::UtcRFC2822.as_integer(&ts), None);
assert_eq!(Format::UtcRFC7231.as_integer(&ts), None);
}
}