reifydb_function/datetime/
format.rs1use reifydb_core::value::column::data::ColumnData;
5use reifydb_type::value::{constraint::bytes::MaxBytes, container::utf8::Utf8Container, date::Date, r#type::Type};
6
7use crate::{ScalarFunction, ScalarFunctionContext, error::ScalarFunctionError, propagate_options};
8
9pub struct DateTimeFormat;
10
11impl DateTimeFormat {
12 pub fn new() -> Self {
13 Self
14 }
15}
16
17fn compute_day_of_year(year: i32, month: u32, day: u32) -> u32 {
19 let mut doy = 0u32;
20 for m in 1..month {
21 doy += Date::days_in_month(year, m);
22 }
23 doy + day
24}
25
26fn format_datetime(
27 year: i32,
28 month: u32,
29 day: u32,
30 hour: u32,
31 minute: u32,
32 second: u32,
33 nanosecond: u32,
34 fmt: &str,
35) -> Result<String, String> {
36 let mut result = String::new();
37 let mut chars = fmt.chars().peekable();
38
39 while let Some(ch) = chars.next() {
40 if ch == '%' {
41 match chars.peek() {
42 Some('Y') => {
43 chars.next();
44 result.push_str(&format!("{:04}", year));
45 }
46 Some('m') => {
47 chars.next();
48 result.push_str(&format!("{:02}", month));
49 }
50 Some('d') => {
51 chars.next();
52 result.push_str(&format!("{:02}", day));
53 }
54 Some('j') => {
55 chars.next();
56 let doy = compute_day_of_year(year, month, day);
57 result.push_str(&format!("{:03}", doy));
58 }
59 Some('H') => {
60 chars.next();
61 result.push_str(&format!("{:02}", hour));
62 }
63 Some('M') => {
64 chars.next();
65 result.push_str(&format!("{:02}", minute));
66 }
67 Some('S') => {
68 chars.next();
69 result.push_str(&format!("{:02}", second));
70 }
71 Some('f') => {
72 chars.next();
73 result.push_str(&format!("{:09}", nanosecond));
74 }
75 Some('3') => {
76 chars.next();
77 if chars.peek() == Some(&'f') {
78 chars.next();
79 result.push_str(&format!("{:03}", nanosecond / 1_000_000));
80 } else {
81 return Err(
82 "invalid format specifier: '%3' (expected '%3f')".to_string()
83 );
84 }
85 }
86 Some('6') => {
87 chars.next();
88 if chars.peek() == Some(&'f') {
89 chars.next();
90 result.push_str(&format!("{:06}", nanosecond / 1_000));
91 } else {
92 return Err(
93 "invalid format specifier: '%6' (expected '%6f')".to_string()
94 );
95 }
96 }
97 Some('%') => {
98 chars.next();
99 result.push('%');
100 }
101 Some(c) => {
102 let c = *c;
103 return Err(format!("invalid format specifier: '%{}'", c));
104 }
105 None => return Err("unexpected end of format string after '%'".to_string()),
106 }
107 } else {
108 result.push(ch);
109 }
110 }
111
112 Ok(result)
113}
114
115impl ScalarFunction for DateTimeFormat {
116 fn scalar(&self, ctx: ScalarFunctionContext) -> crate::error::ScalarFunctionResult<ColumnData> {
117 if let Some(result) = propagate_options(self, &ctx) {
118 return result;
119 }
120 let columns = ctx.columns;
121 let row_count = ctx.row_count;
122
123 if columns.len() != 2 {
124 return Err(ScalarFunctionError::ArityMismatch {
125 function: ctx.fragment.clone(),
126 expected: 2,
127 actual: columns.len(),
128 });
129 }
130
131 let dt_col = columns.get(0).unwrap();
132 let fmt_col = columns.get(1).unwrap();
133
134 match (dt_col.data(), fmt_col.data()) {
135 (
136 ColumnData::DateTime(dt_container),
137 ColumnData::Utf8 {
138 container: fmt_container,
139 ..
140 },
141 ) => {
142 let mut result_data = Vec::with_capacity(row_count);
143
144 for i in 0..row_count {
145 match (dt_container.get(i), fmt_container.is_defined(i)) {
146 (Some(dt), true) => {
147 let fmt_str = &fmt_container[i];
148 match format_datetime(
149 dt.year(),
150 dt.month(),
151 dt.day(),
152 dt.hour(),
153 dt.minute(),
154 dt.second(),
155 dt.nanosecond(),
156 fmt_str,
157 ) {
158 Ok(formatted) => {
159 result_data.push(formatted);
160 }
161 Err(reason) => {
162 return Err(
163 ScalarFunctionError::ExecutionFailed {
164 function: ctx.fragment.clone(),
165 reason,
166 },
167 );
168 }
169 }
170 }
171 _ => {
172 result_data.push(String::new());
173 }
174 }
175 }
176
177 Ok(ColumnData::Utf8 {
178 container: Utf8Container::new(result_data),
179 max_bytes: MaxBytes::MAX,
180 })
181 }
182 (ColumnData::DateTime(_), other) => Err(ScalarFunctionError::InvalidArgumentType {
183 function: ctx.fragment.clone(),
184 argument_index: 1,
185 expected: vec![Type::Utf8],
186 actual: other.get_type(),
187 }),
188 (other, _) => Err(ScalarFunctionError::InvalidArgumentType {
189 function: ctx.fragment.clone(),
190 argument_index: 0,
191 expected: vec![Type::DateTime],
192 actual: other.get_type(),
193 }),
194 }
195 }
196
197 fn return_type(&self, _input_types: &[Type]) -> Type {
198 Type::Utf8
199 }
200}