Skip to content

Commit 7d11a0b

Browse files
authored
fix: Raise ComputeError instead of panicking in truncate when mixing month/week/day/sub-daily units (#23176)
1 parent bc9374c commit 7d11a0b

File tree

9 files changed

+54
-49
lines changed

9 files changed

+54
-49
lines changed

crates/polars-time/src/group_by/dynamic.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -321,7 +321,7 @@ impl Wrap<&DataFrame> {
321321
include_lower_bound,
322322
include_upper_bound,
323323
options.start_by,
324-
);
324+
)?;
325325
update_bounds(lower, upper);
326326
PolarsResult::Ok(GroupsType::Slice {
327327
groups,
@@ -349,7 +349,7 @@ impl Wrap<&DataFrame> {
349349
include_lower_bound,
350350
include_upper_bound,
351351
options.start_by,
352-
);
352+
)?;
353353

354354
PolarsResult::Ok((
355355
groups

crates/polars-time/src/windows/duration.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -834,7 +834,7 @@ impl Duration {
834834
)
835835
},
836836
_ => {
837-
polars_bail!(ComputeError: "duration may not mix month, weeks and nanosecond units")
837+
polars_bail!(ComputeError: "cannot mix month, week, day, and sub-daily units for this operation")
838838
},
839839
}
840840
}

crates/polars-time/src/windows/group_by.rs

Lines changed: 10 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -151,7 +151,7 @@ pub fn group_by_windows(
151151
include_lower_bound: bool,
152152
include_upper_bound: bool,
153153
start_by: StartBy,
154-
) -> (GroupsSlice, Vec<i64>, Vec<i64>) {
154+
) -> PolarsResult<(GroupsSlice, Vec<i64>, Vec<i64>)> {
155155
let start = time[0];
156156
// the boundary we define here is not yet correct. It doesn't take 'period' into account
157157
// and it doesn't have the proper starting point. This boundary is used as a proxy to find
@@ -184,15 +184,13 @@ pub fn group_by_windows(
184184
#[cfg(feature = "timezones")]
185185
Some(tz) => {
186186
update_groups_and_bounds(
187-
window
188-
.get_overlapping_bounds_iter(
189-
boundary,
190-
closed_window,
191-
tu,
192-
tz.parse::<Tz>().ok().as_ref(),
193-
start_by,
194-
)
195-
.unwrap(),
187+
window.get_overlapping_bounds_iter(
188+
boundary,
189+
closed_window,
190+
tu,
191+
tz.parse::<Tz>().ok().as_ref(),
192+
start_by,
193+
)?,
196194
start_offset,
197195
time,
198196
closed_window,
@@ -205,9 +203,7 @@ pub fn group_by_windows(
205203
},
206204
_ => {
207205
update_groups_and_bounds(
208-
window
209-
.get_overlapping_bounds_iter(boundary, closed_window, tu, None, start_by)
210-
.unwrap(),
206+
window.get_overlapping_bounds_iter(boundary, closed_window, tu, None, start_by)?,
211207
start_offset,
212208
time,
213209
closed_window,
@@ -220,7 +216,7 @@ pub fn group_by_windows(
220216
},
221217
};
222218

223-
(groups, lower_bound, upper_bound)
219+
Ok((groups, lower_bound, upper_bound))
224220
}
225221

226222
// t is right at the end of the window

crates/polars-time/src/windows/test.rs

Lines changed: 32 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -120,7 +120,8 @@ fn test_groups_large_interval() {
120120
false,
121121
false,
122122
Default::default(),
123-
);
123+
)
124+
.unwrap();
124125
assert_eq!(groups.len(), 4);
125126
assert_eq!(groups[0], [0, 1]);
126127
assert_eq!(groups[1], [1, 1]);
@@ -135,7 +136,8 @@ fn test_groups_large_interval() {
135136
false,
136137
false,
137138
Default::default(),
138-
);
139+
)
140+
.unwrap();
139141
assert_eq!(groups.len(), 3);
140142
assert_eq!(groups[2], [3, 1]);
141143
let (groups, _, _) = group_by_windows(
@@ -147,7 +149,8 @@ fn test_groups_large_interval() {
147149
false,
148150
false,
149151
Default::default(),
150-
);
152+
)
153+
.unwrap();
151154
assert_eq!(groups.len(), 3);
152155
assert_eq!(groups[1], [1, 1]);
153156
}
@@ -226,7 +229,8 @@ fn test_boundaries() {
226229
true,
227230
true,
228231
Default::default(),
229-
);
232+
)
233+
.unwrap();
230234

