1mod dataframe;
2mod polars_macro;
3mod source;
4
5pub use dataframe::DataFrame;
6pub use source::GGData;
7
8#[derive(Clone, Debug)]
10pub enum Value {
11 Float(f64),
12 Integer(i64),
13 Str(String),
14 Bool(bool),
15 DateTime(i64),
17 Na,
18}
19
20impl Value {
21 pub fn as_f64(&self) -> Option<f64> {
23 match self {
24 Value::Float(f) => Some(*f),
25 Value::Integer(i) => Some(*i as f64),
26 Value::DateTime(secs) => Some(*secs as f64),
27 _ => None,
28 }
29 }
30
31 pub fn as_str(&self) -> Option<&str> {
33 match self {
34 Value::Str(s) => Some(s),
35 _ => None,
36 }
37 }
38
39 pub fn is_na(&self) -> bool {
41 matches!(self, Value::Na)
42 }
43
44 pub fn is_datetime(&self) -> bool {
46 matches!(self, Value::DateTime(_))
47 }
48
49 pub fn from_timestamp(secs: i64) -> Self {
51 Value::DateTime(secs)
52 }
53
54 pub fn to_group_key(&self) -> String {
61 match self {
62 Value::Float(f) => format!("{f}"),
63 Value::Integer(i) => format!("{i}"),
64 Value::Str(s) => s.clone(),
65 Value::Bool(b) => format!("{b}"),
66 Value::DateTime(secs) => format_epoch_secs(*secs),
67 Value::Na => "NA".to_string(),
68 }
69 }
70}
71
72#[derive(Clone, Debug, PartialEq, Eq, Hash, PartialOrd, Ord)]
76pub enum GroupKey<'a> {
77 Na,
79 Key(std::borrow::Cow<'a, str>),
82}
83
84impl Value {
85 pub fn key_str(&self) -> std::borrow::Cow<'_, str> {
88 match self {
89 Value::Str(s) => std::borrow::Cow::Borrowed(s.as_str()),
90 Value::Na => std::borrow::Cow::Borrowed("NA"),
91 other => std::borrow::Cow::Owned(other.to_group_key()),
92 }
93 }
94
95 pub fn group_key(&self) -> GroupKey<'_> {
98 match self {
99 Value::Na => GroupKey::Na,
100 other => GroupKey::Key(other.key_str()),
101 }
102 }
103}
104
105pub fn format_epoch_secs(secs: i64) -> String {
107 const SECS_PER_DAY: i64 = 86400;
109 const SECS_PER_HOUR: i64 = 3600;
110 const SECS_PER_MINUTE: i64 = 60;
111
112 let mut days = secs.div_euclid(SECS_PER_DAY);
115 let rem = secs.rem_euclid(SECS_PER_DAY);
116
117 let hour = rem / SECS_PER_HOUR;
118 let minute = (rem % SECS_PER_HOUR) / SECS_PER_MINUTE;
119 let second = rem % SECS_PER_MINUTE;
120
121 days += 719_468; let era = if days >= 0 { days } else { days - 146_096 } / 146_097;
124 let doe = (days - era * 146_097) as u32;
125 let yoe = (doe - doe / 1460 + doe / 36524 - doe / 146_096) / 365;
126 let y = yoe as i64 + era * 400;
127 let doy = doe - (365 * yoe + yoe / 4 - yoe / 100);
128 let mp = (5 * doy + 2) / 153;
129 let d = doy - (153 * mp + 2) / 5 + 1;
130 let m = if mp < 10 { mp + 3 } else { mp - 9 };
131 let y = if m <= 2 { y + 1 } else { y };
132
133 if hour == 0 && minute == 0 && second == 0 {
134 format!("{y:04}-{m:02}-{d:02}")
135 } else {
136 format!("{y:04}-{m:02}-{d:02} {hour:02}:{minute:02}:{second:02}")
137 }
138}
139
140impl PartialEq for Value {
141 fn eq(&self, other: &Self) -> bool {
142 match (self, other) {
143 (Value::Float(a), Value::Float(b)) => a.to_bits() == b.to_bits(),
144 (Value::Integer(a), Value::Integer(b)) => a == b,
145 (Value::Str(a), Value::Str(b)) => a == b,
146 (Value::Bool(a), Value::Bool(b)) => a == b,
147 (Value::DateTime(a), Value::DateTime(b)) => a == b,
148 (Value::Na, Value::Na) => true,
149 _ => false,
150 }
151 }
152}
153
154#[cfg(test)]
155mod tests {
156 use super::*;
157
158 #[test]
159 fn na_and_literal_na_have_distinct_group_keys() {
160 let na = Value::Na;
161 let lit = Value::Str("NA".to_string());
162 assert_ne!(na.group_key(), lit.group_key());
163 assert_eq!(na.to_group_key(), "NA");
165 assert_eq!(lit.to_group_key(), "NA");
166 assert_eq!(Value::Float(1.0).group_key(), Value::Integer(1).group_key());
168 assert!(matches!(
169 Value::Str("x".into()).key_str(),
170 std::borrow::Cow::Borrowed("x")
171 ));
172 }
173
174 #[test]
175 fn format_epoch_secs_is_total() {
176 assert_eq!(format_epoch_secs(0), "1970-01-01");
177 assert_eq!(format_epoch_secs(-1), "1969-12-31 23:59:59");
178 assert_eq!(format_epoch_secs(-86_400), "1969-12-31");
179 assert_eq!(format_epoch_secs(951_782_400), "2000-02-29");
180 let lo = format_epoch_secs(i64::MIN);
182 let hi = format_epoch_secs(i64::MAX);
183 assert!(lo.starts_with('-') && lo.contains(':'), "{lo}");
184 assert!(hi.contains(':'), "{hi}");
185 }
186}