@@ -20,6 +20,7 @@ use chrono_tz::Tz;
2020use databend_common_column:: types:: timestamp_tz;
2121use databend_common_exception:: ErrorCode ;
2222use databend_common_io:: datetime:: check_input_year;
23+ use databend_common_io:: datetime:: check_timezone_offset;
2324use databend_common_timezone:: fast_utc_from_local;
2425use databend_common_timezone:: offset_seconds_at;
2526
@@ -128,14 +129,22 @@ impl ParsedDateTime {
128129 }
129130}
130131
131- fn try_parse_formats ( val : & str , tz : & Tz , formats : & [ & str ] ) -> Option < ( i64 , i32 ) > {
132+ // A matched format with an invalid explicit offset is an error, not a signal
133+ // to try a more permissive parser. UTC range clamping remains the caller's policy.
134+ #[ allow( clippy:: result_large_err) ]
135+ fn try_parse_formats (
136+ val : & str ,
137+ tz : & Tz ,
138+ formats : & [ & str ] ,
139+ ) -> Result < Option < ( i64 , i32 ) > , ErrorCode > {
132140 for format in formats {
133141 let Some ( parsed) = ParsedDateTime :: parse ( format, val) else {
134142 continue ;
135143 } ;
136144
137145 match parsed. offset_seconds {
138146 Some ( offset) => {
147+ check_timezone_offset ( offset) ?;
139148 let Some ( date) = parsed. naive_date ( ) else {
140149 continue ;
141150 } ;
@@ -148,18 +157,20 @@ fn try_parse_formats(val: &str, tz: &Tz, formats: &[&str]) -> Option<(i64, i32)>
148157 } ;
149158 let micros = local. and_utc ( ) . timestamp ( ) * MICROS_PER_SEC + parsed. micro as i64
150159 - offset as i64 * MICROS_PER_SEC ;
151- return Some ( ( micros, offset) ) ;
160+ return Ok ( Some ( ( micros, offset) ) ) ;
152161 }
153162 None => {
154163 let Some ( micros) = fast_timestamp_from_parsed ( & parsed, tz) else {
155164 continue ;
156165 } ;
157- let offset = offset_seconds_at ( tz, micros. div_euclid ( MICROS_PER_SEC ) ) ?;
158- return Some ( ( micros, offset) ) ;
166+ let Some ( offset) = offset_seconds_at ( tz, micros. div_euclid ( MICROS_PER_SEC ) ) else {
167+ continue ;
168+ } ;
169+ return Ok ( Some ( ( micros, offset) ) ) ;
159170 }
160171 }
161172 }
162- None
173+ Ok ( None )
163174}
164175
165176pub fn fast_timestamp_from_parsed ( parsed : & ParsedDateTime , tz : & Tz ) -> Option < i64 > {
@@ -175,10 +186,13 @@ pub fn fast_timestamp_from_parsed(parsed: &ParsedDateTime, tz: &Tz) -> Option<i6
175186 )
176187}
177188
178- pub fn auto_detect_timestamp ( val : & str , tz : & Tz ) -> Option < i64 > {
179- let ( mut micros, _) = try_parse_formats ( val, tz, AUTO_TS_FORMATS ) ?;
189+ #[ allow( clippy:: result_large_err) ]
190+ pub fn auto_detect_timestamp ( val : & str , tz : & Tz ) -> Result < Option < i64 > , ErrorCode > {
191+ let Some ( ( mut micros, _) ) = try_parse_formats ( val, tz, AUTO_TS_FORMATS ) ? else {
192+ return Ok ( None ) ;
193+ } ;
180194 clamp_timestamp ( & mut micros) ;
181- Some ( micros)
195+ Ok ( Some ( micros) )
182196}
183197
184198pub fn auto_detect_date ( val : & str ) -> Option < i32 > {
@@ -194,10 +208,13 @@ pub fn auto_detect_date(val: &str) -> Option<i32> {
194208 None
195209}
196210
197- pub fn auto_detect_timestamp_tz ( val : & str , tz : & Tz ) -> Option < timestamp_tz > {
198- let ( mut micros, offset) = try_parse_formats ( val, tz, AUTO_TS_FORMATS ) ?;
211+ #[ allow( clippy:: result_large_err) ]
212+ pub fn auto_detect_timestamp_tz ( val : & str , tz : & Tz ) -> Result < Option < timestamp_tz > , ErrorCode > {
213+ let Some ( ( mut micros, offset) ) = try_parse_formats ( val, tz, AUTO_TS_FORMATS ) ? else {
214+ return Ok ( None ) ;
215+ } ;
199216 clamp_timestamp ( & mut micros) ;
200- Some ( timestamp_tz:: new ( micros, offset) )
217+ Ok ( Some ( timestamp_tz:: new ( micros, offset) ) )
201218}
202219
203220/// Parse a date string with optional auto-detect fallback.
@@ -226,12 +243,13 @@ pub fn parse_date_with_auto(val: &str, tz: &Tz, enable_auto: bool) -> Result<i32
226243pub fn parse_timestamp_with_auto ( val : & str , tz : & Tz , enable_auto : bool ) -> Result < i64 , ErrorCode > {
227244 match string_to_timestamp ( val, tz) {
228245 Ok ( micros) => Ok ( micros) ,
246+ Err ( e) if e. code ( ) == ErrorCode :: INVALID_TIMEZONE => Err ( e) ,
229247 Err ( e) => {
230248 if enable_auto {
231249 if let Some ( micros) = parse_epoch_str ( val) {
232250 return Ok ( micros) ;
233251 }
234- if let Some ( micros) = auto_detect_timestamp ( val, tz) {
252+ if let Some ( micros) = auto_detect_timestamp ( val, tz) ? {
235253 return Ok ( micros) ;
236254 }
237255 }
@@ -250,14 +268,15 @@ pub fn parse_timestamp_tz_with_auto(
250268) -> Result < timestamp_tz , ErrorCode > {
251269 match string_to_timestamp_tz ( val. as_bytes ( ) , || tz) {
252270 Ok ( ts_tz) => Ok ( ts_tz) ,
271+ Err ( e) if e. code ( ) == ErrorCode :: INVALID_TIMEZONE => Err ( e) ,
253272 Err ( e) => {
254273 if enable_auto {
255274 if let Some ( micros) = parse_epoch_str ( val) {
256275 let offset = offset_seconds_at ( tz, micros. div_euclid ( MICROS_PER_SEC ) )
257276 . expect ( "validated Databend timestamp has a timezone offset" ) ;
258277 return Ok ( timestamp_tz:: new ( micros, offset) ) ;
259278 }
260- if let Some ( ts_tz) = auto_detect_timestamp_tz ( val, tz) {
279+ if let Some ( ts_tz) = auto_detect_timestamp_tz ( val, tz) ? {
261280 return Ok ( ts_tz) ;
262281 }
263282 }
0 commit comments