synthphonia/text/formatting/
time.rs

1use chrono::NaiveTime;
2use regex::Regex;
3
4use crate::forward::enumeration::Enumerator1;
5use crate::utils::F64;
6use crate::value::{ConstValue, Value};
7use chrono::Timelike;
8use crate::{ impl_name, impl_op1, parser::config::Config};
9
10use crate::galloc::{AllocForExactSizeIter, AllocForStr};
11
12use super::FormattingOp;
13#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
14enum TimeNumberFormat {
15    None, Unknown, Padding, Default
16}
17
18impl TimeNumberFormat {
19    pub fn format(&self, number: u32) -> String {
20        match self {
21            TimeNumberFormat::None => "".into(),
22            TimeNumberFormat::Padding => format!("{:02}", number),
23            TimeNumberFormat::Default | TimeNumberFormat::Unknown => format!("{}", number),
24        }
25    }
26    pub fn format_colon(&self, number: u32) -> String {
27        match self {
28            TimeNumberFormat::None => "".into(),
29            TimeNumberFormat::Padding => format!(":{:02}", number),
30            TimeNumberFormat::Default | TimeNumberFormat::Unknown => format!(":{}", number),
31        }
32    }
33    pub fn union(self, other: Self) -> Option<Self> {
34        if self == other { Some(self) }
35        else {
36            match (self, other) {
37                (Self::Unknown, Self::Padding) | (Self::Padding, Self::Unknown) => Some(Self::Padding),
38                (Self::Unknown, Self::Default) | (Self::Default, Self::Unknown) => Some(Self::Default),
39                _ => None,
40            }
41        }
42    }
43    pub fn from_name(name: &str) -> Self {
44        match name {
45            "none" => Self::None,
46            "unknown" => Self::Unknown,
47            "padding" => Self::Padding,
48            "default" => Self::Default,
49            _ => panic!()
50        }
51    }
52    pub fn to_name(self) -> &'static str {
53        match self {
54            Self::None => "none",
55            Self::Unknown => "unknown",
56            Self::Padding => "padding",
57            Self::Default => "default",
58        }
59    }
60    pub fn get_format(input: &str) -> Self {
61        if input.len() == 2 {
62            if input.starts_with("0") { Self::Padding }
63            else { Self::Unknown }
64        } else if input.len() == 1 { Self::Default }
65        else { Self::None }
66    }
67}
68
69#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
70pub struct FormatTime {
71    hour: TimeNumberFormat,
72    minute: TimeNumberFormat,
73    second: TimeNumberFormat,
74    pm: Option<bool>,
75}
76
77impl FormatTime {
78    pub fn from_config(config: &Config) -> Self {
79        Self{
80            hour: TimeNumberFormat::from_name(config.get_str("h").unwrap_or("default")),
81            minute: TimeNumberFormat::from_name(config.get_str("m").unwrap_or("default")),
82            second: TimeNumberFormat::from_name(config.get_str("s").unwrap_or("default")),
83            pm: config.get_bool("pm"),
84        }
85    }
86}
87impl FormatTime {
88    pub fn name() ->  &'static str {
89        "time.fmt"
90    }
91}
92impl std::fmt::Display for FormatTime {
93    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
94        write!(f, "time.fmt #h:{} #m:{} #s:{}", self.hour.to_name(), self.minute.to_name(), self.second.to_name())?;
95        if let Some(a) = self.pm {
96            write!(f, " #pm:{}", a)?;
97        }
98        Ok(())
99    }
100}
101
102impl Default for FormatTime {
103    fn default() -> Self {
104        Self::from_config(&Default::default())
105    }
106}
107
108impl Enumerator1 for FormatTime {
109    fn enumerate(&self, this: &'static crate::expr::ops::Op1Enum, exec: &'static crate::forward::executor::Executor, opnt: [usize; 1]) -> Result<(), ()> { Ok(()) }
110}
111
112impl crate::expr::ops::Op1 for FormatTime {
113    fn cost(&self) -> usize { 1 }
114    fn try_eval(&self,a1:Value) -> (bool, Value) {
115        match a1 {
116            Value::Int(s) => (true, Value::Str(s.iter().map(|&s1|{
117                let time = NaiveTime::from_num_seconds_from_midnight_opt(s1 as u32, 0).unwrap_or_default();
118                let mut h = time.hour();
119                let mut pm = false;
120                if self.pm.is_some() { 
121                    let (a,b) = hour_to_pm(h);
122                    h = a;
123                    pm = b;}
124                
125                let hour = self.hour.format(h);
126                let minute = self.minute.format_colon(time.minute());
127                let second = self.second.format_colon(time.second());
128                let mut result = hour + &minute + &second;
129                if let Some(true) = self.pm {
130                    if pm { result.push_str("PM") } else { result.push_str("AM")}
131                } else if let Some(false) = self.pm {
132                    if pm { result.push_str("pm") } else { result.push_str("am")}
133                }
134                result.galloc_str()
135            }).galloc_scollect())),
136            _ => (false, Value::Null),
137        }
138    }
139}
140
141lazy_static::lazy_static!{
142    static ref REGEX: Regex = Regex::new(r"^(?<h>\d{1,2})(:(?<m>\d{1,2}))?(:(?<s>\d{1,2}))?((?<pm>pm|PM|am|AM))?").unwrap();
143}
144
145impl FormattingOp for FormatTime {
146    fn format(&self, input: &'static str) -> Option<(Self, crate::value::ConstValue, &'static str)> {
147        if let Some(caps) = REGEX.captures(input) {
148            let mut h = caps.name("h").unwrap().as_str().parse::<u32>().unwrap();
149            let m = caps.name("m").map(|a| a.as_str().parse::<u32>().unwrap()).unwrap_or(0);
150            let s = caps.name("s").map(|a| a.as_str().parse::<u32>().unwrap()).unwrap_or(0);
151            if let Some(a) = caps.name("pm") {
152                if h == 0 || h > 12 { return None; }
153                h = convert_hour(a.as_str().starts_with('p') || a.as_str().starts_with('P'), h);
154            }
155            if caps.name("m").is_some() || caps.name("s").is_some() || caps.name("pm").is_some() {
156                if let Some(a) = NaiveTime::from_hms_opt(h, m, s) {
157                    let hfmt = TimeNumberFormat::get_format(caps.name("h").map(|x| x.as_str()).unwrap_or(""));
158                    let mfmt = TimeNumberFormat::get_format(caps.name("m").map(|x| x.as_str()).unwrap_or(""));
159                    let sfmt = TimeNumberFormat::get_format(caps.name("s").map(|x| x.as_str()).unwrap_or(""));
160                    let pmfmt = caps.name("pm").map(|a| a.as_str() == "AM" || a.as_str() == "PM");
161                    return Some((Self{ hour: hfmt, minute: mfmt, second: sfmt, pm: pmfmt}, a.num_seconds_from_midnight().into(), &input[caps.get(0).unwrap().as_str().len()..]))
162                }
163            }
164        }
165        None
166    }
167
168    fn union(self, other: Self) -> Option<Self> {
169        Some(Self{ 
170            hour: self.hour.union(other.hour)?,
171            minute: self.minute.union(other.minute)?,
172            second: self.second.union(other.second)?,
173            pm: if self.pm == other.pm { self.pm } else { return None },
174        })
175    }
176
177    fn bad_value() -> crate::value::ConstValue {
178        crate::value::ConstValue::Int(0.into())
179    }
180}
181
182fn convert_hour(pm: bool, mut h: u32) -> u32 {
183    if pm  {
184        if h != 12 { h += 12; }
185    } else if h == 12 { h = 0; }
186    h
187}
188fn hour_to_pm(h: u32) -> (u32, bool) {
189    if h == 0 { (12, false) }
190    else if h <= 11 { (h, false) }
191    else if h == 12 { (12, true) }
192    else { (h - 12, true) }
193}
194
195fn conflict(a: Option<bool>, b: Option<bool>) -> Option<Option<bool>> {
196    match (a, b) {
197        (Some(x), Some(y)) if x != y => { None }
198        (Some(x), _) | (None, Some(x)) => { Some(Some(x)) }
199        (None, None) => { Some(None) }
200    }
201}