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