@@ -81,20 +81,6 @@ impl IntervalUnit {
81
81
self . usecs . rem_euclid ( USECS_PER_DAY ) as u64
82
82
}
83
83
84
- #[ deprecated]
85
- fn from_total_usecs ( usecs : i64 ) -> Self {
86
- let mut remaining_usecs = usecs;
87
- let months = remaining_usecs / USECS_PER_MONTH ;
88
- remaining_usecs -= months * USECS_PER_MONTH ;
89
- let days = remaining_usecs / USECS_PER_DAY ;
90
- remaining_usecs -= days * USECS_PER_DAY ;
91
- IntervalUnit {
92
- months : ( months as i32 ) ,
93
- days : ( days as i32 ) ,
94
- usecs : remaining_usecs,
95
- }
96
- }
97
-
98
84
pub fn to_protobuf < T : Write > ( self , output : & mut T ) -> ArrayResult < usize > {
99
85
output. write_i32 :: < BigEndian > ( self . months ) ?;
100
86
output. write_i32 :: < BigEndian > ( self . days ) ?;
@@ -119,6 +105,63 @@ impl IntervalUnit {
119
105
} )
120
106
}
121
107
108
+ /// Internal utility used by [`Self::mul_float`] and [`Self::div_float`] to adjust fractional
109
+ /// units. Not intended as general constructor.
110
+ fn from_floats ( months : f64 , days : f64 , usecs : f64 ) -> Option < Self > {
111
+ // TSROUND in include/datatype/timestamp.h
112
+ // round eagerly at usecs precision because floats are imprecise
113
+ // should round to even #5576
114
+ let months_round_usecs =
115
+ |months : f64 | ( months * ( USECS_PER_MONTH as f64 ) ) . round ( ) / ( USECS_PER_MONTH as f64 ) ;
116
+
117
+ let days_round_usecs =
118
+ |days : f64 | ( days * ( USECS_PER_DAY as f64 ) ) . round ( ) / ( USECS_PER_DAY as f64 ) ;
119
+
120
+ let trunc_fract = |num : f64 | ( num. trunc ( ) , num. fract ( ) ) ;
121
+
122
+ // Handle months
123
+ let ( months, months_fract) = trunc_fract ( months_round_usecs ( months) ) ;
124
+ if months. is_nan ( ) || months < i32:: MIN . into ( ) || months > i32:: MAX . into ( ) {
125
+ return None ;
126
+ }
127
+ let months = months as i32 ;
128
+ let ( leftover_days, leftover_days_fract) =
129
+ trunc_fract ( days_round_usecs ( months_fract * 30. ) ) ;
130
+
131
+ // Handle days
132
+ let ( days, days_fract) = trunc_fract ( days_round_usecs ( days) ) ;
133
+ if days. is_nan ( ) || days < i32:: MIN . into ( ) || days > i32:: MAX . into ( ) {
134
+ return None ;
135
+ }
136
+ // Note that PostgreSQL split the integer part and fractional part individually before
137
+ // adding `leftover_days`. This makes a difference for mixed sign interval.
138
+ // For example in `interval '3 mons -3 days' / 2`
139
+ // * `leftover_days` is `15`
140
+ // * `days` from input is `-1.5`
141
+ // If we add first, we get `13.5` which is `13 days 12:00:00`;
142
+ // If we split first, we get `14` and `-0.5`, which ends up as `14 days -12:00:00`.
143
+ let ( days_fract_whole, days_fract) =
144
+ trunc_fract ( days_round_usecs ( days_fract + leftover_days_fract) ) ;
145
+ let days = ( days as i32 )
146
+ . checked_add ( leftover_days as i32 ) ?
147
+ . checked_add ( days_fract_whole as i32 ) ?;
148
+ let leftover_usecs = days_fract * ( USECS_PER_DAY as f64 ) ;
149
+
150
+ // Handle usecs
151
+ let result_usecs = usecs + leftover_usecs;
152
+ let usecs = result_usecs. round ( ) ;
153
+ if usecs. is_nan ( ) || usecs < ( i64:: MIN as f64 ) || usecs > ( i64:: MAX as f64 ) {
154
+ return None ;
155
+ }
156
+ let usecs = usecs as i64 ;
157
+
158
+ Some ( Self {
159
+ months,
160
+ days,
161
+ usecs,
162
+ } )
163
+ }
164
+
122
165
/// Divides [`IntervalUnit`] by an integer/float with zero check.
123
166
pub fn div_float < I > ( & self , rhs : I ) -> Option < Self >
124
167
where
@@ -131,17 +174,11 @@ impl IntervalUnit {
131
174
return None ;
132
175
}
133
176
134
- #[ expect( deprecated) ]
135
- let usecs = self . as_usecs_i64 ( ) ;
136
- #[ expect( deprecated) ]
137
- Some ( IntervalUnit :: from_total_usecs (
138
- ( usecs as f64 / rhs) . round ( ) as i64
139
- ) )
140
- }
141
-
142
- #[ deprecated]
143
- fn as_usecs_i64 ( & self ) -> i64 {
144
- self . months as i64 * USECS_PER_MONTH + self . days as i64 * USECS_PER_DAY + self . usecs
177
+ Self :: from_floats (
178
+ self . months as f64 / rhs,
179
+ self . days as f64 / rhs,
180
+ self . usecs as f64 / rhs,
181
+ )
145
182
}
146
183
147
184
/// times [`IntervalUnit`] with an integer/float.
@@ -152,12 +189,11 @@ impl IntervalUnit {
152
189
let rhs = rhs. try_into ( ) . ok ( ) ?;
153
190
let rhs = rhs. 0 ;
154
191
155
- #[ expect( deprecated) ]
156
- let usecs = self . as_usecs_i64 ( ) ;
157
- #[ expect( deprecated) ]
158
- Some ( IntervalUnit :: from_total_usecs (
159
- ( usecs as f64 * rhs) . round ( ) as i64
160
- ) )
192
+ Self :: from_floats (
193
+ self . months as f64 * rhs,
194
+ self . days as f64 * rhs,
195
+ self . usecs as f64 * rhs,
196
+ )
161
197
}
162
198
163
199
/// Performs an exact division, returns [`None`] if for any unit, lhs % rhs != 0.
0 commit comments