231235
// 1st group
232236
// expected boundary:
@@ -328,7 +332,8 @@ fn test_boundaries() {
328332
false,
329333
false,
330334
Default::default(),
331-
);
335+
)
336+
.unwrap();
332337
assert_eq!(groups[0], [0, 2]); // 00:00:00 -> 00:30:00
333338
assert_eq!(groups[1], [2, 2]); // 01:00:00 -> 01:30:00
334339
assert_eq!(groups[2], [4, 2]); // 02:00:00 -> 02:30:00
@@ -343,7 +348,8 @@ fn test_boundaries() {
343348
false,
344349
false,
345350
Default::default(),
346-
);
351+
)
352+
.unwrap();
347353
assert_eq!(groups[0], [0, 1]); // (2021-12-15 23:30, 2021-12-16 00:00]
348354
assert_eq!(groups[1], [1, 2]); // (2021-12-16 00:00, 2021-12-16 00:30]
349355
assert_eq!(groups[2], [3, 2]); // (2021-12-16 00:30, 2021-12-16 01:00]
@@ -359,7 +365,8 @@ fn test_boundaries() {
359365
false,
360366
false,
361367
Default::default(),
362-
);
368+
)
369+
.unwrap();
363370
assert_eq!(groups[0], [1, 1]); // 00:00:00 -> 00:30:00
364371
assert_eq!(groups[1], [3, 1]); // 01:00:00 -> 01:30:00
365372
assert_eq!(groups[2], [5, 1]); // 02:00:00 -> 02:30:00
@@ -416,7 +423,8 @@ fn test_boundaries_2() {
416423
true,
417424
true,
418425
Default::default(),
419-
);
426+
)
427+
.unwrap();
420428

421429
// 1st group
422430
// expected boundary:
@@ -544,7 +552,8 @@ fn test_boundaries_ms() {
544552
true,
545553
true,
546554
Default::default(),
547-
);
555+
)
556+
.unwrap();
548557

549558
// 1st group
550559
// expected boundary:
@@ -646,7 +655,8 @@ fn test_boundaries_ms() {
646655
false,
647656
false,
648657
Default::default(),
649-
);
658+
)
659+
.unwrap();
650660
assert_eq!(groups[0], [0, 2]); // 00:00:00 -> 00:30:00
651661
assert_eq!(groups[1], [2, 2]); // 01:00:00 -> 01:30:00
652662
assert_eq!(groups[2], [4, 2]); // 02:00:00 -> 02:30:00
@@ -661,7 +671,8 @@ fn test_boundaries_ms() {
661671
false,
662672
false,
663673
Default::default(),
664-
);
674+
)
675+
.unwrap();
665676
assert_eq!(groups[0], [0, 1]); // (2021-12-15 23:30, 2021-12-16 00:00]
666677
assert_eq!(groups[1], [1, 2]); // (2021-12-16 00:00, 2021-12-16 00:30]
667678
assert_eq!(groups[2], [3, 2]); // (2021-12-16 00:30, 2021-12-16 01:00]
@@ -677,7 +688,8 @@ fn test_boundaries_ms() {
677688
false,
678689
false,
679690
Default::default(),
680-
);
691+
)
692+
.unwrap();
681693
assert_eq!(groups[0], [1, 1]); // 00:00:00 -> 00:30:00
682694
assert_eq!(groups[1], [3, 1]); // 01:00:00 -> 01:30:00
683695
assert_eq!(groups[2], [5, 1]); // 02:00:00 -> 02:30:00
@@ -840,7 +852,8 @@ fn test_end_membership() {
840852
false,
841853
false,
842854
Default::default(),
843-
);
855+
)
856+
.unwrap();
844857
assert_eq!(groups[0], [0, 1]);
845858
assert_eq!(groups[1], [0, 1]);
846859
assert_eq!(groups[2], [1, 1]);
@@ -864,7 +877,8 @@ fn test_group_by_windows_membership_2791() {
864877
false,
865878
false,
866879
Default::default(),
867-
);
880+
)
881+
.unwrap();
868882
assert_eq!(groups[0], [0, 2]);
869883
assert_eq!(groups[1], [2, 2]);
870884
}
@@ -887,7 +901,8 @@ fn test_group_by_windows_duplicates_2931() {
887901
false,
888902
false,
889903
Default::default(),
890-
);
904+
)
905+
.unwrap();
891906
assert_eq!(groups, [[0, 1], [1, 2], [3, 2]]);
892907
}
893908

