1212// See the License for the specific language governing permissions and
1313// limitations under the License.
1414
15- use chrono:: { DateTime , Datelike , NaiveDate , NaiveDateTime , TimeZone } ;
15+ use chrono:: {
16+ DateTime , Datelike , FixedOffset , LocalResult , NaiveDate , NaiveDateTime , TimeZone , Utc ,
17+ } ;
1618use chrono_tz:: Tz ;
1719use std:: collections:: HashMap ;
1820use std:: hash:: Hash ;
1921
2022use crate :: error:: { ConvertError , Error , Result } ;
2123
2224use super :: { NumberValue , Value , DAYS_FROM_CE } ;
25+ use jiff:: { tz:: TimeZone as JiffTimeZone , Timestamp , Zoned } ;
2326
2427impl TryFrom < Value > for bool {
2528 type Error = Error ;
@@ -70,11 +73,62 @@ impl_try_from_number_value!(i64);
7073impl_try_from_number_value ! ( f32 ) ;
7174impl_try_from_number_value ! ( f64 ) ;
7275
76+ fn unix_micros_from_zoned ( zdt : & Zoned ) -> i64 {
77+ zdt. timestamp ( ) . as_microsecond ( )
78+ }
79+
80+ fn naive_datetime_from_micros ( micros : i64 ) -> Result < NaiveDateTime > {
81+ DateTime :: < Utc > :: from_timestamp_micros ( micros)
82+ . map ( |dt| dt. naive_utc ( ) )
83+ . ok_or_else ( || Error :: Parsing ( format ! ( "invalid unix timestamp {micros}" ) ) )
84+ }
85+
86+ pub fn zoned_to_chrono_datetime ( zdt : & Zoned ) -> Result < DateTime < Tz > > {
87+ let tz_name = zdt. time_zone ( ) . iana_name ( ) . ok_or_else ( || {
88+ ConvertError :: new (
89+ "DateTime" ,
90+ "timestamp does not contain an IANA time zone" . to_string ( ) ,
91+ )
92+ } ) ?;
93+ let tz: Tz = tz_name. parse ( ) . map_err ( |_| {
94+ ConvertError :: new (
95+ "DateTime" ,
96+ format ! ( "invalid time zone identifier {tz_name}" ) ,
97+ )
98+ } ) ?;
99+ let micros = unix_micros_from_zoned ( zdt) ;
100+ match tz. timestamp_micros ( micros) {
101+ LocalResult :: Single ( dt) => Ok ( dt) ,
102+ LocalResult :: Ambiguous ( dt, _) => Ok ( dt) ,
103+ LocalResult :: None => Err ( Error :: Parsing ( format ! (
104+ "time {micros} not exists in timezone {tz_name}"
105+ ) ) ) ,
106+ }
107+ }
108+
109+ pub fn zoned_to_chrono_fixed_offset ( zdt : & Zoned ) -> Result < DateTime < FixedOffset > > {
110+ let offset_seconds = zdt. offset ( ) . seconds ( ) ;
111+ let offset = FixedOffset :: east_opt ( offset_seconds)
112+ . ok_or_else ( || Error :: Parsing ( format ! ( "invalid offset {offset_seconds}" ) ) ) ?;
113+ let micros = unix_micros_from_zoned ( zdt) ;
114+ let naive = naive_datetime_from_micros ( micros) ?;
115+ Ok ( DateTime :: < FixedOffset > :: from_naive_utc_and_offset (
116+ naive, offset,
117+ ) )
118+ }
119+
120+ fn zoned_from_naive_datetime ( naive_dt : & NaiveDateTime ) -> Zoned {
121+ let micros = naive_dt. and_utc ( ) . timestamp_micros ( ) ;
122+ let timestamp = Timestamp :: from_microsecond ( micros)
123+ . expect ( "NaiveDateTime out of range for Timestamp conversion" ) ;
124+ timestamp. to_zoned ( JiffTimeZone :: UTC )
125+ }
126+
73127impl TryFrom < Value > for NaiveDateTime {
74128 type Error = Error ;
75129 fn try_from ( val : Value ) -> Result < Self > {
76130 match val {
77- Value :: Timestamp ( dt) => Ok ( dt . naive_utc ( ) ) ,
131+ Value :: Timestamp ( dt) => naive_datetime_from_micros ( unix_micros_from_zoned ( & dt ) ) ,
78132 _ => Err ( ConvertError :: new ( "NaiveDateTime" , format ! ( "{val}" ) ) . into ( ) ) ,
79133 }
80134 }
@@ -84,7 +138,7 @@ impl TryFrom<Value> for DateTime<Tz> {
84138 type Error = Error ;
85139 fn try_from ( val : Value ) -> Result < Self > {
86140 match val {
87- Value :: Timestamp ( dt) => Ok ( dt) ,
141+ Value :: Timestamp ( dt) => zoned_to_chrono_datetime ( & dt) ,
88142 _ => Err ( ConvertError :: new ( "DateTime" , format ! ( "{val}" ) ) . into ( ) ) ,
89143 }
90144 }
@@ -426,15 +480,13 @@ impl From<&NaiveDate> for Value {
426480
427481impl From < NaiveDateTime > for Value {
428482 fn from ( naive_dt : NaiveDateTime ) -> Self {
429- let dt = Tz :: UTC . from_local_datetime ( & naive_dt) . unwrap ( ) ;
430- Value :: Timestamp ( dt)
483+ Value :: Timestamp ( zoned_from_naive_datetime ( & naive_dt) )
431484 }
432485}
433486
434487impl From < & NaiveDateTime > for Value {
435488 fn from ( naive_dt : & NaiveDateTime ) -> Self {
436- let dt = Tz :: UTC . from_local_datetime ( naive_dt) . unwrap ( ) ;
437- Value :: Timestamp ( dt)
489+ Value :: Timestamp ( zoned_from_naive_datetime ( naive_dt) )
438490 }
439491}
440492
0 commit comments