@@ -31,31 +31,56 @@ use crate::scalar::ScalarValue;
3131pub struct Timestamp ;
3232
3333impl Timestamp {
34- /// Creates a new Timestamp extension =dtype with the given time unit and nullability.
34+ /// Creates a timestamp dtype with the given time unit and nullability.
35+ ///
36+ /// # Panics
37+ ///
38+ /// Panics if `time_unit` is [`TimeUnit::Days`].
3539 pub fn new ( time_unit : TimeUnit , nullability : Nullability ) -> ExtDType < Self > {
3640 Self :: new_with_tz ( time_unit, None , nullability)
3741 }
3842
39- /// Creates a new Timestamp extension dtype with the given time unit, timezone, and nullability.
40- pub fn new_with_tz (
43+ /// Creates a timestamp dtype with the given time unit, time zone, and nullability.
44+ ///
45+ /// # Errors
46+ ///
47+ /// Returns an error for [`TimeUnit::Days`] or an unresolved time zone. IANA names and fixed
48+ /// UTC offsets are accepted.
49+ pub fn try_new_with_tz (
4150 time_unit : TimeUnit ,
4251 timezone : Option < Arc < str > > ,
4352 nullability : Nullability ,
44- ) -> ExtDType < Self > {
53+ ) -> VortexResult < ExtDType < Self > > {
4554 ExtDType :: try_new (
4655 TimestampOptions {
4756 unit : time_unit,
4857 tz : timezone,
4958 } ,
5059 DType :: Primitive ( PType :: I64 , nullability) ,
5160 )
52- . vortex_expect ( "failed to create timestamp dtype" )
5361 }
5462
55- /// Creates a new `Timestamp` extension dtype with the given options and nullability.
63+ /// Creates a timestamp dtype with the given time unit, time zone, and nullability.
64+ ///
65+ /// # Panics
66+ ///
67+ /// Panics if [`Self::try_new_with_tz`] rejects the unit or time zone.
68+ pub fn new_with_tz (
69+ time_unit : TimeUnit ,
70+ timezone : Option < Arc < str > > ,
71+ nullability : Nullability ,
72+ ) -> ExtDType < Self > {
73+ Self :: try_new_with_tz ( time_unit, timezone, nullability)
74+ . vortex_expect ( "timestamp unit and time zone must be valid" )
75+ }
76+
77+ /// Creates a timestamp dtype with the given options and nullability.
78+ ///
79+ /// # Panics
80+ ///
81+ /// Panics if [`Self::try_new_with_tz`] rejects the unit or time zone.
5682 pub fn new_with_options ( options : TimestampOptions , nullability : Nullability ) -> ExtDType < Self > {
57- ExtDType :: try_new ( options, DType :: Primitive ( PType :: I64 , nullability) )
58- . vortex_expect ( "failed to create timestamp dtype" )
83+ Self :: new_with_tz ( options. unit , options. tz , nullability)
5984 }
6085}
6186
@@ -200,6 +225,13 @@ impl ExtVTable for Timestamp {
200225 matches!( ext_dtype. storage_dtype( ) , DType :: Primitive ( PType :: I64 , _) ) ,
201226 "Timestamp storage dtype must be i64"
202227 ) ;
228+ vortex_ensure ! (
229+ ext_dtype. metadata( ) . unit != TimeUnit :: Days ,
230+ "Timestamp time unit must be finer than days, got {:?}" ,
231+ ext_dtype. metadata( ) . unit
232+ ) ;
233+ resolve_time_zone ( ext_dtype. metadata ( ) . tz . as_deref ( ) ) ?;
234+
203235 Ok ( ( ) )
204236 }
205237
@@ -213,19 +245,19 @@ impl ExtVTable for Timestamp {
213245
214246 let ( span, value) = match metadata. unit {
215247 TimeUnit :: Nanoseconds => (
216- Span :: new ( ) . nanoseconds ( ts_value) ,
248+ Span :: new ( ) . try_nanoseconds ( ts_value) ? ,
217249 TimestampValue :: Nanoseconds ( ts_value, tz) ,
218250 ) ,
219251 TimeUnit :: Microseconds => (
220- Span :: new ( ) . microseconds ( ts_value) ,
252+ Span :: new ( ) . try_microseconds ( ts_value) ? ,
221253 TimestampValue :: Microseconds ( ts_value, tz) ,
222254 ) ,
223255 TimeUnit :: Milliseconds => (
224- Span :: new ( ) . milliseconds ( ts_value) ,
256+ Span :: new ( ) . try_milliseconds ( ts_value) ? ,
225257 TimestampValue :: Milliseconds ( ts_value, tz) ,
226258 ) ,
227259 TimeUnit :: Seconds => (
228- Span :: new ( ) . seconds ( ts_value) ,
260+ Span :: new ( ) . try_seconds ( ts_value) ? ,
229261 TimestampValue :: Seconds ( ts_value, tz) ,
230262 ) ,
231263 TimeUnit :: Days => {
@@ -238,11 +270,6 @@ impl ExtVTable for Timestamp {
238270 . checked_add ( span)
239271 . map_err ( |e| vortex_err ! ( "Invalid timestamp scalar: {}" , e) ) ?;
240272
241- if let Some ( tz) = tz {
242- resolve_time_zone ( Some ( tz) )
243- . map_err ( |e| e. with_context ( "Invalid timezone for timestamp scalar" ) ) ?;
244- }
245-
246273 Ok ( value)
247274 }
248275}
@@ -289,16 +316,29 @@ mod tests {
289316 #[ cfg_attr( miri, ignore) ]
290317 #[ test]
291318 fn reject_timestamp_with_invalid_timezone ( ) {
292- let dtype = DType :: Extension (
293- Timestamp :: new_with_tz (
319+ assert ! (
320+ Timestamp :: try_new_with_tz (
294321 TimeUnit :: Seconds ,
295322 Some ( Arc :: from( "Not/A/Timezone" ) ) ,
296323 Nullable ,
297324 )
298- . erased ( ) ,
325+ . is_err ( )
299326 ) ;
300- let result = Scalar :: try_new ( dtype, Some ( ScalarValue :: Primitive ( PValue :: I64 ( 0 ) ) ) ) ;
301- assert ! ( result. is_err( ) ) ;
327+ }
328+
329+ #[ test]
330+ fn test_reject_timestamp_days ( ) {
331+ assert ! ( Timestamp :: try_new_with_tz( TimeUnit :: Days , None , Nullable ) . is_err( ) ) ;
332+ }
333+
334+ #[ rstest]
335+ #[ case:: seconds( TimeUnit :: Seconds , i64 :: MAX ) ]
336+ #[ case:: milliseconds( TimeUnit :: Milliseconds , i64 :: MAX ) ]
337+ #[ case:: microseconds( TimeUnit :: Microseconds , i64 :: MAX ) ]
338+ #[ case:: nanoseconds( TimeUnit :: Nanoseconds , i64 :: MIN ) ]
339+ fn test_reject_timestamp_span_out_of_range ( #[ case] unit : TimeUnit , #[ case] value : i64 ) {
340+ let dtype = DType :: Extension ( Timestamp :: new ( unit, Nullable ) . erased ( ) ) ;
341+ assert ! ( Scalar :: try_new( dtype, Some ( ScalarValue :: from( value) ) ) . is_err( ) ) ;
302342 }
303343
304344 #[ cfg_attr( miri, ignore) ]
0 commit comments