use crate::{
rule::{LastRules, Rule},
types::{
rule::{DayOfMonth, WeekDay},
Month, QualifiedTime, Sign, Time,
},
utils::month_to_day,
zone::ZoneEntry,
};
use alloc::string::String;
use core::fmt::Write;
#[derive(Debug, PartialEq)]
pub struct PosixTimeZone {
pub abbr: PosixAbbreviation,
pub offset: Time,
pub transition_info: Option<PosixTransition>,
}
impl PosixTimeZone {
pub(crate) fn from_zone_and_savings(entry: &ZoneEntry, savings: Time) -> Self {
let offset = entry.std_offset.add(savings);
let formatted = entry
.format
.format(offset.as_secs(), None, savings != Time::default());
let is_numeric = is_numeric(&formatted);
let abbr = PosixAbbreviation {
is_numeric,
formatted,
};
Self {
abbr,
offset,
transition_info: None,
}
}
pub(crate) fn from_zone_and_rules(entry: &ZoneEntry, rules: &LastRules) -> Self {
let offset = entry.std_offset.add(rules.standard.save);
let formatted = entry.format.format(
entry.std_offset.as_secs(),
rules.standard.letter.as_deref(),
rules.standard.is_dst(),
);
let is_numeric = is_numeric(&formatted);
let abbr = PosixAbbreviation {
is_numeric,
formatted,
};
let transition_info = rules.saving.as_ref().map(|rule| {
let formatted = entry.format.format(
entry.std_offset.as_secs() + rule.save.as_secs(),
rule.letter.as_deref(),
rule.is_dst(),
);
let abbr = PosixAbbreviation {
is_numeric,
formatted,
};
let savings = rule.save;
let start = PosixDateTime::from_rule_and_transition_info(
rule,
entry.std_offset,
rules.standard.save,
);
let end = PosixDateTime::from_rule_and_transition_info(
&rules.standard,
entry.std_offset,
rule.save,
);
PosixTransition {
abbr,
savings,
start,
end,
}
});
PosixTimeZone {
abbr,
offset,
transition_info,
}
}
}
impl PosixTimeZone {
pub fn to_string(&self) -> Result<String, core::fmt::Error> {
let mut posix_string = String::new();
write_abbr(&self.abbr, &mut posix_string)?;
write_inverted_time(&self.offset, &mut posix_string)?;
if let Some(transition_info) = &self.transition_info {
write_abbr(&transition_info.abbr, &mut posix_string)?;
if transition_info.savings != Time::one_hour() {
write_inverted_time(&self.offset.add(transition_info.savings), &mut posix_string)?;
}
write_date_time(&transition_info.start, &mut posix_string)?;
write_date_time(&transition_info.end, &mut posix_string)?;
}
Ok(posix_string)
}
}
#[non_exhaustive]
#[derive(Debug, PartialEq)]
pub struct PosixTransition {
pub abbr: PosixAbbreviation,
pub savings: Time,
pub start: PosixDateTime,
pub end: PosixDateTime,
}
#[non_exhaustive]
#[derive(Debug, PartialEq, Clone)]
pub struct PosixAbbreviation {
pub is_numeric: bool,
pub formatted: String,
}
#[derive(Debug, PartialEq, Clone, Copy)]
pub struct MonthWeekDay(pub Month, pub u8, pub WeekDay);
#[derive(Debug, PartialEq, Clone, Copy)]
pub enum PosixDate {
JulianNoLeap(u16),
JulianLeap(u16),
MonthWeekDay(MonthWeekDay),
}
impl PosixDate {
pub(crate) fn from_rule(rule: &Rule) -> (Self, i64) {
match rule.on_date {
DayOfMonth::Day(day) if rule.in_month == Month::Jan || rule.in_month == Month::Feb => (
PosixDate::JulianNoLeap(month_to_day(rule.in_month as u8, 1) as u16 + day as u16),
0,
),
DayOfMonth::Day(day) => (
PosixDate::JulianLeap(month_to_day(rule.in_month as u8, 1) as u16 + day as u16),
0,
),
DayOfMonth::Last(wd) => (
PosixDate::MonthWeekDay(MonthWeekDay(rule.in_month, 5, wd)),
0,
),
DayOfMonth::WeekDayGEThanMonthDay(week_day, day_of_month) => {
let zero_based_day_of_month = day_of_month - 1;
let week_day_from_dom = zero_based_day_of_month % 7;
let mut adjusted_week_day = week_day as i8 - week_day_from_dom as i8;
let week = 1 + zero_based_day_of_month / 7;
if adjusted_week_day < 0 {
adjusted_week_day += 7;
}
let week_day = WeekDay::from_u8(adjusted_week_day as u8);
(
PosixDate::MonthWeekDay(MonthWeekDay(rule.in_month, week, week_day)),
week_day_from_dom as i64 * 86_400,
)
}
DayOfMonth::WeekDayLEThanMonthDay(week_day, day_of_month) => {
let week_day_from_dom = day_of_month as i8 % 7;
let mut adjusted_week_day = week_day as i8 - week_day_from_dom;
let week = day_of_month / 7;
if adjusted_week_day < 0 {
adjusted_week_day += 7;
}
(
PosixDate::MonthWeekDay(MonthWeekDay(
rule.in_month,
week,
WeekDay::from_u8(adjusted_week_day as u8),
)),
week_day_from_dom as i64 * 86_400,
)
}
}
}
}
#[derive(Debug, PartialEq, Clone, Copy)]
pub struct PosixDateTime {
pub date: PosixDate,
pub time: Time,
}
impl PosixDateTime {
pub(crate) fn from_rule_and_transition_info(rule: &Rule, offset: Time, savings: Time) -> Self {
let (date, time_overflow) = PosixDate::from_rule(rule);
let time = match rule.at {
QualifiedTime::Local(time) => time,
QualifiedTime::Standard(standard_time) => standard_time.add(rule.save),
QualifiedTime::Universal(universal_time) => universal_time.add(offset).add(savings),
};
let time = time.add(Time::from_seconds(time_overflow));
Self { date, time }
}
}
fn is_numeric(str: &str) -> bool {
str.parse::<i16>().is_ok()
}
fn write_abbr(posix_abbr: &PosixAbbreviation, output: &mut String) -> core::fmt::Result {
if posix_abbr.is_numeric {
write!(output, "<")?;
write!(output, "{}", posix_abbr.formatted)?;
write!(output, ">")?;
return Ok(());
}
write!(output, "{}", posix_abbr.formatted)
}
fn write_inverted_time(time: &Time, output: &mut String) -> core::fmt::Result {
if time.sign == Sign::Positive && time.hour != 0 {
write!(output, "-")?;
}
write_time(time, output)
}
fn write_time(time: &Time, output: &mut String) -> core::fmt::Result {
write!(output, "{}", time.hour)?;
if time.minute == 0 && time.second == 0 {
return Ok(());
}
write!(output, ":{}", time.minute)?;
if time.second > 0 {
write!(output, ":{}", time.second)?;
}
Ok(())
}
fn write_date_time(datetime: &PosixDateTime, output: &mut String) -> core::fmt::Result {
write!(output, ",")?;
match datetime.date {
PosixDate::JulianLeap(d) => write!(output, "{d}")?,
PosixDate::JulianNoLeap(d) => write!(output, "J{d}")?,
PosixDate::MonthWeekDay(MonthWeekDay(month, week, day)) => {
write!(output, "M{}.{week}.{}", month as u8, day as u8)?
}
}
if datetime.time != Time::two_hour() {
write!(output, "/")?;
write_time(&datetime.time, output)?;
}
Ok(())
}