synthphonia/text/formatting/
time.rs1use 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}