Skip to content

Commit 8ba1efe

Browse files
Inline chronoutils
1 parent 8c2007d commit 8ba1efe

4 files changed

Lines changed: 184 additions & 2 deletions

File tree

datafusion/physical-expr/Cargo.toml

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,6 @@ arrow = { version = "17.0.0", features = ["prettyprint"] }
4444
blake2 = { version = "^0.10.2", optional = true }
4545
blake3 = { version = "1.0", optional = true }
4646
chrono = { version = "0.4", default-features = false }
47-
chronoutil = "0.2.3"
4847
datafusion-common = { path = "../common", version = "9.0.0" }
4948
datafusion-expr = { path = "../expr", version = "9.0.0" }
5049
datafusion-row = { path = "../row", version = "9.0.0" }

datafusion/physical-expr/src/expressions/datetime.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,11 +15,11 @@
1515
// specific language governing permissions and limitations
1616
// under the License.
1717

18+
use crate::expressions::delta::shift_months;
1819
use crate::PhysicalExpr;
1920
use arrow::datatypes::{DataType, Schema};
2021
use arrow::record_batch::RecordBatch;
2122
use chrono::{Duration, NaiveDate};
22-
use chronoutil::shift_months;
2323
use datafusion_common::Result;
2424
use datafusion_common::{DataFusionError, ScalarValue};
2525
use datafusion_expr::{ColumnarValue, Operator};
Lines changed: 182 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,182 @@
1+
// MIT License
2+
//
3+
// Copyright (c) 2020-2022 Oliver Margetts
4+
//
5+
// Permission is hereby granted, free of charge, to any person obtaining a copy
6+
// of this software and associated documentation files (the "Software"), to deal
7+
// in the Software without restriction, including without limitation the rights
8+
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9+
// copies of the Software, and to permit persons to whom the Software is
10+
// furnished to do so, subject to the following conditions:
11+
//
12+
// The above copyright notice and this permission notice shall be included in all
13+
// copies or substantial portions of the Software.
14+
//
15+
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16+
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17+
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18+
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19+
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20+
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21+
// SOFTWARE.
22+
23+
// Copied from chronoutil crate
24+
25+
//! Contains utility functions for shifting Date objects.
26+
use chrono::Datelike;
27+
28+
/// Returns true if the year is a leap-year, as naively defined in the Gregorian calendar.
29+
#[inline]
30+
pub(crate) fn is_leap_year(year: i32) -> bool {
31+
year % 4 == 0 && (year % 100 != 0 || year % 400 == 0)
32+
}
33+
34+
// If the day lies within the month, this function has no effect. Otherwise, it shifts
35+
// day backwards to the final day of the month.
36+
// XXX: No attempt is made to handle days outside the 1-31 range.
37+
#[inline]
38+
fn normalise_day(year: i32, month: u32, day: u32) -> u32 {
39+
if day <= 28 {
40+
day
41+
} else if month == 2 {
42+
28 + is_leap_year(year) as u32
43+
} else if day == 31 && (month == 4 || month == 6 || month == 9 || month == 11) {
44+
30
45+
} else {
46+
day
47+
}
48+
}
49+
50+
/// Shift a date by the given number of months.
51+
/// Ambiguous month-ends are shifted backwards as necessary.
52+
pub(crate) fn shift_months<D: Datelike>(date: D, months: i32) -> D {
53+
let mut year = date.year() + (date.month() as i32 + months) / 12;
54+
let mut month = (date.month() as i32 + months) % 12;
55+
let mut day = date.day();
56+
57+
if month < 1 {
58+
year -= 1;
59+
month += 12;
60+
}
61+
62+
day = normalise_day(year, month as u32, day);
63+
64+
// This is slow but guaranteed to succeed (short of interger overflow)
65+
if day <= 28 {
66+
date.with_day(day)
67+
.unwrap()
68+
.with_month(month as u32)
69+
.unwrap()
70+
.with_year(year)
71+
.unwrap()
72+
} else {
73+
date.with_day(1)
74+
.unwrap()
75+
.with_month(month as u32)
76+
.unwrap()
77+
.with_year(year)
78+
.unwrap()
79+
.with_day(day)
80+
.unwrap()
81+
}
82+
}
83+
84+
#[cfg(test)]
85+
mod tests {
86+
use std::collections::HashSet;
87+
88+
use chrono::naive::{NaiveDate, NaiveDateTime, NaiveTime};
89+
90+
use super::*;
91+
92+
#[test]
93+
fn test_leap_year_cases() {
94+
let _leap_years: Vec<i32> = vec![
95+
1904, 1908, 1912, 1916, 1920, 1924, 1928, 1932, 1936, 1940, 1944, 1948, 1952,
96+
1956, 1960, 1964, 1968, 1972, 1976, 1980, 1984, 1988, 1992, 1996, 2000, 2004,
97+
2008, 2012, 2016, 2020,
98+
];
99+
let leap_years_1900_to_2020: HashSet<i32> = _leap_years.into_iter().collect();
100+
101+
for year in 1900..2021 {
102+
assert_eq!(is_leap_year(year), leap_years_1900_to_2020.contains(&year))
103+
}
104+
}
105+
106+
#[test]
107+
fn test_shift_months() {
108+
let base = NaiveDate::from_ymd(2020, 1, 31);
109+
110+
assert_eq!(shift_months(base, 0), NaiveDate::from_ymd(2020, 1, 31));
111+
assert_eq!(shift_months(base, 1), NaiveDate::from_ymd(2020, 2, 29));
112+
assert_eq!(shift_months(base, 2), NaiveDate::from_ymd(2020, 3, 31));
113+
assert_eq!(shift_months(base, 3), NaiveDate::from_ymd(2020, 4, 30));
114+
assert_eq!(shift_months(base, 4), NaiveDate::from_ymd(2020, 5, 31));
115+
assert_eq!(shift_months(base, 5), NaiveDate::from_ymd(2020, 6, 30));
116+
assert_eq!(shift_months(base, 6), NaiveDate::from_ymd(2020, 7, 31));
117+
assert_eq!(shift_months(base, 7), NaiveDate::from_ymd(2020, 8, 31));
118+
assert_eq!(shift_months(base, 8), NaiveDate::from_ymd(2020, 9, 30));
119+
assert_eq!(shift_months(base, 9), NaiveDate::from_ymd(2020, 10, 31));
120+
assert_eq!(shift_months(base, 10), NaiveDate::from_ymd(2020, 11, 30));
121+
assert_eq!(shift_months(base, 11), NaiveDate::from_ymd(2020, 12, 31));
122+
assert_eq!(shift_months(base, 12), NaiveDate::from_ymd(2021, 1, 31));
123+
assert_eq!(shift_months(base, 13), NaiveDate::from_ymd(2021, 2, 28));
124+
125+
assert_eq!(shift_months(base, -1), NaiveDate::from_ymd(2019, 12, 31));
126+
assert_eq!(shift_months(base, -2), NaiveDate::from_ymd(2019, 11, 30));
127+
assert_eq!(shift_months(base, -3), NaiveDate::from_ymd(2019, 10, 31));
128+
assert_eq!(shift_months(base, -4), NaiveDate::from_ymd(2019, 9, 30));
129+
assert_eq!(shift_months(base, -5), NaiveDate::from_ymd(2019, 8, 31));
130+
assert_eq!(shift_months(base, -6), NaiveDate::from_ymd(2019, 7, 31));
131+
assert_eq!(shift_months(base, -7), NaiveDate::from_ymd(2019, 6, 30));
132+
assert_eq!(shift_months(base, -8), NaiveDate::from_ymd(2019, 5, 31));
133+
assert_eq!(shift_months(base, -9), NaiveDate::from_ymd(2019, 4, 30));
134+
assert_eq!(shift_months(base, -10), NaiveDate::from_ymd(2019, 3, 31));
135+
assert_eq!(shift_months(base, -11), NaiveDate::from_ymd(2019, 2, 28));
136+
assert_eq!(shift_months(base, -12), NaiveDate::from_ymd(2019, 1, 31));
137+
assert_eq!(shift_months(base, -13), NaiveDate::from_ymd(2018, 12, 31));
138+
139+
assert_eq!(shift_months(base, 1265), NaiveDate::from_ymd(2125, 6, 30));
140+
}
141+
142+
#[test]
143+
fn test_shift_months_with_overflow() {
144+
let base = NaiveDate::from_ymd(2020, 12, 31);
145+
146+
assert_eq!(shift_months(base, 0), base);
147+
assert_eq!(shift_months(base, 1), NaiveDate::from_ymd(2021, 1, 31));
148+
assert_eq!(shift_months(base, 2), NaiveDate::from_ymd(2021, 2, 28));
149+
assert_eq!(shift_months(base, 12), NaiveDate::from_ymd(2021, 12, 31));
150+
assert_eq!(shift_months(base, 18), NaiveDate::from_ymd(2022, 6, 30));
151+
152+
assert_eq!(shift_months(base, -1), NaiveDate::from_ymd(2020, 11, 30));
153+
assert_eq!(shift_months(base, -2), NaiveDate::from_ymd(2020, 10, 31));
154+
assert_eq!(shift_months(base, -10), NaiveDate::from_ymd(2020, 2, 29));
155+
assert_eq!(shift_months(base, -12), NaiveDate::from_ymd(2019, 12, 31));
156+
assert_eq!(shift_months(base, -18), NaiveDate::from_ymd(2019, 6, 30));
157+
}
158+
159+
#[test]
160+
fn test_shift_months_datetime() {
161+
let date = NaiveDate::from_ymd(2020, 1, 31);
162+
let o_clock = NaiveTime::from_hms(1, 2, 3);
163+
164+
let base = NaiveDateTime::new(date, o_clock);
165+
166+
assert_eq!(
167+
shift_months(base, 0).date(),
168+
NaiveDate::from_ymd(2020, 1, 31)
169+
);
170+
assert_eq!(
171+
shift_months(base, 1).date(),
172+
NaiveDate::from_ymd(2020, 2, 29)
173+
);
174+
assert_eq!(
175+
shift_months(base, 2).date(),
176+
NaiveDate::from_ymd(2020, 3, 31)
177+
);
178+
assert_eq!(shift_months(base, 0).time(), o_clock);
179+
assert_eq!(shift_months(base, 1).time(), o_clock);
180+
assert_eq!(shift_months(base, 2).time(), o_clock);
181+
}
182+
}

datafusion/physical-expr/src/expressions/mod.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ mod case;
2323
mod cast;
2424
mod column;
2525
mod datetime;
26+
mod delta;
2627
mod get_indexed_field;
2728
mod in_list;
2829
mod is_not_null;

0 commit comments

Comments
 (0)