(
stride: Interval,
source: CheckedTimestamp<T>,
origin: CheckedTimestamp<T>,
)
| 2016 | } |
| 2017 | |
| 2018 | pub fn date_bin<T>( |
| 2019 | stride: Interval, |
| 2020 | source: CheckedTimestamp<T>, |
| 2021 | origin: CheckedTimestamp<T>, |
| 2022 | ) -> Result<CheckedTimestamp<T>, EvalError> |
| 2023 | where |
| 2024 | T: TimestampLike, |
| 2025 | { |
| 2026 | if stride.months != 0 { |
| 2027 | return Err(EvalError::DateBinOutOfRange( |
| 2028 | "timestamps cannot be binned into intervals containing months or years".into(), |
| 2029 | )); |
| 2030 | } |
| 2031 | |
| 2032 | let stride_ns = match stride.duration_as_chrono().num_nanoseconds() { |
| 2033 | Some(ns) if ns <= 0 => Err(EvalError::DateBinOutOfRange( |
| 2034 | "stride must be greater than zero".into(), |
| 2035 | )), |
| 2036 | Some(ns) => Ok(ns), |
| 2037 | None => Err(EvalError::DateBinOutOfRange( |
| 2038 | format!("stride cannot exceed {}/{} nanoseconds", i64::MAX, i64::MIN,).into(), |
| 2039 | )), |
| 2040 | }?; |
| 2041 | |
| 2042 | // Make sure the returned timestamp is at the start of the bin, even if the |
| 2043 | // origin is in the future. We do this here because `T` is not `Copy` and |
| 2044 | // gets moved by its subtraction operation. |
| 2045 | let sub_stride = origin > source; |
| 2046 | |
| 2047 | let tm_diff = (source - origin.clone()).num_nanoseconds().ok_or_else(|| { |
| 2048 | EvalError::DateBinOutOfRange( |
| 2049 | "source and origin must not differ more than 2^63 nanoseconds".into(), |
| 2050 | ) |
| 2051 | })?; |
| 2052 | |
| 2053 | let remainder = tm_diff % stride_ns; |
| 2054 | let mut tm_delta = tm_diff - remainder; |
| 2055 | |
| 2056 | if sub_stride && remainder != 0 { |
| 2057 | tm_delta = tm_delta.checked_sub(stride_ns).ok_or_else(|| { |
| 2058 | EvalError::DateBinOutOfRange( |
| 2059 | "source and origin must not differ more than 2^63 nanoseconds".into(), |
| 2060 | ) |
| 2061 | })?; |
| 2062 | } |
| 2063 | |
| 2064 | let res = origin |
| 2065 | .checked_add_signed(Duration::nanoseconds(tm_delta)) |
| 2066 | .ok_or(EvalError::TimestampOutOfRange)?; |
| 2067 | Ok(CheckedTimestamp::from_timestamplike(res)?) |
| 2068 | } |
| 2069 | |
| 2070 | // Non-monotone in `stride`: the result is `origin + floor((source - origin) / |
| 2071 | // stride) * stride`. For a fixed source like `2024-01-01 12:00:00`, a 1-day |
no test coverage detected