1use ferray_core::Array;
56use ferray_core::dimension::Dimension;
57use ferray_core::dtype::{DateTime64, NAT, TimeUnit, Timedelta64};
58use ferray_core::error::{FerrayError, FerrayResult};
59
60pub fn isnat_datetime<D: Dimension>(input: &Array<DateTime64, D>) -> FerrayResult<Array<bool, D>> {
70 let data: Vec<bool> = input.iter().map(|v| v.is_nat()).collect();
71 Array::from_vec(input.dim().clone(), data)
72}
73
74pub fn isnat_timedelta<D: Dimension>(
81 input: &Array<Timedelta64, D>,
82) -> FerrayResult<Array<bool, D>> {
83 let data: Vec<bool> = input.iter().map(|v| v.is_nat()).collect();
84 Array::from_vec(input.dim().clone(), data)
85}
86
87#[inline]
94fn nat_propagate(a: i64, b: i64, op: impl FnOnce(i64, i64) -> i64) -> i64 {
95 if a == NAT || b == NAT { NAT } else { op(a, b) }
96}
97
98fn check_same_shape<A, B, DA, DB>(
99 a: &Array<A, DA>,
100 b: &Array<B, DB>,
101 name: &str,
102) -> FerrayResult<()>
103where
104 A: ferray_core::Element,
105 B: ferray_core::Element,
106 DA: Dimension,
107 DB: Dimension,
108{
109 if a.shape() != b.shape() {
110 return Err(FerrayError::shape_mismatch(format!(
111 "{name}: shapes {:?} and {:?} differ",
112 a.shape(),
113 b.shape()
114 )));
115 }
116 Ok(())
117}
118
119pub fn sub_datetime<D: Dimension>(
128 a: &Array<DateTime64, D>,
129 b: &Array<DateTime64, D>,
130) -> FerrayResult<Array<Timedelta64, D>> {
131 check_same_shape(a, b, "sub_datetime")?;
132 let data: Vec<Timedelta64> = a
133 .iter()
134 .zip(b.iter())
135 .map(|(x, y)| Timedelta64(nat_propagate(x.0, y.0, |p, q| p.wrapping_sub(q))))
136 .collect();
137 Array::from_vec(a.dim().clone(), data)
138}
139
140pub fn add_datetime_timedelta<D: Dimension>(
145 a: &Array<DateTime64, D>,
146 b: &Array<Timedelta64, D>,
147) -> FerrayResult<Array<DateTime64, D>> {
148 check_same_shape(a, b, "add_datetime_timedelta")?;
149 let data: Vec<DateTime64> = a
150 .iter()
151 .zip(b.iter())
152 .map(|(x, y)| DateTime64(nat_propagate(x.0, y.0, |p, q| p.wrapping_add(q))))
153 .collect();
154 Array::from_vec(a.dim().clone(), data)
155}
156
157pub fn sub_datetime_timedelta<D: Dimension>(
162 a: &Array<DateTime64, D>,
163 b: &Array<Timedelta64, D>,
164) -> FerrayResult<Array<DateTime64, D>> {
165 check_same_shape(a, b, "sub_datetime_timedelta")?;
166 let data: Vec<DateTime64> = a
167 .iter()
168 .zip(b.iter())
169 .map(|(x, y)| DateTime64(nat_propagate(x.0, y.0, |p, q| p.wrapping_sub(q))))
170 .collect();
171 Array::from_vec(a.dim().clone(), data)
172}
173
174pub fn add_timedelta<D: Dimension>(
179 a: &Array<Timedelta64, D>,
180 b: &Array<Timedelta64, D>,
181) -> FerrayResult<Array<Timedelta64, D>> {
182 check_same_shape(a, b, "add_timedelta")?;
183 let data: Vec<Timedelta64> = a
184 .iter()
185 .zip(b.iter())
186 .map(|(x, y)| Timedelta64(nat_propagate(x.0, y.0, |p, q| p.wrapping_add(q))))
187 .collect();
188 Array::from_vec(a.dim().clone(), data)
189}
190
191pub fn sub_timedelta<D: Dimension>(
196 a: &Array<Timedelta64, D>,
197 b: &Array<Timedelta64, D>,
198) -> FerrayResult<Array<Timedelta64, D>> {
199 check_same_shape(a, b, "sub_timedelta")?;
200 let data: Vec<Timedelta64> = a
201 .iter()
202 .zip(b.iter())
203 .map(|(x, y)| Timedelta64(nat_propagate(x.0, y.0, |p, q| p.wrapping_sub(q))))
204 .collect();
205 Array::from_vec(a.dim().clone(), data)
206}
207
208#[inline]
222fn rescale_datetime(arr: &[DateTime64], factor: i64) -> Vec<DateTime64> {
223 arr.iter()
224 .map(|v| {
225 if v.is_nat() {
226 DateTime64(NAT)
227 } else {
228 DateTime64(v.0.wrapping_mul(factor))
229 }
230 })
231 .collect()
232}
233
234#[inline]
235fn rescale_timedelta(arr: &[Timedelta64], factor: i64) -> Vec<Timedelta64> {
236 arr.iter()
237 .map(|v| {
238 if v.is_nat() {
239 Timedelta64(NAT)
240 } else {
241 Timedelta64(v.0.wrapping_mul(factor))
242 }
243 })
244 .collect()
245}
246
247pub fn sub_datetime_promoted<D: Dimension>(
255 a: &Array<DateTime64, D>,
256 unit_a: TimeUnit,
257 b: &Array<DateTime64, D>,
258 unit_b: TimeUnit,
259) -> FerrayResult<(Array<Timedelta64, D>, TimeUnit)> {
260 check_same_shape(a, b, "sub_datetime_promoted")?;
261 let target = unit_a.finer(unit_b);
262 let scale_a = unit_a.scale_to(target).ok_or_else(|| {
263 FerrayError::invalid_value(format!(
264 "sub_datetime_promoted: cannot rescale {unit_a:?} → {target:?} (non-divisible)"
265 ))
266 })?;
267 let scale_b = unit_b.scale_to(target).ok_or_else(|| {
268 FerrayError::invalid_value(format!(
269 "sub_datetime_promoted: cannot rescale {unit_b:?} → {target:?} (non-divisible)"
270 ))
271 })?;
272 let a_data: Vec<DateTime64> = a.iter().copied().collect();
273 let b_data: Vec<DateTime64> = b.iter().copied().collect();
274 let a_rescaled = if scale_a == 1 {
275 a_data
276 } else {
277 rescale_datetime(&a_data, scale_a)
278 };
279 let b_rescaled = if scale_b == 1 {
280 b_data
281 } else {
282 rescale_datetime(&b_data, scale_b)
283 };
284 let data: Vec<Timedelta64> = a_rescaled
285 .iter()
286 .zip(b_rescaled.iter())
287 .map(|(x, y)| Timedelta64(nat_propagate(x.0, y.0, |p, q| p.wrapping_sub(q))))
288 .collect();
289 let arr = Array::from_vec(a.dim().clone(), data)?;
290 Ok((arr, target))
291}
292
293pub fn add_datetime_timedelta_promoted<D: Dimension>(
298 a: &Array<DateTime64, D>,
299 unit_a: TimeUnit,
300 b: &Array<Timedelta64, D>,
301 unit_b: TimeUnit,
302) -> FerrayResult<(Array<DateTime64, D>, TimeUnit)> {
303 check_same_shape(a, b, "add_datetime_timedelta_promoted")?;
304 let target = unit_a.finer(unit_b);
305 let scale_a = unit_a.scale_to(target).ok_or_else(|| {
306 FerrayError::invalid_value(format!(
307 "add_datetime_timedelta_promoted: cannot rescale {unit_a:?} → {target:?}"
308 ))
309 })?;
310 let scale_b = unit_b.scale_to(target).ok_or_else(|| {
311 FerrayError::invalid_value(format!(
312 "add_datetime_timedelta_promoted: cannot rescale {unit_b:?} → {target:?}"
313 ))
314 })?;
315 let a_data: Vec<DateTime64> = a.iter().copied().collect();
316 let b_data: Vec<Timedelta64> = b.iter().copied().collect();
317 let a_rescaled = if scale_a == 1 {
318 a_data
319 } else {
320 rescale_datetime(&a_data, scale_a)
321 };
322 let b_rescaled = if scale_b == 1 {
323 b_data
324 } else {
325 rescale_timedelta(&b_data, scale_b)
326 };
327 let data: Vec<DateTime64> = a_rescaled
328 .iter()
329 .zip(b_rescaled.iter())
330 .map(|(x, y)| DateTime64(nat_propagate(x.0, y.0, |p, q| p.wrapping_add(q))))
331 .collect();
332 let arr = Array::from_vec(a.dim().clone(), data)?;
333 Ok((arr, target))
334}
335
336pub fn add_timedelta_promoted<D: Dimension>(
341 a: &Array<Timedelta64, D>,
342 unit_a: TimeUnit,
343 b: &Array<Timedelta64, D>,
344 unit_b: TimeUnit,
345) -> FerrayResult<(Array<Timedelta64, D>, TimeUnit)> {
346 check_same_shape(a, b, "add_timedelta_promoted")?;
347 let target = unit_a.finer(unit_b);
348 let scale_a = unit_a.scale_to(target).ok_or_else(|| {
349 FerrayError::invalid_value(format!(
350 "add_timedelta_promoted: cannot rescale {unit_a:?} → {target:?}"
351 ))
352 })?;
353 let scale_b = unit_b.scale_to(target).ok_or_else(|| {
354 FerrayError::invalid_value(format!(
355 "add_timedelta_promoted: cannot rescale {unit_b:?} → {target:?}"
356 ))
357 })?;
358 let a_data: Vec<Timedelta64> = a.iter().copied().collect();
359 let b_data: Vec<Timedelta64> = b.iter().copied().collect();
360 let a_rescaled = if scale_a == 1 {
361 a_data
362 } else {
363 rescale_timedelta(&a_data, scale_a)
364 };
365 let b_rescaled = if scale_b == 1 {
366 b_data
367 } else {
368 rescale_timedelta(&b_data, scale_b)
369 };
370 let data: Vec<Timedelta64> = a_rescaled
371 .iter()
372 .zip(b_rescaled.iter())
373 .map(|(x, y)| Timedelta64(nat_propagate(x.0, y.0, |p, q| p.wrapping_add(q))))
374 .collect();
375 let arr = Array::from_vec(a.dim().clone(), data)?;
376 Ok((arr, target))
377}
378
379#[inline]
416fn rescale_td_ticks(arr: &[Timedelta64], factor: i64) -> Vec<i64> {
417 arr.iter()
418 .map(|v| {
419 if v.is_nat() {
420 NAT
421 } else {
422 v.0.wrapping_mul(factor)
423 }
424 })
425 .collect()
426}
427
428fn promote_td_pair<D: Dimension>(
439 a: &Array<Timedelta64, D>,
440 unit_a: TimeUnit,
441 b: &Array<Timedelta64, D>,
442 unit_b: TimeUnit,
443 name: &str,
444) -> FerrayResult<(Vec<i64>, Vec<i64>, TimeUnit)> {
445 check_same_shape(a, b, name)?;
446 let target = unit_a.finer(unit_b);
447 let scale_a = unit_a.scale_to(target).ok_or_else(|| {
448 FerrayError::invalid_value(format!("{name}: cannot rescale {unit_a:?} → {target:?}"))
449 })?;
450 let scale_b = unit_b.scale_to(target).ok_or_else(|| {
451 FerrayError::invalid_value(format!("{name}: cannot rescale {unit_b:?} → {target:?}"))
452 })?;
453 let a_data: Vec<Timedelta64> = a.iter().copied().collect();
454 let b_data: Vec<Timedelta64> = b.iter().copied().collect();
455 let a_ticks = if scale_a == 1 {
456 a_data.iter().map(|v| v.0).collect()
457 } else {
458 rescale_td_ticks(&a_data, scale_a)
459 };
460 let b_ticks = if scale_b == 1 {
461 b_data.iter().map(|v| v.0).collect()
462 } else {
463 rescale_td_ticks(&b_data, scale_b)
464 };
465 Ok((a_ticks, b_ticks, target))
466}
467
468pub fn mul_timedelta_scalar_i64<D: Dimension>(
478 a: &Array<Timedelta64, D>,
479 k: i64,
480) -> FerrayResult<Array<Timedelta64, D>> {
481 let data: Vec<Timedelta64> = a
482 .iter()
483 .map(|v| {
484 if v.is_nat() {
485 Timedelta64(NAT)
486 } else {
487 Timedelta64(v.0.wrapping_mul(k))
488 }
489 })
490 .collect();
491 Array::from_vec(a.dim().clone(), data)
492}
493
494pub fn mul_timedelta_scalar_f64<D: Dimension>(
504 a: &Array<Timedelta64, D>,
505 k: f64,
506) -> FerrayResult<Array<Timedelta64, D>> {
507 let data: Vec<Timedelta64> = a
508 .iter()
509 .map(|v| {
510 if v.is_nat() {
511 Timedelta64(NAT)
512 } else {
513 let r = v.0 as f64 * k;
514 if r.is_finite() {
515 Timedelta64(r as i64)
516 } else {
517 Timedelta64(NAT)
518 }
519 }
520 })
521 .collect();
522 Array::from_vec(a.dim().clone(), data)
523}
524
525pub fn div_timedelta_scalar_i64<D: Dimension>(
534 a: &Array<Timedelta64, D>,
535 k: i64,
536) -> FerrayResult<Array<Timedelta64, D>> {
537 let data: Vec<Timedelta64> = a
538 .iter()
539 .map(|v| {
540 if v.is_nat() || k == 0 {
541 Timedelta64(NAT)
542 } else {
543 Timedelta64(v.0.wrapping_div(k))
544 }
545 })
546 .collect();
547 Array::from_vec(a.dim().clone(), data)
548}
549
550pub fn div_timedelta_scalar_f64<D: Dimension>(
560 a: &Array<Timedelta64, D>,
561 k: f64,
562) -> FerrayResult<Array<Timedelta64, D>> {
563 let data: Vec<Timedelta64> = a
564 .iter()
565 .map(|v| {
566 if v.is_nat() {
567 Timedelta64(NAT)
568 } else {
569 let r = v.0 as f64 / k;
570 if r.is_finite() {
571 Timedelta64(r as i64)
572 } else {
573 Timedelta64(NAT)
574 }
575 }
576 })
577 .collect();
578 Array::from_vec(a.dim().clone(), data)
579}
580
581pub fn truediv_timedelta<D: Dimension>(
594 a: &Array<Timedelta64, D>,
595 unit_a: TimeUnit,
596 b: &Array<Timedelta64, D>,
597 unit_b: TimeUnit,
598) -> FerrayResult<Array<f64, D>> {
599 let (at, bt, _target) = promote_td_pair(a, unit_a, b, unit_b, "truediv_timedelta")?;
600 let data: Vec<f64> = at
601 .iter()
602 .zip(bt.iter())
603 .map(|(&x, &y)| {
604 if x == NAT || y == NAT {
605 f64::NAN
606 } else {
607 x as f64 / y as f64
608 }
609 })
610 .collect();
611 Array::from_vec(a.dim().clone(), data)
612}
613
614pub fn floordiv_timedelta<D: Dimension>(
628 a: &Array<Timedelta64, D>,
629 unit_a: TimeUnit,
630 b: &Array<Timedelta64, D>,
631 unit_b: TimeUnit,
632) -> FerrayResult<Array<i64, D>> {
633 let (at, bt, _target) = promote_td_pair(a, unit_a, b, unit_b, "floordiv_timedelta")?;
634 let data: Vec<i64> = at
635 .iter()
636 .zip(bt.iter())
637 .map(|(&x, &y)| {
638 if x == NAT || y == NAT || y == 0 {
639 0
640 } else {
641 let q = x.wrapping_div(y);
645 if (x % y != 0) && ((x < 0) != (y < 0)) {
646 q - 1
647 } else {
648 q
649 }
650 }
651 })
652 .collect();
653 Array::from_vec(a.dim().clone(), data)
654}
655
656pub fn mod_timedelta<D: Dimension>(
670 a: &Array<Timedelta64, D>,
671 unit_a: TimeUnit,
672 b: &Array<Timedelta64, D>,
673 unit_b: TimeUnit,
674) -> FerrayResult<(Array<Timedelta64, D>, TimeUnit)> {
675 let (at, bt, target) = promote_td_pair(a, unit_a, b, unit_b, "mod_timedelta")?;
676 let data: Vec<Timedelta64> = at
677 .iter()
678 .zip(bt.iter())
679 .map(|(&x, &y)| {
680 if x == NAT || y == NAT || y == 0 {
681 Timedelta64(NAT)
682 } else {
683 let rem = x.wrapping_rem(y);
684 if rem == 0 || ((x > 0) == (y > 0)) {
685 Timedelta64(rem)
686 } else {
687 Timedelta64(rem.wrapping_add(y))
688 }
689 }
690 })
691 .collect();
692 let arr = Array::from_vec(a.dim().clone(), data)?;
693 Ok((arr, target))
694}
695
696#[cfg(test)]
697mod tests {
698 use super::*;
699 use ferray_core::dimension::Ix1;
700
701 #[test]
702 fn isnat_datetime_flags_min_int() {
703 let data = vec![
704 DateTime64(0),
705 DateTime64::nat(),
706 DateTime64(1_700_000_000_000_000_000),
707 DateTime64::nat(),
708 ];
709 let arr = Array::<DateTime64, Ix1>::from_vec(Ix1::new([4]), data).unwrap();
710 let r = isnat_datetime(&arr).unwrap();
711 let v: Vec<bool> = r.iter().copied().collect();
712 assert_eq!(v, vec![false, true, false, true]);
713 }
714
715 #[test]
716 fn isnat_timedelta_flags_min_int() {
717 let data = vec![Timedelta64(1_000), Timedelta64::nat(), Timedelta64(0)];
718 let arr = Array::<Timedelta64, Ix1>::from_vec(Ix1::new([3]), data).unwrap();
719 let r = isnat_timedelta(&arr).unwrap();
720 let v: Vec<bool> = r.iter().copied().collect();
721 assert_eq!(v, vec![false, true, false]);
722 }
723
724 #[test]
725 fn isnat_datetime_all_finite() {
726 let data = vec![DateTime64(1), DateTime64(2), DateTime64(3)];
727 let arr = Array::<DateTime64, Ix1>::from_vec(Ix1::new([3]), data).unwrap();
728 let r = isnat_datetime(&arr).unwrap();
729 let v: Vec<bool> = r.iter().copied().collect();
730 assert_eq!(v, vec![false, false, false]);
731 }
732
733 #[test]
734 fn isnat_datetime_all_nat() {
735 let data = vec![DateTime64::nat(); 5];
736 let arr = Array::<DateTime64, Ix1>::from_vec(Ix1::new([5]), data).unwrap();
737 let r = isnat_datetime(&arr).unwrap();
738 let v: Vec<bool> = r.iter().copied().collect();
739 assert_eq!(v, vec![true; 5]);
740 }
741
742 #[test]
745 fn sub_datetime_basic() {
746 let a = Array::<DateTime64, Ix1>::from_vec(
747 Ix1::new([3]),
748 vec![DateTime64(100), DateTime64(200), DateTime64(50)],
749 )
750 .unwrap();
751 let b = Array::<DateTime64, Ix1>::from_vec(
752 Ix1::new([3]),
753 vec![DateTime64(40), DateTime64(150), DateTime64(50)],
754 )
755 .unwrap();
756 let r = sub_datetime(&a, &b).unwrap();
757 let v: Vec<i64> = r.iter().map(|x| x.0).collect();
758 assert_eq!(v, vec![60, 50, 0]);
759 }
760
761 #[test]
762 fn sub_datetime_propagates_nat() {
763 let a = Array::<DateTime64, Ix1>::from_vec(
764 Ix1::new([3]),
765 vec![DateTime64(100), DateTime64::nat(), DateTime64(50)],
766 )
767 .unwrap();
768 let b = Array::<DateTime64, Ix1>::from_vec(
769 Ix1::new([3]),
770 vec![DateTime64(40), DateTime64(150), DateTime64::nat()],
771 )
772 .unwrap();
773 let r = sub_datetime(&a, &b).unwrap();
774 let v: Vec<i64> = r.iter().map(|x| x.0).collect();
775 assert_eq!(v[0], 60);
776 assert_eq!(v[1], NAT);
777 assert_eq!(v[2], NAT);
778 }
779
780 #[test]
781 fn add_datetime_timedelta_basic() {
782 let a = Array::<DateTime64, Ix1>::from_vec(
783 Ix1::new([2]),
784 vec![DateTime64(1000), DateTime64(2000)],
785 )
786 .unwrap();
787 let b = Array::<Timedelta64, Ix1>::from_vec(
788 Ix1::new([2]),
789 vec![Timedelta64(50), Timedelta64::nat()],
790 )
791 .unwrap();
792 let r = add_datetime_timedelta(&a, &b).unwrap();
793 let v: Vec<i64> = r.iter().map(|x| x.0).collect();
794 assert_eq!(v[0], 1050);
795 assert_eq!(v[1], NAT); }
797
798 #[test]
799 fn sub_datetime_timedelta_basic() {
800 let a = Array::<DateTime64, Ix1>::from_vec(
801 Ix1::new([2]),
802 vec![DateTime64(1000), DateTime64(500)],
803 )
804 .unwrap();
805 let b = Array::<Timedelta64, Ix1>::from_vec(
806 Ix1::new([2]),
807 vec![Timedelta64(100), Timedelta64(50)],
808 )
809 .unwrap();
810 let r = sub_datetime_timedelta(&a, &b).unwrap();
811 let v: Vec<i64> = r.iter().map(|x| x.0).collect();
812 assert_eq!(v, vec![900, 450]);
813 }
814
815 #[test]
816 fn add_sub_timedelta_basic() {
817 let a = Array::<Timedelta64, Ix1>::from_vec(
818 Ix1::new([2]),
819 vec![Timedelta64(10), Timedelta64(20)],
820 )
821 .unwrap();
822 let b = Array::<Timedelta64, Ix1>::from_vec(
823 Ix1::new([2]),
824 vec![Timedelta64(3), Timedelta64(7)],
825 )
826 .unwrap();
827 let s = add_timedelta(&a, &b).unwrap();
828 let sv: Vec<i64> = s.iter().map(|x| x.0).collect();
829 assert_eq!(sv, vec![13, 27]);
830 let d = sub_timedelta(&a, &b).unwrap();
831 let dv: Vec<i64> = d.iter().map(|x| x.0).collect();
832 assert_eq!(dv, vec![7, 13]);
833 }
834
835 #[test]
836 fn arithmetic_shape_mismatch_errors() {
837 let a =
838 Array::<DateTime64, Ix1>::from_vec(Ix1::new([2]), vec![DateTime64(1), DateTime64(2)])
839 .unwrap();
840 let b = Array::<DateTime64, Ix1>::from_vec(
841 Ix1::new([3]),
842 vec![DateTime64(1), DateTime64(2), DateTime64(3)],
843 )
844 .unwrap();
845 assert!(sub_datetime(&a, &b).is_err());
846 }
847
848 #[test]
851 fn timeunit_finer_picks_finer() {
852 assert_eq!(TimeUnit::Ns.finer(TimeUnit::Us), TimeUnit::Ns);
853 assert_eq!(TimeUnit::Us.finer(TimeUnit::Ns), TimeUnit::Ns);
854 assert_eq!(TimeUnit::S.finer(TimeUnit::Ms), TimeUnit::Ms);
855 assert_eq!(TimeUnit::Ns.finer(TimeUnit::Ns), TimeUnit::Ns);
856 }
857
858 #[test]
859 fn timeunit_scale_to_correct_factors() {
860 assert_eq!(TimeUnit::Us.scale_to(TimeUnit::Ns), Some(1_000));
861 assert_eq!(TimeUnit::S.scale_to(TimeUnit::Us), Some(1_000_000));
862 assert_eq!(TimeUnit::Ns.scale_to(TimeUnit::S), None);
863 assert_eq!(TimeUnit::Ns.scale_to(TimeUnit::Ns), Some(1));
864 }
865
866 #[test]
867 fn sub_datetime_promoted_us_minus_ns() {
868 let a =
869 Array::<DateTime64, Ix1>::from_vec(Ix1::new([2]), vec![DateTime64(5), DateTime64(10)])
870 .unwrap();
871 let b = Array::<DateTime64, Ix1>::from_vec(
872 Ix1::new([2]),
873 vec![DateTime64(1234), DateTime64(7777)],
874 )
875 .unwrap();
876 let (result, unit) = sub_datetime_promoted(&a, TimeUnit::Us, &b, TimeUnit::Ns).unwrap();
877 assert_eq!(unit, TimeUnit::Ns);
878 let v: Vec<i64> = result.iter().map(|x| x.0).collect();
879 assert_eq!(v, vec![5_000 - 1_234, 10_000 - 7_777]);
880 }
881
882 #[test]
883 fn sub_datetime_promoted_propagates_nat() {
884 let a = Array::<DateTime64, Ix1>::from_vec(
885 Ix1::new([2]),
886 vec![DateTime64(5), DateTime64::nat()],
887 )
888 .unwrap();
889 let b = Array::<DateTime64, Ix1>::from_vec(
890 Ix1::new([2]),
891 vec![DateTime64(1000), DateTime64(2000)],
892 )
893 .unwrap();
894 let (result, _) = sub_datetime_promoted(&a, TimeUnit::Us, &b, TimeUnit::Ns).unwrap();
895 let v: Vec<i64> = result.iter().map(|x| x.0).collect();
896 assert_eq!(v[0], 5_000 - 1_000);
897 assert_eq!(v[1], NAT);
898 }
899
900 #[test]
901 fn add_datetime_timedelta_promoted_basic() {
902 let a = Array::<DateTime64, Ix1>::from_vec(Ix1::new([1]), vec![DateTime64(2)]).unwrap();
903 let b = Array::<Timedelta64, Ix1>::from_vec(Ix1::new([1]), vec![Timedelta64(500)]).unwrap();
904 let (result, unit) =
905 add_datetime_timedelta_promoted(&a, TimeUnit::S, &b, TimeUnit::Ms).unwrap();
906 assert_eq!(unit, TimeUnit::Ms);
907 assert_eq!(result.iter().next().unwrap().0, 2_500);
909 }
910
911 #[test]
912 fn add_timedelta_promoted_basic() {
913 let a = Array::<Timedelta64, Ix1>::from_vec(Ix1::new([1]), vec![Timedelta64(3)]).unwrap();
914 let b = Array::<Timedelta64, Ix1>::from_vec(Ix1::new([1]), vec![Timedelta64(250)]).unwrap();
915 let (result, unit) = add_timedelta_promoted(&a, TimeUnit::S, &b, TimeUnit::Ms).unwrap();
916 assert_eq!(unit, TimeUnit::Ms);
917 assert_eq!(result.iter().next().unwrap().0, 3_250);
918 }
919
920 #[test]
923 fn array_sub_datetime_via_operator() {
924 let a = Array::<DateTime64, Ix1>::from_vec(
926 Ix1::new([3]),
927 vec![DateTime64(100), DateTime64(200), DateTime64(50)],
928 )
929 .unwrap();
930 let b = Array::<DateTime64, Ix1>::from_vec(
931 Ix1::new([3]),
932 vec![DateTime64(40), DateTime64(150), DateTime64(50)],
933 )
934 .unwrap();
935 let result = (&a - &b).unwrap();
936 let v: Vec<i64> = result.iter().map(|x| x.0).collect();
937 assert_eq!(v, vec![60, 50, 0]);
938 }
939
940 #[test]
941 fn array_add_datetime_timedelta_via_operator() {
942 let a = Array::<DateTime64, Ix1>::from_vec(
943 Ix1::new([2]),
944 vec![DateTime64(1000), DateTime64(2000)],
945 )
946 .unwrap();
947 let b = Array::<Timedelta64, Ix1>::from_vec(
948 Ix1::new([2]),
949 vec![Timedelta64(50), Timedelta64(75)],
950 )
951 .unwrap();
952 let result = (&a + &b).unwrap();
953 let v: Vec<i64> = result.iter().map(|x| x.0).collect();
954 assert_eq!(v, vec![1050, 2075]);
955 }
956
957 #[test]
958 fn array_timedelta_arith_via_operators() {
959 let a = Array::<Timedelta64, Ix1>::from_vec(
960 Ix1::new([2]),
961 vec![Timedelta64(10), Timedelta64(20)],
962 )
963 .unwrap();
964 let b = Array::<Timedelta64, Ix1>::from_vec(
965 Ix1::new([2]),
966 vec![Timedelta64(3), Timedelta64(7)],
967 )
968 .unwrap();
969 let s = (&a + &b).unwrap();
970 let sv: Vec<i64> = s.iter().map(|x| x.0).collect();
971 assert_eq!(sv, vec![13, 27]);
972 let d = (&a - &b).unwrap();
973 let dv: Vec<i64> = d.iter().map(|x| x.0).collect();
974 assert_eq!(dv, vec![7, 13]);
975 }
976
977 #[test]
978 fn promoted_same_unit_passes_through() {
979 let a = Array::<DateTime64, Ix1>::from_vec(
980 Ix1::new([3]),
981 vec![DateTime64(100), DateTime64(200), DateTime64(50)],
982 )
983 .unwrap();
984 let b = Array::<DateTime64, Ix1>::from_vec(
985 Ix1::new([3]),
986 vec![DateTime64(40), DateTime64(150), DateTime64(50)],
987 )
988 .unwrap();
989 let basic = sub_datetime(&a, &b).unwrap();
990 let (promoted, unit) = sub_datetime_promoted(&a, TimeUnit::Ns, &b, TimeUnit::Ns).unwrap();
991 assert_eq!(unit, TimeUnit::Ns);
992 let bv: Vec<i64> = basic.iter().map(|x| x.0).collect();
993 let pv: Vec<i64> = promoted.iter().map(|x| x.0).collect();
994 assert_eq!(bv, pv);
995 }
996}
997
998#[cfg(test)]
999mod arith_tests {
1000 use super::*;
1005 use ferray_core::dimension::Ix1;
1006
1007 fn td1(vals: &[i64]) -> Array<Timedelta64, Ix1> {
1008 let data: Vec<Timedelta64> = vals.iter().map(|&v| Timedelta64(v)).collect();
1009 Array::<Timedelta64, Ix1>::from_vec(Ix1::new([vals.len()]), data).unwrap()
1010 }
1011
1012 fn first_td(a: Array<Timedelta64, Ix1>) -> i64 {
1013 a.iter().next().unwrap().0
1014 }
1015
1016 fn first_i64(a: Array<i64, Ix1>) -> i64 {
1017 *a.iter().next().unwrap()
1018 }
1019
1020 #[test]
1021 fn mul_timedelta_scalar_i64_exact() {
1022 let r = mul_timedelta_scalar_i64(&td1(&[6, 5, NAT]), 3).unwrap();
1024 let v: Vec<i64> = r.iter().map(|x| x.0).collect();
1025 assert_eq!(v, vec![18, 15, NAT]);
1026 }
1027
1028 #[test]
1029 fn mul_timedelta_scalar_f64_truncates_toward_zero() {
1030 assert_eq!(
1034 first_td(mul_timedelta_scalar_f64(&td1(&[5]), 1.5).unwrap()),
1035 7
1036 );
1037 assert_eq!(
1038 first_td(mul_timedelta_scalar_f64(&td1(&[5]), 2.5).unwrap()),
1039 12
1040 );
1041 assert_eq!(
1042 first_td(mul_timedelta_scalar_f64(&td1(&[1]), 0.5).unwrap()),
1043 0
1044 );
1045 assert_eq!(
1046 first_td(mul_timedelta_scalar_f64(&td1(&[1]), -2.9).unwrap()),
1047 -2
1048 );
1049 assert_eq!(
1050 first_td(mul_timedelta_scalar_f64(&td1(&[NAT]), 2.0).unwrap()),
1051 NAT
1052 );
1053 }
1054
1055 #[test]
1056 fn div_timedelta_scalar_i64_trunc_and_zero() {
1057 assert_eq!(
1060 first_td(div_timedelta_scalar_i64(&td1(&[6]), 2).unwrap()),
1061 3
1062 );
1063 assert_eq!(
1064 first_td(div_timedelta_scalar_i64(&td1(&[-7]), 2).unwrap()),
1065 -3
1066 );
1067 assert_eq!(
1068 first_td(div_timedelta_scalar_i64(&td1(&[6]), 0).unwrap()),
1069 NAT
1070 );
1071 assert_eq!(
1072 first_td(div_timedelta_scalar_i64(&td1(&[NAT]), 2).unwrap()),
1073 NAT
1074 );
1075 }
1076
1077 #[test]
1078 fn div_timedelta_scalar_f64_trunc_and_nonfinite() {
1079 assert_eq!(
1081 first_td(div_timedelta_scalar_f64(&td1(&[5]), 2.0).unwrap()),
1082 2
1083 );
1084 assert_eq!(
1085 first_td(div_timedelta_scalar_f64(&td1(&[5]), 2.5).unwrap()),
1086 2
1087 );
1088 assert_eq!(
1089 first_td(div_timedelta_scalar_f64(&td1(&[5]), 0.0).unwrap()),
1090 NAT
1091 );
1092 }
1093
1094 #[test]
1095 fn truediv_timedelta_ratio_and_nat() {
1096 let r = truediv_timedelta(&td1(&[6]), TimeUnit::D, &td1(&[2]), TimeUnit::D).unwrap();
1098 assert_eq!(*r.iter().next().unwrap(), 3.0);
1099 let rn = truediv_timedelta(&td1(&[NAT]), TimeUnit::D, &td1(&[2]), TimeUnit::D).unwrap();
1100 assert!(rn.iter().next().unwrap().is_nan());
1101 let rz = truediv_timedelta(&td1(&[5]), TimeUnit::D, &td1(&[0]), TimeUnit::D).unwrap();
1102 assert!(rz.iter().next().unwrap().is_infinite());
1103 }
1104
1105 #[test]
1106 fn truediv_timedelta_cross_unit() {
1107 let r = truediv_timedelta(&td1(&[1]), TimeUnit::D, &td1(&[12]), TimeUnit::H).unwrap();
1109 assert_eq!(*r.iter().next().unwrap(), 2.0);
1110 }
1111
1112 #[test]
1113 fn floordiv_timedelta_floor_and_zero() {
1114 assert_eq!(
1117 first_i64(
1118 floordiv_timedelta(&td1(&[7]), TimeUnit::D, &td1(&[2]), TimeUnit::D).unwrap()
1119 ),
1120 3
1121 );
1122 assert_eq!(
1123 first_i64(
1124 floordiv_timedelta(&td1(&[-7]), TimeUnit::D, &td1(&[2]), TimeUnit::D).unwrap()
1125 ),
1126 -4
1127 );
1128 assert_eq!(
1129 first_i64(
1130 floordiv_timedelta(&td1(&[5]), TimeUnit::D, &td1(&[0]), TimeUnit::D).unwrap()
1131 ),
1132 0
1133 );
1134 assert_eq!(
1135 first_i64(
1136 floordiv_timedelta(&td1(&[NAT]), TimeUnit::D, &td1(&[2]), TimeUnit::D).unwrap()
1137 ),
1138 0
1139 );
1140 }
1141
1142 #[test]
1143 fn floordiv_timedelta_cross_unit() {
1144 assert_eq!(
1146 first_i64(
1147 floordiv_timedelta(&td1(&[1]), TimeUnit::D, &td1(&[5]), TimeUnit::H).unwrap()
1148 ),
1149 4
1150 );
1151 }
1152
1153 #[test]
1154 fn mod_timedelta_python_floor_mod() {
1155 let (r, u) = mod_timedelta(&td1(&[7]), TimeUnit::D, &td1(&[4]), TimeUnit::D).unwrap();
1158 assert_eq!(u, TimeUnit::D);
1159 assert_eq!(first_td(r), 3);
1160 assert_eq!(
1161 first_td(
1162 mod_timedelta(&td1(&[-7]), TimeUnit::D, &td1(&[2]), TimeUnit::D)
1163 .unwrap()
1164 .0
1165 ),
1166 1
1167 );
1168 assert_eq!(
1169 first_td(
1170 mod_timedelta(&td1(&[7]), TimeUnit::D, &td1(&[-2]), TimeUnit::D)
1171 .unwrap()
1172 .0
1173 ),
1174 -1
1175 );
1176 assert_eq!(
1177 first_td(
1178 mod_timedelta(&td1(&[NAT]), TimeUnit::D, &td1(&[2]), TimeUnit::D)
1179 .unwrap()
1180 .0
1181 ),
1182 NAT
1183 );
1184 assert_eq!(
1185 first_td(
1186 mod_timedelta(&td1(&[5]), TimeUnit::D, &td1(&[0]), TimeUnit::D)
1187 .unwrap()
1188 .0
1189 ),
1190 NAT
1191 );
1192 }
1193
1194 #[test]
1195 fn mod_timedelta_cross_unit() {
1196 let (r, u) = mod_timedelta(&td1(&[1]), TimeUnit::D, &td1(&[5]), TimeUnit::H).unwrap();
1198 assert_eq!(u, TimeUnit::H);
1199 assert_eq!(first_td(r), 4);
1200 }
1201}