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