1use std::fmt;
19use std::num::NonZeroI64;
20use std::ops::{Add, Sub};
21use std::str::FromStr;
22use std::sync::Arc;
23
24use arrow::array::temporal_conversions::{
25 MICROSECONDS, MILLISECONDS, NANOSECONDS, as_datetime_with_timezone,
26};
27use arrow::array::timezone::Tz;
28use arrow::array::types::{
29 ArrowTimestampType, Time32MillisecondType, Time32SecondType, Time64MicrosecondType,
30 Time64NanosecondType, TimestampMicrosecondType, TimestampMillisecondType,
31 TimestampNanosecondType, TimestampSecondType,
32};
33use arrow::array::{Array, ArrayRef, PrimitiveArray};
34use arrow::datatypes::DataType::{self, Time32, Time64, Timestamp};
35use arrow::datatypes::TimeUnit::{self, Microsecond, Millisecond, Nanosecond, Second};
36use arrow::datatypes::{Field, FieldRef};
37use datafusion_common::cast::as_primitive_array;
38use datafusion_common::types::{NativeType, logical_date, logical_string};
39use datafusion_common::{
40 DataFusionError, Result, ScalarValue, exec_datafusion_err, exec_err, internal_err,
41};
42use datafusion_expr::sort_properties::{ExprProperties, SortProperties};
43use datafusion_expr::{
44 ColumnarValue, Documentation, ReturnFieldArgs, ScalarFunctionArgs, ScalarUDFImpl,
45 Signature, TypeSignature, Volatility,
46};
47use datafusion_expr_common::signature::{Coercion, TypeSignatureClass};
48use datafusion_macros::user_doc;
49
50use chrono::{
51 DateTime, Datelike, Duration, LocalResult, NaiveDateTime, Offset, TimeDelta, Timelike,
52};
53
54#[derive(Debug, Clone, Copy, PartialEq, Eq)]
56enum DateTruncGranularity {
57 Microsecond,
58 Millisecond,
59 Second,
60 Minute,
61 Hour,
62 Day,
63 Week,
64 Month,
65 Quarter,
66 Year,
67}
68
69impl DateTruncGranularity {
70 const SUPPORTED_GRANULARITIES: &[&str] = &[
74 "microsecond",
75 "millisecond",
76 "second",
77 "minute",
78 "hour",
79 "day",
80 "week",
81 "month",
82 "quarter",
83 "year",
84 ];
85
86 fn from_str(s: &str) -> Result<Self> {
88 match s.to_lowercase().as_str() {
90 "microsecond" => Ok(Self::Microsecond),
91 "millisecond" => Ok(Self::Millisecond),
92 "second" => Ok(Self::Second),
93 "minute" => Ok(Self::Minute),
94 "hour" => Ok(Self::Hour),
95 "day" => Ok(Self::Day),
96 "week" => Ok(Self::Week),
97 "month" => Ok(Self::Month),
98 "quarter" => Ok(Self::Quarter),
99 "year" => Ok(Self::Year),
100 _ => {
101 let supported = Self::SUPPORTED_GRANULARITIES.join(", ");
102 exec_err!(
103 "Unsupported date_trunc granularity: '{s}'. Supported values are: {supported}"
104 )
105 }
106 }
107 }
108
109 fn is_fine_granularity(&self) -> bool {
112 matches!(
113 self,
114 Self::Second | Self::Minute | Self::Millisecond | Self::Microsecond
115 )
116 }
117
118 fn is_fine_granularity_utc(&self) -> bool {
121 self.is_fine_granularity() || matches!(self, Self::Hour | Self::Day)
122 }
123
124 fn valid_for_time(&self) -> bool {
127 matches!(
128 self,
129 Self::Hour
130 | Self::Minute
131 | Self::Second
132 | Self::Millisecond
133 | Self::Microsecond
134 )
135 }
136}
137
138impl fmt::Display for DateTruncGranularity {
139 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
140 let value = match self {
141 Self::Microsecond => "microsecond",
142 Self::Millisecond => "millisecond",
143 Self::Second => "second",
144 Self::Minute => "minute",
145 Self::Hour => "hour",
146 Self::Day => "day",
147 Self::Week => "week",
148 Self::Month => "month",
149 Self::Quarter => "quarter",
150 Self::Year => "year",
151 };
152 f.write_str(value)
153 }
154}
155
156#[user_doc(
157 doc_section(label = "Time and Date Functions"),
158 description = "Truncates a timestamp or time value to a specified precision.",
159 syntax_example = "date_trunc(precision, expression)",
160 argument(
161 name = "precision",
162 description = r#"Time precision to truncate to. The following precisions are supported:
163
164 For Timestamp types:
165 - year / YEAR
166 - quarter / QUARTER
167 - month / MONTH
168 - week / WEEK
169 - day / DAY
170 - hour / HOUR
171 - minute / MINUTE
172 - second / SECOND
173 - millisecond / MILLISECOND
174 - microsecond / MICROSECOND
175
176 For Time types (hour, minute, second, millisecond, microsecond only):
177 - hour / HOUR
178 - minute / MINUTE
179 - second / SECOND
180 - millisecond / MILLISECOND
181 - microsecond / MICROSECOND
182"#
183 ),
184 argument(
185 name = "expression",
186 description = "Timestamp or time expression to operate on. Can be a constant, column, or function."
187 ),
188 sql_example = r#"```sql
189> SELECT date_trunc('month', '2024-05-15T10:30:00');
190+-----------------------------------------------+
191| date_trunc(Utf8("month"),Utf8("2024-05-15T10:30:00")) |
192+-----------------------------------------------+
193| 2024-05-01T00:00:00 |
194+-----------------------------------------------+
195> SELECT date_trunc('hour', '2024-05-15T10:30:00');
196+----------------------------------------------+
197| date_trunc(Utf8("hour"),Utf8("2024-05-15T10:30:00")) |
198+----------------------------------------------+
199| 2024-05-15T10:00:00 |
200+----------------------------------------------+
201```"#
202)]
203#[derive(Debug, PartialEq, Eq, Hash)]
204pub struct DateTruncFunc {
205 signature: Signature,
206 aliases: Vec<String>,
207}
208
209impl Default for DateTruncFunc {
210 fn default() -> Self {
211 Self::new()
212 }
213}
214
215impl DateTruncFunc {
216 pub fn new() -> Self {
217 Self {
218 signature: Signature::one_of(
219 vec![
220 TypeSignature::Coercible(vec![
221 Coercion::new_exact(TypeSignatureClass::Native(logical_string())),
222 Coercion::new_implicit(
223 TypeSignatureClass::Timestamp,
224 vec![
226 TypeSignatureClass::Native(logical_string()),
227 TypeSignatureClass::Native(logical_date()),
228 ],
229 NativeType::Timestamp(Nanosecond, None),
230 ),
231 ]),
232 TypeSignature::Coercible(vec![
233 Coercion::new_exact(TypeSignatureClass::Native(logical_string())),
234 Coercion::new_exact(TypeSignatureClass::Time),
235 ]),
236 ],
237 Volatility::Immutable,
238 ),
239 aliases: vec![String::from("datetrunc")],
240 }
241 }
242}
243
244impl ScalarUDFImpl for DateTruncFunc {
245 fn name(&self) -> &str {
246 "date_trunc"
247 }
248
249 fn signature(&self) -> &Signature {
250 &self.signature
251 }
252
253 fn return_type(&self, _arg_types: &[DataType]) -> Result<DataType> {
254 internal_err!("return_field_from_args should be called instead")
255 }
256
257 fn return_field_from_args(&self, args: ReturnFieldArgs) -> Result<FieldRef> {
258 let field = &args.arg_fields[1];
259 let return_type = if field.data_type().is_null() {
260 Timestamp(Nanosecond, None)
261 } else {
262 field.data_type().clone()
263 };
264 Ok(Arc::new(Field::new(
265 self.name(),
266 return_type,
267 field.is_nullable(),
268 )))
269 }
270
271 fn invoke_with_args(&self, args: ScalarFunctionArgs) -> Result<ColumnarValue> {
272 let args = args.args;
273 let (granularity, array) = (&args[0], &args[1]);
274
275 let granularity_str = if let ColumnarValue::Scalar(ScalarValue::Utf8(Some(v))) =
276 granularity
277 {
278 v.to_lowercase()
279 } else if let ColumnarValue::Scalar(ScalarValue::Utf8View(Some(v))) = granularity
280 {
281 v.to_lowercase()
282 } else if let ColumnarValue::Scalar(ScalarValue::LargeUtf8(Some(v))) = granularity
283 {
284 v.to_lowercase()
285 } else {
286 return exec_err!("Granularity of `date_trunc` must be non-null scalar Utf8");
287 };
288
289 let granularity = DateTruncGranularity::from_str(&granularity_str)?;
290
291 let is_time_type = matches!(array.data_type(), Time64(_) | Time32(_));
293 if is_time_type && !granularity.valid_for_time() {
294 return exec_err!(
295 "date_trunc does not support '{}' granularity for Time types. Valid values are: hour, minute, second, millisecond, microsecond",
296 granularity_str
297 );
298 }
299
300 fn process_array<T: ArrowTimestampType>(
301 array: &dyn Array,
302 granularity: DateTruncGranularity,
303 tz_opt: &Option<Arc<str>>,
304 ) -> Result<ColumnarValue> {
305 let parsed_tz = parse_tz(tz_opt)?;
306 let array = as_primitive_array::<T>(array)?;
307
308 if granularity.is_fine_granularity()
314 || (parsed_tz.is_none() && granularity.is_fine_granularity_utc())
315 {
316 let result = general_date_trunc_array_fine_granularity(
317 T::UNIT,
318 array,
319 granularity,
320 tz_opt.clone(),
321 )?;
322 return Ok(ColumnarValue::Array(result));
323 }
324
325 let array: PrimitiveArray<T> = array
326 .try_unary(|x| general_date_trunc(T::UNIT, x, parsed_tz, granularity))?
327 .with_timezone_opt(tz_opt.clone());
328 Ok(ColumnarValue::Array(Arc::new(array)))
329 }
330
331 fn process_scalar<T: ArrowTimestampType>(
332 v: &Option<i64>,
333 granularity: DateTruncGranularity,
334 tz_opt: &Option<Arc<str>>,
335 ) -> Result<ColumnarValue> {
336 let parsed_tz = parse_tz(tz_opt)?;
337 let value = if let Some(v) = v {
338 Some(general_date_trunc(T::UNIT, *v, parsed_tz, granularity)?)
339 } else {
340 None
341 };
342 let value = ScalarValue::new_timestamp::<T>(value, tz_opt.clone());
343 Ok(ColumnarValue::Scalar(value))
344 }
345
346 Ok(match array {
347 ColumnarValue::Scalar(ScalarValue::Null) => {
348 ColumnarValue::Scalar(ScalarValue::TimestampNanosecond(None, None))
350 }
351 ColumnarValue::Scalar(ScalarValue::TimestampNanosecond(v, tz_opt)) => {
352 process_scalar::<TimestampNanosecondType>(v, granularity, tz_opt)?
353 }
354 ColumnarValue::Scalar(ScalarValue::TimestampMicrosecond(v, tz_opt)) => {
355 process_scalar::<TimestampMicrosecondType>(v, granularity, tz_opt)?
356 }
357 ColumnarValue::Scalar(ScalarValue::TimestampMillisecond(v, tz_opt)) => {
358 process_scalar::<TimestampMillisecondType>(v, granularity, tz_opt)?
359 }
360 ColumnarValue::Scalar(ScalarValue::TimestampSecond(v, tz_opt)) => {
361 process_scalar::<TimestampSecondType>(v, granularity, tz_opt)?
362 }
363 ColumnarValue::Scalar(ScalarValue::Time64Nanosecond(v)) => {
364 let truncated = v.map(|val| truncate_time_nanos(val, granularity));
365 ColumnarValue::Scalar(ScalarValue::Time64Nanosecond(truncated))
366 }
367 ColumnarValue::Scalar(ScalarValue::Time64Microsecond(v)) => {
368 let truncated = v.map(|val| truncate_time_micros(val, granularity));
369 ColumnarValue::Scalar(ScalarValue::Time64Microsecond(truncated))
370 }
371 ColumnarValue::Scalar(ScalarValue::Time32Millisecond(v)) => {
372 let truncated = v.map(|val| truncate_time_millis(val, granularity));
373 ColumnarValue::Scalar(ScalarValue::Time32Millisecond(truncated))
374 }
375 ColumnarValue::Scalar(ScalarValue::Time32Second(v)) => {
376 let truncated = v.map(|val| truncate_time_secs(val, granularity));
377 ColumnarValue::Scalar(ScalarValue::Time32Second(truncated))
378 }
379 ColumnarValue::Array(array) => {
380 let array_type = array.data_type();
381 match array_type {
382 Timestamp(Second, tz_opt) => {
383 process_array::<TimestampSecondType>(array, granularity, tz_opt)?
384 }
385 Timestamp(Millisecond, tz_opt) => process_array::<
386 TimestampMillisecondType,
387 >(
388 array, granularity, tz_opt
389 )?,
390 Timestamp(Microsecond, tz_opt) => process_array::<
391 TimestampMicrosecondType,
392 >(
393 array, granularity, tz_opt
394 )?,
395 Timestamp(Nanosecond, tz_opt) => process_array::<
396 TimestampNanosecondType,
397 >(
398 array, granularity, tz_opt
399 )?,
400 Time64(Nanosecond) => {
401 let arr = as_primitive_array::<Time64NanosecondType>(array)?;
402 let result: PrimitiveArray<Time64NanosecondType> =
403 arr.unary(|v| truncate_time_nanos(v, granularity));
404 ColumnarValue::Array(Arc::new(result))
405 }
406 Time64(Microsecond) => {
407 let arr = as_primitive_array::<Time64MicrosecondType>(array)?;
408 let result: PrimitiveArray<Time64MicrosecondType> =
409 arr.unary(|v| truncate_time_micros(v, granularity));
410 ColumnarValue::Array(Arc::new(result))
411 }
412 Time32(Millisecond) => {
413 let arr = as_primitive_array::<Time32MillisecondType>(array)?;
414 let result: PrimitiveArray<Time32MillisecondType> =
415 arr.unary(|v| truncate_time_millis(v, granularity));
416 ColumnarValue::Array(Arc::new(result))
417 }
418 Time32(Second) => {
419 let arr = as_primitive_array::<Time32SecondType>(array)?;
420 let result: PrimitiveArray<Time32SecondType> =
421 arr.unary(|v| truncate_time_secs(v, granularity));
422 ColumnarValue::Array(Arc::new(result))
423 }
424 _ => {
425 return exec_err!(
426 "second argument of `date_trunc` is an unsupported array type: {array_type}"
427 );
428 }
429 }
430 }
431 _ => {
432 return exec_err!(
433 "second argument of `date_trunc` must be timestamp, time scalar or array"
434 );
435 }
436 })
437 }
438
439 fn aliases(&self) -> &[String] {
440 &self.aliases
441 }
442
443 fn output_ordering(&self, input: &[ExprProperties]) -> Result<SortProperties> {
444 let precision = &input[0];
446 let date_value = &input[1];
447
448 if precision.sort_properties.eq(&SortProperties::Singleton) {
449 Ok(date_value.sort_properties)
450 } else {
451 Ok(SortProperties::Unordered)
452 }
453 }
454 fn documentation(&self) -> Option<&Documentation> {
455 self.doc()
456 }
457}
458
459const NANOS_PER_MICROSECOND: i64 = NANOSECONDS / MICROSECONDS;
460const NANOS_PER_MILLISECOND: i64 = NANOSECONDS / MILLISECONDS;
461const NANOS_PER_SECOND: i64 = NANOSECONDS;
462const NANOS_PER_MINUTE: i64 = 60 * NANOS_PER_SECOND;
463const NANOS_PER_HOUR: i64 = 60 * NANOS_PER_MINUTE;
464const NANOS_PER_DAY: i64 = 24 * NANOS_PER_HOUR;
465
466const MICROS_PER_MILLISECOND: i64 = MICROSECONDS / MILLISECONDS;
467const MICROS_PER_SECOND: i64 = MICROSECONDS;
468const MICROS_PER_MINUTE: i64 = 60 * MICROS_PER_SECOND;
469const MICROS_PER_HOUR: i64 = 60 * MICROS_PER_MINUTE;
470
471const MILLIS_PER_SECOND: i32 = MILLISECONDS as i32;
472const MILLIS_PER_MINUTE: i32 = 60 * MILLIS_PER_SECOND;
473const MILLIS_PER_HOUR: i32 = 60 * MILLIS_PER_MINUTE;
474
475const SECS_PER_MINUTE: i32 = 60;
476const SECS_PER_HOUR: i32 = 60 * SECS_PER_MINUTE;
477
478fn truncate_time_nanos(value: i64, granularity: DateTruncGranularity) -> i64 {
480 match granularity {
481 DateTruncGranularity::Hour => value - (value % NANOS_PER_HOUR),
482 DateTruncGranularity::Minute => value - (value % NANOS_PER_MINUTE),
483 DateTruncGranularity::Second => value - (value % NANOS_PER_SECOND),
484 DateTruncGranularity::Millisecond => value - (value % NANOS_PER_MILLISECOND),
485 DateTruncGranularity::Microsecond => value - (value % NANOS_PER_MICROSECOND),
486 _ => value,
488 }
489}
490
491fn truncate_time_micros(value: i64, granularity: DateTruncGranularity) -> i64 {
493 match granularity {
494 DateTruncGranularity::Hour => value - (value % MICROS_PER_HOUR),
495 DateTruncGranularity::Minute => value - (value % MICROS_PER_MINUTE),
496 DateTruncGranularity::Second => value - (value % MICROS_PER_SECOND),
497 DateTruncGranularity::Millisecond => value - (value % MICROS_PER_MILLISECOND),
498 DateTruncGranularity::Microsecond => value, _ => value,
501 }
502}
503
504fn truncate_time_millis(value: i32, granularity: DateTruncGranularity) -> i32 {
506 match granularity {
507 DateTruncGranularity::Hour => value - (value % MILLIS_PER_HOUR),
508 DateTruncGranularity::Minute => value - (value % MILLIS_PER_MINUTE),
509 DateTruncGranularity::Second => value - (value % MILLIS_PER_SECOND),
510 DateTruncGranularity::Millisecond => value, DateTruncGranularity::Microsecond => value, _ => value,
514 }
515}
516
517fn truncate_time_secs(value: i32, granularity: DateTruncGranularity) -> i32 {
519 match granularity {
520 DateTruncGranularity::Hour => value - (value % SECS_PER_HOUR),
521 DateTruncGranularity::Minute => value - (value % SECS_PER_MINUTE),
522 DateTruncGranularity::Second => value, DateTruncGranularity::Millisecond => value, DateTruncGranularity::Microsecond => value, _ => value,
527 }
528}
529
530fn _date_trunc_coarse<T>(
531 granularity: DateTruncGranularity,
532 value: Option<T>,
533) -> Result<Option<T>>
534where
535 T: Datelike + Timelike + Sub<Duration, Output = T> + Copy,
536{
537 let value = match granularity {
538 DateTruncGranularity::Millisecond => value,
539 DateTruncGranularity::Microsecond => value,
540 DateTruncGranularity::Second => value.and_then(|d| d.with_nanosecond(0)),
541 DateTruncGranularity::Minute => value
542 .and_then(|d| d.with_nanosecond(0))
543 .and_then(|d| d.with_second(0)),
544 DateTruncGranularity::Hour => value
545 .and_then(|d| d.with_nanosecond(0))
546 .and_then(|d| d.with_second(0))
547 .and_then(|d| d.with_minute(0)),
548 DateTruncGranularity::Day => value
549 .and_then(|d| d.with_nanosecond(0))
550 .and_then(|d| d.with_second(0))
551 .and_then(|d| d.with_minute(0))
552 .and_then(|d| d.with_hour(0)),
553 DateTruncGranularity::Week => value
554 .and_then(|d| d.with_nanosecond(0))
555 .and_then(|d| d.with_second(0))
556 .and_then(|d| d.with_minute(0))
557 .and_then(|d| d.with_hour(0))
558 .map(|d| {
559 d - TimeDelta::try_seconds(60 * 60 * 24 * d.weekday() as i64).unwrap()
560 }),
561 DateTruncGranularity::Month => value
562 .and_then(|d| d.with_nanosecond(0))
563 .and_then(|d| d.with_second(0))
564 .and_then(|d| d.with_minute(0))
565 .and_then(|d| d.with_hour(0))
566 .and_then(|d| d.with_day0(0)),
567 DateTruncGranularity::Quarter => value
568 .and_then(|d| d.with_nanosecond(0))
569 .and_then(|d| d.with_second(0))
570 .and_then(|d| d.with_minute(0))
571 .and_then(|d| d.with_hour(0))
572 .and_then(|d| d.with_day0(0))
573 .and_then(|d| d.with_month(quarter_month(&d))),
574 DateTruncGranularity::Year => value
575 .and_then(|d| d.with_nanosecond(0))
576 .and_then(|d| d.with_second(0))
577 .and_then(|d| d.with_minute(0))
578 .and_then(|d| d.with_hour(0))
579 .and_then(|d| d.with_day0(0))
580 .and_then(|d| d.with_month0(0)),
581 };
582 Ok(value)
583}
584
585fn quarter_month<T>(date: &T) -> u32
586where
587 T: Datelike,
588{
589 1 + 3 * ((date.month() - 1) / 3)
590}
591
592fn _date_trunc_coarse_with_tz(
593 granularity: DateTruncGranularity,
594 value: DateTime<Tz>,
595) -> Result<Option<i64>> {
596 let local = value.naive_local();
597 let truncated = _date_trunc_coarse::<NaiveDateTime>(granularity, Some(local))?;
598 let truncated = truncated.and_then(|truncated| {
599 match truncated.and_local_timezone(value.timezone()) {
600 LocalResult::None => {
601 truncated
607 .sub(TimeDelta::try_hours(3).unwrap())
608 .and_local_timezone(value.timezone())
609 .single()
610 .map(|v| v.add(TimeDelta::try_hours(3).unwrap()))
611 }
612 LocalResult::Single(datetime) => Some(datetime),
613 LocalResult::Ambiguous(datetime1, datetime2) => {
614 if datetime1.offset().fix() == value.offset().fix() {
619 Some(datetime1)
620 } else {
621 Some(datetime2)
622 }
623 }
624 }
625 });
626 Ok(truncated.and_then(|value| value.timestamp_nanos_opt()))
627}
628
629const DAYS_EPOCH_SHIFT: i64 = 719_468;
639
640const DAYS_PER_ERA: i64 = 146_097;
642
643fn civil_from_days(days: i64) -> (i64, i64, i64) {
650 let z = days + DAYS_EPOCH_SHIFT;
651 let era = z.div_euclid(DAYS_PER_ERA);
652 let day_of_era = z.rem_euclid(DAYS_PER_ERA);
653 let year_of_era = (day_of_era - day_of_era / 1460 + day_of_era / 36524
654 - day_of_era / 146_096)
655 / 365;
656 let day_of_year =
657 day_of_era - (365 * year_of_era + year_of_era / 4 - year_of_era / 100);
658 let month_index = (5 * day_of_year + 2) / 153;
660 let day = day_of_year - (153 * month_index + 2) / 5 + 1;
661 let month = if month_index < 10 {
662 month_index + 3
663 } else {
664 month_index - 9
665 };
666 let year = year_of_era + era * 400 + i64::from(month <= 2);
667 (year, month, day)
668}
669
670fn days_from_civil(year: i64, month: i64, day: i64) -> i64 {
677 let year = year - i64::from(month <= 2);
678 let era = year.div_euclid(400);
679 let year_of_era = year.rem_euclid(400);
680 let month_index = if month > 2 { month - 3 } else { month + 9 };
681 let day_of_year = (153 * month_index + 2) / 5 + day - 1;
682 let day_of_era =
683 year_of_era * 365 + year_of_era / 4 - year_of_era / 100 + day_of_year;
684 era * DAYS_PER_ERA + day_of_era - DAYS_EPOCH_SHIFT
685}
686
687fn _date_trunc_coarse_without_tz(
695 granularity: DateTruncGranularity,
696 value: i64,
697) -> Option<i64> {
698 let truncate_to = |unit: i64| value.checked_sub(value.rem_euclid(unit));
699 let days = || value.div_euclid(NANOS_PER_DAY);
700 let nanos_from_days = |days: i64| days.checked_mul(NANOS_PER_DAY);
701
702 match granularity {
703 DateTruncGranularity::Millisecond | DateTruncGranularity::Microsecond => {
706 Some(value)
707 }
708 DateTruncGranularity::Second => truncate_to(NANOS_PER_SECOND),
709 DateTruncGranularity::Minute => truncate_to(NANOS_PER_MINUTE),
710 DateTruncGranularity::Hour => truncate_to(NANOS_PER_HOUR),
711 DateTruncGranularity::Day => nanos_from_days(days()),
712 DateTruncGranularity::Week => {
713 let days = days();
714 nanos_from_days(days - (days + 3).rem_euclid(7))
716 }
717 DateTruncGranularity::Month => {
718 let days = days();
719 let (_, _, day_of_month) = civil_from_days(days);
720 nanos_from_days(days - (day_of_month - 1))
721 }
722 DateTruncGranularity::Quarter => {
723 let (year, month, _) = civil_from_days(days());
724 nanos_from_days(days_from_civil(year, 1 + 3 * ((month - 1) / 3), 1))
725 }
726 DateTruncGranularity::Year => {
727 let (year, _, _) = civil_from_days(days());
728 nanos_from_days(days_from_civil(year, 1, 1))
729 }
730 }
731}
732
733fn date_trunc_coarse(
738 granularity: DateTruncGranularity,
739 value: i64,
740 tz: Option<Tz>,
741) -> Result<i64> {
742 let input = value;
743 let value = match tz {
744 Some(tz) => {
745 let value = as_datetime_with_timezone::<TimestampNanosecondType>(value, tz)
748 .ok_or(exec_datafusion_err!("Timestamp {value} out of range"))?;
749 _date_trunc_coarse_with_tz(granularity, value)?
750 }
751 None => _date_trunc_coarse_without_tz(granularity, value),
752 };
753
754 value.ok_or_else(|| {
755 exec_datafusion_err!(
756 "Timestamp {input} out of range after truncating to {granularity}"
757 )
758 })
759}
760
761fn general_date_trunc_array_fine_granularity<T: ArrowTimestampType>(
768 tu: TimeUnit,
769 array: &PrimitiveArray<T>,
770 granularity: DateTruncGranularity,
771 tz_opt: Option<Arc<str>>,
772) -> Result<ArrayRef> {
773 let unit = match (tu, granularity) {
774 (Second, DateTruncGranularity::Minute) => NonZeroI64::new(60),
775 (Second, DateTruncGranularity::Hour) => NonZeroI64::new(3600),
776 (Second, DateTruncGranularity::Day) => NonZeroI64::new(86400),
777
778 (Millisecond, DateTruncGranularity::Second) => NonZeroI64::new(1_000),
779 (Millisecond, DateTruncGranularity::Minute) => NonZeroI64::new(60_000),
780 (Millisecond, DateTruncGranularity::Hour) => NonZeroI64::new(3_600_000),
781 (Millisecond, DateTruncGranularity::Day) => NonZeroI64::new(86_400_000),
782
783 (Microsecond, DateTruncGranularity::Millisecond) => NonZeroI64::new(1_000),
784 (Microsecond, DateTruncGranularity::Second) => NonZeroI64::new(1_000_000),
785 (Microsecond, DateTruncGranularity::Minute) => NonZeroI64::new(60_000_000),
786 (Microsecond, DateTruncGranularity::Hour) => NonZeroI64::new(3_600_000_000),
787 (Microsecond, DateTruncGranularity::Day) => NonZeroI64::new(86_400_000_000),
788
789 (Nanosecond, DateTruncGranularity::Microsecond) => NonZeroI64::new(1_000),
790 (Nanosecond, DateTruncGranularity::Millisecond) => NonZeroI64::new(1_000_000),
791 (Nanosecond, DateTruncGranularity::Second) => NonZeroI64::new(1_000_000_000),
792 (Nanosecond, DateTruncGranularity::Minute) => NonZeroI64::new(60_000_000_000),
793 (Nanosecond, DateTruncGranularity::Hour) => NonZeroI64::new(3_600_000_000_000),
794 (Nanosecond, DateTruncGranularity::Day) => NonZeroI64::new(86_400_000_000_000),
795 _ => None,
796 };
797
798 if let Some(unit) = unit {
799 let unit = unit.get();
800 let array = PrimitiveArray::<T>::from_iter_values_with_nulls(
801 array
802 .values()
803 .iter()
804 .map(|v| *v - i64::rem_euclid(*v, unit)),
805 array.nulls().cloned(),
806 )
807 .with_timezone_opt(tz_opt);
808 Ok(Arc::new(array))
809 } else {
810 Ok(Arc::new(array.clone()))
812 }
813}
814
815fn general_date_trunc(
817 tu: TimeUnit,
818 value: i64,
819 tz: Option<Tz>,
820 granularity: DateTruncGranularity,
821) -> Result<i64, DataFusionError> {
822 let scale = match tu {
823 Second => 1_000_000_000,
824 Millisecond => 1_000_000,
825 Microsecond => 1_000,
826 Nanosecond => 1,
827 };
828
829 let nano = date_trunc_coarse(
831 granularity,
832 value
833 .checked_mul(scale)
834 .ok_or_else(|| exec_datafusion_err!("Timestamp {value} out of range"))?,
835 tz,
836 )?;
837
838 let result = match tu {
839 Second => match granularity {
840 DateTruncGranularity::Minute => nano / 1_000_000_000 / 60 * 60,
841 _ => nano / 1_000_000_000,
842 },
843 Millisecond => match granularity {
844 DateTruncGranularity::Minute => nano / 1_000_000 / 1_000 / 60 * 1_000 * 60,
845 DateTruncGranularity::Second => nano / 1_000_000 / 1_000 * 1_000,
846 _ => nano / 1_000_000,
847 },
848 Microsecond => match granularity {
849 DateTruncGranularity::Minute => {
850 nano / 1_000 / 1_000_000 / 60 * 60 * 1_000_000
851 }
852 DateTruncGranularity::Second => nano / 1_000 / 1_000_000 * 1_000_000,
853 DateTruncGranularity::Millisecond => nano / 1_000 / 1_000 * 1_000,
854 _ => nano / 1_000,
855 },
856 _ => match granularity {
857 DateTruncGranularity::Minute => {
858 nano / 1_000_000_000 / 60 * 1_000_000_000 * 60
859 }
860 DateTruncGranularity::Second => nano / 1_000_000_000 * 1_000_000_000,
861 DateTruncGranularity::Millisecond => nano / 1_000_000 * 1_000_000,
862 DateTruncGranularity::Microsecond => nano / 1_000 * 1_000,
863 _ => nano,
864 },
865 };
866 Ok(result)
867}
868
869fn parse_tz(tz: &Option<Arc<str>>) -> Result<Option<Tz>> {
870 tz.as_ref()
871 .map(|tz| {
872 Tz::from_str(tz)
873 .map_err(|op| exec_datafusion_err!("failed on timezone {tz}: {op:?}"))
874 })
875 .transpose()
876}
877
878#[cfg(test)]
879mod tests {
880 use std::sync::Arc;
881
882 use crate::datetime::date_trunc::{
883 DateTruncFunc, DateTruncGranularity, date_trunc_coarse,
884 };
885
886 use arrow::array::cast::as_primitive_array;
887 use arrow::array::types::TimestampNanosecondType;
888 use arrow::array::{Array, TimestampNanosecondArray};
889 use arrow::compute::kernels::cast_utils::string_to_timestamp_nanos;
890 use arrow::datatypes::{DataType, Field, TimeUnit};
891 use datafusion_common::ScalarValue;
892 use datafusion_common::config::ConfigOptions;
893 use datafusion_expr::{ColumnarValue, ScalarFunctionArgs, ScalarUDFImpl};
894
895 #[test]
896 fn date_trunc_test() {
897 let cases = vec![
898 (
899 "2020-09-08T13:42:29.190855Z",
900 "second",
901 "2020-09-08T13:42:29.000000Z",
902 ),
903 (
904 "2020-09-08T13:42:29.190855Z",
905 "minute",
906 "2020-09-08T13:42:00.000000Z",
907 ),
908 (
909 "2020-09-08T13:42:29.190855Z",
910 "hour",
911 "2020-09-08T13:00:00.000000Z",
912 ),
913 (
914 "2020-09-08T13:42:29.190855Z",
915 "day",
916 "2020-09-08T00:00:00.000000Z",
917 ),
918 (
919 "2020-09-08T13:42:29.190855Z",
920 "week",
921 "2020-09-07T00:00:00.000000Z",
922 ),
923 (
924 "2020-09-08T13:42:29.190855Z",
925 "month",
926 "2020-09-01T00:00:00.000000Z",
927 ),
928 (
929 "2020-09-08T13:42:29.190855Z",
930 "year",
931 "2020-01-01T00:00:00.000000Z",
932 ),
933 (
935 "2021-01-01T13:42:29.190855Z",
936 "week",
937 "2020-12-28T00:00:00.000000Z",
938 ),
939 (
940 "2020-01-01T13:42:29.190855Z",
941 "week",
942 "2019-12-30T00:00:00.000000Z",
943 ),
944 (
946 "2020-01-01T13:42:29.190855Z",
947 "quarter",
948 "2020-01-01T00:00:00.000000Z",
949 ),
950 (
951 "2020-02-01T13:42:29.190855Z",
952 "quarter",
953 "2020-01-01T00:00:00.000000Z",
954 ),
955 (
956 "2020-03-01T13:42:29.190855Z",
957 "quarter",
958 "2020-01-01T00:00:00.000000Z",
959 ),
960 (
961 "2020-04-01T13:42:29.190855Z",
962 "quarter",
963 "2020-04-01T00:00:00.000000Z",
964 ),
965 (
966 "2020-08-01T13:42:29.190855Z",
967 "quarter",
968 "2020-07-01T00:00:00.000000Z",
969 ),
970 (
971 "2020-11-01T13:42:29.190855Z",
972 "quarter",
973 "2020-10-01T00:00:00.000000Z",
974 ),
975 (
976 "2020-12-01T13:42:29.190855Z",
977 "quarter",
978 "2020-10-01T00:00:00.000000Z",
979 ),
980 ];
981
982 cases.iter().for_each(|(original, granularity, expected)| {
983 let left = string_to_timestamp_nanos(original).unwrap();
984 let right = string_to_timestamp_nanos(expected).unwrap();
985 let granularity_enum = DateTruncGranularity::from_str(granularity).unwrap();
986 let result = date_trunc_coarse(granularity_enum, left, None).unwrap();
987 assert_eq!(result, right, "{original} = {expected}");
988 });
989 }
990
991 #[test]
992 fn date_trunc_out_of_range_lower_bound_returns_error() {
993 let timestamp = string_to_timestamp_nanos("1677-09-22T00:00:00Z").unwrap();
994 let err = date_trunc_coarse(DateTruncGranularity::Year, timestamp, None)
995 .unwrap_err()
996 .to_string();
997
998 assert!(
999 err.contains("out of range after truncating to year"),
1000 "{err}"
1001 );
1002 }
1003
1004 #[test]
1005 fn test_date_trunc_timezones() {
1006 let cases = [
1007 (
1008 vec![
1009 "2020-09-08T00:00:00Z",
1010 "2020-09-08T01:00:00Z",
1011 "2020-09-08T02:00:00Z",
1012 "2020-09-08T03:00:00Z",
1013 "2020-09-08T04:00:00Z",
1014 ],
1015 Some("+00".into()),
1016 vec![
1017 "2020-09-08T00:00:00Z",
1018 "2020-09-08T00:00:00Z",
1019 "2020-09-08T00:00:00Z",
1020 "2020-09-08T00:00:00Z",
1021 "2020-09-08T00:00:00Z",
1022 ],
1023 ),
1024 (
1025 vec![
1026 "2020-09-08T00:00:00Z",
1027 "2020-09-08T01:00:00Z",
1028 "2020-09-08T02:00:00Z",
1029 "2020-09-08T03:00:00Z",
1030 "2020-09-08T04:00:00Z",
1031 ],
1032 None,
1033 vec![
1034 "2020-09-08T00:00:00Z",
1035 "2020-09-08T00:00:00Z",
1036 "2020-09-08T00:00:00Z",
1037 "2020-09-08T00:00:00Z",
1038 "2020-09-08T00:00:00Z",
1039 ],
1040 ),
1041 (
1042 vec![
1043 "2020-09-08T00:00:00Z",
1044 "2020-09-08T01:00:00Z",
1045 "2020-09-08T02:00:00Z",
1046 "2020-09-08T03:00:00Z",
1047 "2020-09-08T04:00:00Z",
1048 ],
1049 Some("-02".into()),
1050 vec![
1051 "2020-09-07T02:00:00Z",
1052 "2020-09-07T02:00:00Z",
1053 "2020-09-08T02:00:00Z",
1054 "2020-09-08T02:00:00Z",
1055 "2020-09-08T02:00:00Z",
1056 ],
1057 ),
1058 (
1059 vec![
1060 "2020-09-08T00:00:00+05",
1061 "2020-09-08T01:00:00+05",
1062 "2020-09-08T02:00:00+05",
1063 "2020-09-08T03:00:00+05",
1064 "2020-09-08T04:00:00+05",
1065 ],
1066 Some("+05".into()),
1067 vec![
1068 "2020-09-08T00:00:00+05",
1069 "2020-09-08T00:00:00+05",
1070 "2020-09-08T00:00:00+05",
1071 "2020-09-08T00:00:00+05",
1072 "2020-09-08T00:00:00+05",
1073 ],
1074 ),
1075 (
1076 vec![
1077 "2020-09-08T00:00:00+08",
1078 "2020-09-08T01:00:00+08",
1079 "2020-09-08T02:00:00+08",
1080 "2020-09-08T03:00:00+08",
1081 "2020-09-08T04:00:00+08",
1082 ],
1083 Some("+08".into()),
1084 vec![
1085 "2020-09-08T00:00:00+08",
1086 "2020-09-08T00:00:00+08",
1087 "2020-09-08T00:00:00+08",
1088 "2020-09-08T00:00:00+08",
1089 "2020-09-08T00:00:00+08",
1090 ],
1091 ),
1092 (
1093 vec![
1094 "2024-10-26T23:00:00Z",
1095 "2024-10-27T00:00:00Z",
1096 "2024-10-27T01:00:00Z",
1097 "2024-10-27T02:00:00Z",
1098 ],
1099 Some("Europe/Berlin".into()),
1100 vec![
1101 "2024-10-27T00:00:00+02",
1102 "2024-10-27T00:00:00+02",
1103 "2024-10-27T00:00:00+02",
1104 "2024-10-27T00:00:00+02",
1105 ],
1106 ),
1107 (
1108 vec![
1109 "2018-02-18T00:00:00Z",
1110 "2018-02-18T01:00:00Z",
1111 "2018-02-18T02:00:00Z",
1112 "2018-02-18T03:00:00Z",
1113 "2018-11-04T01:00:00Z",
1114 "2018-11-04T02:00:00Z",
1115 "2018-11-04T03:00:00Z",
1116 "2018-11-04T04:00:00Z",
1117 ],
1118 Some("America/Sao_Paulo".into()),
1119 vec![
1120 "2018-02-17T00:00:00-02",
1121 "2018-02-17T00:00:00-02",
1122 "2018-02-17T00:00:00-02",
1123 "2018-02-18T00:00:00-03",
1124 "2018-11-03T00:00:00-03",
1125 "2018-11-03T00:00:00-03",
1126 "2018-11-04T01:00:00-02",
1127 "2018-11-04T01:00:00-02",
1128 ],
1129 ),
1130 ];
1131
1132 cases.iter().for_each(|(original, tz_opt, expected)| {
1133 let input = original
1134 .iter()
1135 .map(|s| Some(string_to_timestamp_nanos(s).unwrap()))
1136 .collect::<TimestampNanosecondArray>()
1137 .with_timezone_opt(tz_opt.clone());
1138 let right = expected
1139 .iter()
1140 .map(|s| Some(string_to_timestamp_nanos(s).unwrap()))
1141 .collect::<TimestampNanosecondArray>()
1142 .with_timezone_opt(tz_opt.clone());
1143 let batch_len = input.len();
1144 let arg_fields = vec![
1145 Field::new("a", DataType::Utf8, false).into(),
1146 Field::new("b", input.data_type().clone(), false).into(),
1147 ];
1148 let args = ScalarFunctionArgs {
1149 args: vec![
1150 ColumnarValue::Scalar(ScalarValue::from("day")),
1151 ColumnarValue::Array(Arc::new(input)),
1152 ],
1153 arg_fields,
1154 number_rows: batch_len,
1155 return_field: Field::new(
1156 "f",
1157 DataType::Timestamp(TimeUnit::Nanosecond, tz_opt.clone()),
1158 true,
1159 )
1160 .into(),
1161 config_options: Arc::new(ConfigOptions::default()),
1162 };
1163 let result = DateTruncFunc::new().invoke_with_args(args).unwrap();
1164 if let ColumnarValue::Array(result) = result {
1165 assert_eq!(
1166 result.data_type(),
1167 &DataType::Timestamp(TimeUnit::Nanosecond, tz_opt.clone())
1168 );
1169 let left = as_primitive_array::<TimestampNanosecondType>(&result);
1170 assert_eq!(left, &right);
1171 } else {
1172 panic!("unexpected column type");
1173 }
1174 });
1175 }
1176
1177 #[test]
1178 fn test_date_trunc_hour_timezones() {
1179 let cases = [
1180 (
1181 vec![
1182 "2020-09-08T00:30:00Z",
1183 "2020-09-08T01:30:00Z",
1184 "2020-09-08T02:30:00Z",
1185 "2020-09-08T03:30:00Z",
1186 "2020-09-08T04:30:00Z",
1187 ],
1188 Some("+00".into()),
1189 vec![
1190 "2020-09-08T00:00:00Z",
1191 "2020-09-08T01:00:00Z",
1192 "2020-09-08T02:00:00Z",
1193 "2020-09-08T03:00:00Z",
1194 "2020-09-08T04:00:00Z",
1195 ],
1196 ),
1197 (
1198 vec![
1199 "2020-09-08T00:30:00Z",
1200 "2020-09-08T01:30:00Z",
1201 "2020-09-08T02:30:00Z",
1202 "2020-09-08T03:30:00Z",
1203 "2020-09-08T04:30:00Z",
1204 ],
1205 None,
1206 vec![
1207 "2020-09-08T00:00:00Z",
1208 "2020-09-08T01:00:00Z",
1209 "2020-09-08T02:00:00Z",
1210 "2020-09-08T03:00:00Z",
1211 "2020-09-08T04:00:00Z",
1212 ],
1213 ),
1214 (
1215 vec![
1216 "2020-09-08T00:30:00Z",
1217 "2020-09-08T01:30:00Z",
1218 "2020-09-08T02:30:00Z",
1219 "2020-09-08T03:30:00Z",
1220 "2020-09-08T04:30:00Z",
1221 ],
1222 Some("-02".into()),
1223 vec![
1224 "2020-09-08T00:00:00Z",
1225 "2020-09-08T01:00:00Z",
1226 "2020-09-08T02:00:00Z",
1227 "2020-09-08T03:00:00Z",
1228 "2020-09-08T04:00:00Z",
1229 ],
1230 ),
1231 (
1232 vec![
1233 "2020-09-08T00:30:00+05",
1234 "2020-09-08T01:30:00+05",
1235 "2020-09-08T02:30:00+05",
1236 "2020-09-08T03:30:00+05",
1237 "2020-09-08T04:30:00+05",
1238 ],
1239 Some("+05".into()),
1240 vec![
1241 "2020-09-08T00:00:00+05",
1242 "2020-09-08T01:00:00+05",
1243 "2020-09-08T02:00:00+05",
1244 "2020-09-08T03:00:00+05",
1245 "2020-09-08T04:00:00+05",
1246 ],
1247 ),
1248 (
1249 vec![
1250 "2020-09-08T00:30:00+08",
1251 "2020-09-08T01:30:00+08",
1252 "2020-09-08T02:30:00+08",
1253 "2020-09-08T03:30:00+08",
1254 "2020-09-08T04:30:00+08",
1255 ],
1256 Some("+08".into()),
1257 vec![
1258 "2020-09-08T00:00:00+08",
1259 "2020-09-08T01:00:00+08",
1260 "2020-09-08T02:00:00+08",
1261 "2020-09-08T03:00:00+08",
1262 "2020-09-08T04:00:00+08",
1263 ],
1264 ),
1265 (
1266 vec![
1267 "2024-10-26T23:30:00Z",
1268 "2024-10-27T00:30:00Z",
1269 "2024-10-27T01:30:00Z",
1270 "2024-10-27T02:30:00Z",
1271 ],
1272 Some("Europe/Berlin".into()),
1273 vec![
1274 "2024-10-27T01:00:00+02",
1275 "2024-10-27T02:00:00+02",
1276 "2024-10-27T02:00:00+01",
1277 "2024-10-27T03:00:00+01",
1278 ],
1279 ),
1280 (
1281 vec![
1282 "2018-02-18T00:30:00Z",
1283 "2018-02-18T01:30:00Z",
1284 "2018-02-18T02:30:00Z",
1285 "2018-02-18T03:30:00Z",
1286 "2018-11-04T01:00:00Z",
1287 "2018-11-04T02:00:00Z",
1288 "2018-11-04T03:00:00Z",
1289 "2018-11-04T04:00:00Z",
1290 ],
1291 Some("America/Sao_Paulo".into()),
1292 vec![
1293 "2018-02-17T22:00:00-02",
1294 "2018-02-17T23:00:00-02",
1295 "2018-02-17T23:00:00-03",
1296 "2018-02-18T00:00:00-03",
1297 "2018-11-03T22:00:00-03",
1298 "2018-11-03T23:00:00-03",
1299 "2018-11-04T01:00:00-02",
1300 "2018-11-04T02:00:00-02",
1301 ],
1302 ),
1303 (
1304 vec![
1305 "2024-10-26T23:30:00Z",
1306 "2024-10-27T00:30:00Z",
1307 "2024-10-27T01:30:00Z",
1308 "2024-10-27T02:30:00Z",
1309 ],
1310 Some("Asia/Kathmandu".into()), vec![
1312 "2024-10-27T05:00:00+05:45",
1313 "2024-10-27T06:00:00+05:45",
1314 "2024-10-27T07:00:00+05:45",
1315 "2024-10-27T08:00:00+05:45",
1316 ],
1317 ),
1318 ];
1319
1320 cases.iter().for_each(|(original, tz_opt, expected)| {
1321 let input = original
1322 .iter()
1323 .map(|s| Some(string_to_timestamp_nanos(s).unwrap()))
1324 .collect::<TimestampNanosecondArray>()
1325 .with_timezone_opt(tz_opt.clone());
1326 let right = expected
1327 .iter()
1328 .map(|s| Some(string_to_timestamp_nanos(s).unwrap()))
1329 .collect::<TimestampNanosecondArray>()
1330 .with_timezone_opt(tz_opt.clone());
1331 let batch_len = input.len();
1332 let arg_fields = vec![
1333 Field::new("a", DataType::Utf8, false).into(),
1334 Field::new("b", input.data_type().clone(), false).into(),
1335 ];
1336 let args = ScalarFunctionArgs {
1337 args: vec![
1338 ColumnarValue::Scalar(ScalarValue::from("hour")),
1339 ColumnarValue::Array(Arc::new(input)),
1340 ],
1341 arg_fields,
1342 number_rows: batch_len,
1343 return_field: Field::new(
1344 "f",
1345 DataType::Timestamp(TimeUnit::Nanosecond, tz_opt.clone()),
1346 true,
1347 )
1348 .into(),
1349 config_options: Arc::new(ConfigOptions::default()),
1350 };
1351 let result = DateTruncFunc::new().invoke_with_args(args).unwrap();
1352 if let ColumnarValue::Array(result) = result {
1353 assert_eq!(
1354 result.data_type(),
1355 &DataType::Timestamp(TimeUnit::Nanosecond, tz_opt.clone())
1356 );
1357 let left = as_primitive_array::<TimestampNanosecondType>(&result);
1358 assert_eq!(left, &right);
1359 } else {
1360 panic!("unexpected column type");
1361 }
1362 });
1363 }
1364
1365 #[test]
1366 fn test_date_trunc_fine_granularity_timezones() {
1367 let cases = [
1368 (
1370 vec![
1371 "2020-09-08T13:42:29.190855Z",
1372 "2020-09-08T13:42:30.500000Z",
1373 "2020-09-08T13:42:31.999999Z",
1374 ],
1375 Some("+00".into()),
1376 "second",
1377 vec![
1378 "2020-09-08T13:42:29.000000Z",
1379 "2020-09-08T13:42:30.000000Z",
1380 "2020-09-08T13:42:31.000000Z",
1381 ],
1382 ),
1383 (
1384 vec![
1385 "2020-09-08T13:42:29.190855+05",
1386 "2020-09-08T13:42:30.500000+05",
1387 "2020-09-08T13:42:31.999999+05",
1388 ],
1389 Some("+05".into()),
1390 "second",
1391 vec![
1392 "2020-09-08T13:42:29.000000+05",
1393 "2020-09-08T13:42:30.000000+05",
1394 "2020-09-08T13:42:31.000000+05",
1395 ],
1396 ),
1397 (
1398 vec![
1399 "2020-09-08T13:42:29.190855Z",
1400 "2020-09-08T13:42:30.500000Z",
1401 "2020-09-08T13:42:31.999999Z",
1402 ],
1403 Some("Europe/Berlin".into()),
1404 "second",
1405 vec![
1406 "2020-09-08T13:42:29.000000Z",
1407 "2020-09-08T13:42:30.000000Z",
1408 "2020-09-08T13:42:31.000000Z",
1409 ],
1410 ),
1411 (
1413 vec![
1414 "2020-09-08T13:42:29.190855Z",
1415 "2020-09-08T13:43:30.500000Z",
1416 "2020-09-08T13:44:31.999999Z",
1417 ],
1418 Some("+00".into()),
1419 "minute",
1420 vec![
1421 "2020-09-08T13:42:00.000000Z",
1422 "2020-09-08T13:43:00.000000Z",
1423 "2020-09-08T13:44:00.000000Z",
1424 ],
1425 ),
1426 (
1427 vec![
1428 "2020-09-08T13:42:29.190855+08",
1429 "2020-09-08T13:43:30.500000+08",
1430 "2020-09-08T13:44:31.999999+08",
1431 ],
1432 Some("+08".into()),
1433 "minute",
1434 vec![
1435 "2020-09-08T13:42:00.000000+08",
1436 "2020-09-08T13:43:00.000000+08",
1437 "2020-09-08T13:44:00.000000+08",
1438 ],
1439 ),
1440 (
1441 vec![
1442 "2020-09-08T13:42:29.190855Z",
1443 "2020-09-08T13:43:30.500000Z",
1444 "2020-09-08T13:44:31.999999Z",
1445 ],
1446 Some("America/Sao_Paulo".into()),
1447 "minute",
1448 vec![
1449 "2020-09-08T13:42:00.000000Z",
1450 "2020-09-08T13:43:00.000000Z",
1451 "2020-09-08T13:44:00.000000Z",
1452 ],
1453 ),
1454 (
1456 vec![
1457 "2020-09-08T13:42:29.190855Z",
1458 "2020-09-08T13:43:30.500000Z",
1459 "2020-09-08T13:44:31.999999Z",
1460 ],
1461 None,
1462 "minute",
1463 vec![
1464 "2020-09-08T13:42:00.000000Z",
1465 "2020-09-08T13:43:00.000000Z",
1466 "2020-09-08T13:44:00.000000Z",
1467 ],
1468 ),
1469 (
1471 vec![
1472 "2020-09-08T13:42:29.190855Z",
1473 "2020-09-08T13:42:29.191999Z",
1474 "2020-09-08T13:42:29.192500Z",
1475 ],
1476 Some("Asia/Kolkata".into()),
1477 "millisecond",
1478 vec![
1479 "2020-09-08T19:12:29.190000+05:30",
1480 "2020-09-08T19:12:29.191000+05:30",
1481 "2020-09-08T19:12:29.192000+05:30",
1482 ],
1483 ),
1484 ];
1485
1486 cases
1487 .iter()
1488 .for_each(|(original, tz_opt, granularity, expected)| {
1489 let input = original
1490 .iter()
1491 .map(|s| Some(string_to_timestamp_nanos(s).unwrap()))
1492 .collect::<TimestampNanosecondArray>()
1493 .with_timezone_opt(tz_opt.clone());
1494 let right = expected
1495 .iter()
1496 .map(|s| Some(string_to_timestamp_nanos(s).unwrap()))
1497 .collect::<TimestampNanosecondArray>()
1498 .with_timezone_opt(tz_opt.clone());
1499 let batch_len = input.len();
1500 let arg_fields = vec![
1501 Field::new("a", DataType::Utf8, false).into(),
1502 Field::new("b", input.data_type().clone(), false).into(),
1503 ];
1504 let args = ScalarFunctionArgs {
1505 args: vec![
1506 ColumnarValue::Scalar(ScalarValue::from(*granularity)),
1507 ColumnarValue::Array(Arc::new(input)),
1508 ],
1509 arg_fields,
1510 number_rows: batch_len,
1511 return_field: Field::new(
1512 "f",
1513 DataType::Timestamp(TimeUnit::Nanosecond, tz_opt.clone()),
1514 true,
1515 )
1516 .into(),
1517 config_options: Arc::new(ConfigOptions::default()),
1518 };
1519 let result = DateTruncFunc::new().invoke_with_args(args).unwrap();
1520 if let ColumnarValue::Array(result) = result {
1521 assert_eq!(
1522 result.data_type(),
1523 &DataType::Timestamp(TimeUnit::Nanosecond, tz_opt.clone()),
1524 "Failed for granularity: {granularity}, timezone: {tz_opt:?}"
1525 );
1526 let left = as_primitive_array::<TimestampNanosecondType>(&result);
1527 assert_eq!(
1528 left, &right,
1529 "Failed for granularity: {granularity}, timezone: {tz_opt:?}"
1530 );
1531 } else {
1532 panic!("unexpected column type");
1533 }
1534 });
1535 }
1536}