@@ -923,6 +938,7 @@ fn test_group_by_windows_offsets_3776() {
923938
false,
924939
false,
925940
Default::default(),
926-
);
941+
)
942+
.unwrap();
927943
assert_eq!(groups, [[0, 1], [1, 1], [2, 1]]);
928944
}

py-polars/polars/dataframe/frame.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7033,7 +7033,7 @@ def group_by_dynamic(
70337033
- 1y (1 calendar year)
70347034
- 1i (1 index count)
70357035
7036-
Or combine them:
7036+
Or combine them (except in `every`):
70377037
"3d12h4m25s" # 3 days, 12 hours, 4 minutes, and 25 seconds
70387038
70397039
By "calendar day", we mean the corresponding time on the next day (which may

py-polars/polars/expr/datetime.py

Lines changed: 0 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -207,10 +207,6 @@ def truncate(self, every: str | dt.timedelta | Expr) -> Expr:
207207
- 1q (1 calendar quarter)
208208
- 1y (1 calendar year)
209209
210-
These strings can be combined:
211-
212-
- 3d12h4m25s # 3 days, 12 hours, 4 minutes, and 25 seconds
213-
214210
By "calendar day", we mean the corresponding time on the next day (which may
215211
not be 24 hours, due to daylight savings). Similarly for "calendar week",
216212
"calendar month", "calendar quarter", and "calendar year".
@@ -340,8 +336,6 @@ def round(self, every: str | dt.timedelta | IntoExprColumn) -> Expr:
340336
- 1q (1 calendar quarter)
341337
- 1y (1 calendar year)
342338
343-
eg: 3d12h4m25s # 3 days, 12 hours, 4 minutes, and 25 seconds
344-
345339
By "calendar day", we mean the corresponding time on the next day (which may
346340
not be 24 hours, due to daylight savings). Similarly for "calendar week",
347341
"calendar month", "calendar quarter", and "calendar year".

py-polars/polars/lazyframe/frame.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4794,7 +4794,7 @@ def group_by_dynamic(
47944794
- 1y (1 calendar year)
47954795
- 1i (1 index count)
47964796
4797-
Or combine them:
4797+
Or combine them (except in `every`):
47984798
"3d12h4m25s" # 3 days, 12 hours, 4 minutes, and 25 seconds
47994799
48004800
By "calendar day", we mean the corresponding time on the next day (which may

py-polars/polars/series/datetime.py

Lines changed: 0 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1828,10 +1828,6 @@ def truncate(self, every: str | dt.timedelta | IntoExprColumn) -> Series:
18281828
- 1q (1 calendar quarter)
18291829
- 1y (1 calendar year)
18301830
1831-
These strings can be combined:
1832-
1833-
- 3d12h4m25s # 3 days, 12 hours, 4 minutes, and 25 seconds
1834-
18351831
By "calendar day", we mean the corresponding time on the next day (which may
18361832
not be 24 hours, due to daylight savings). Similarly for "calendar week",
18371833
"calendar month", "calendar quarter", and "calendar year".
@@ -1950,10 +1946,6 @@ def round(self, every: str | dt.timedelta | IntoExprColumn) -> Series:
19501946
- 1q (1 calendar quarter)
19511947
- 1y (1 calendar year)
19521948
1953-
These strings can be combined:
1954-
1955-
- 3d12h4m25s # 3 days, 12 hours, 4 minutes, and 25 seconds
1956-
19571949
By "calendar day", we mean the corresponding time on the next day (which may
19581950
not be 24 hours, due to daylight savings). Similarly for "calendar week",
19591951
"calendar month", "calendar quarter", and "calendar year".

py-polars/tests/unit/operations/namespaces/temporal/test_truncate.py

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99

1010
import polars as pl
1111
from polars._utils.convert import parse_as_duration_string
12+
from polars.exceptions import ComputeError
1213
from polars.testing import assert_series_equal
1314

1415
if TYPE_CHECKING:
@@ -159,3 +160,9 @@ def test_truncate_origin_22590(
159160
.item()
160161
)
161162
assert result == expected, result
163+
164+
165+
def test_truncate_invalid() -> None:
166+
s = pl.Series([date(2020, 1, 1)])
167+
with pytest.raises(ComputeError, match="cannot mix"):
168+
s.dt.truncate("1d1h")

0 commit comments

Comments
 (0)