diff --git a/CHANGELOG.md b/CHANGELOG.md index b5cbe85b..e6a7b45c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,8 @@ between `chrono::DateTime` and `chrono::DateTime` and Ruby `Time` objects. - `typed_data::Writebarrier::writebarrier` and `writebarrier_unprotect`. +- Bug fixes for `IntoValue` for `chrono::DateTime` and + `chrono::DateTime` ### Changed - Conversions between Ruby's `Time` and Rust's `SystemTime` now preserve diff --git a/src/time.rs b/src/time.rs index de32e3e3..584ab5e0 100644 --- a/src/time.rs +++ b/src/time.rs @@ -261,9 +261,10 @@ impl IntoValue for chrono::DateTime { #[inline] fn into_value_with(self, ruby: &Ruby) -> Value { let delta = self.signed_duration_since(Self::UNIX_EPOCH); - ruby.time_nano_new(delta.num_seconds(), delta.subsec_nanos() as _) - .unwrap() - .as_value() + let val: Time = ruby + .time_nano_new(delta.num_seconds(), delta.subsec_nanos() as _) + .unwrap(); + val.funcall("utc", ()).unwrap() } } @@ -272,12 +273,15 @@ impl IntoValue for chrono::DateTime { impl IntoValue for chrono::DateTime { #[inline] fn into_value_with(self, ruby: &Ruby) -> Value { - use chrono::{DateTime, Utc}; - let epoch = DateTime::::UNIX_EPOCH.with_timezone(&self.timezone()); - let delta = self.signed_duration_since(epoch); - ruby.time_nano_new(delta.num_seconds(), delta.subsec_nanos() as _) + use chrono::{DateTime, FixedOffset, Utc}; + let delta = self.signed_duration_since(DateTime::::UNIX_EPOCH); + let val = ruby + .time_nano_new(delta.num_seconds(), delta.subsec_nanos() as _) + .unwrap() + .as_value(); + let offset: FixedOffset = self.timezone().into(); + val.funcall("getlocal", (offset.local_minus_utc(),)) .unwrap() - .as_value() } } @@ -335,15 +339,12 @@ impl TryConvert for chrono::DateTime { timespec = rb_time_timespec(val.as_rb_value()); Ruby::get_unchecked().qnil() })?; - if timespec.tv_sec >= 0 && timespec.tv_nsec >= 0 { - let mut duration = Duration::from_secs(timespec.tv_sec as _); - duration += Duration::from_nanos(timespec.tv_nsec as _); - Ok(Self::UNIX_EPOCH + duration) - } else { - Err(Error::new( + match chrono::Duration::new(timespec.tv_sec as _, timespec.tv_nsec as _) { + Some(duration) => Ok(Self::UNIX_EPOCH + duration), + None => Err(Error::new( Ruby::get_with(val).exception_arg_error(), - "time must not be negative", - )) + "invalid timespec", + )), } } } diff --git a/tests/time.rs b/tests/time.rs index 11897ece..46137a9f 100644 --- a/tests/time.rs +++ b/tests/time.rs @@ -2,6 +2,7 @@ #[cfg(feature = "chrono")] fn it_supports_chrono() { use chrono::{DateTime, Datelike, FixedOffset, Utc}; + use magnus::rb_assert; let ruby = unsafe { magnus::embed::init() }; let t = ruby.eval::>("Time.at(0, 10, :nsec)").unwrap(); @@ -14,6 +15,18 @@ fn it_supports_chrono() { .eval::>(r#"Time.new(1971, 1, 1, 2, 2, 2.0000001, "Z")"#) .unwrap(); assert_eq!(&dt.to_rfc3339(), "1971-01-01T02:02:02.000000099+00:00"); + rb_assert!(ruby, "dt.utc?", dt); + rb_assert!(ruby, "dt.utc_offset == 0", dt); + + let dt = ruby + .eval::>(r#"Time.new(1950, 1, 1)"#) + .unwrap(); + assert_eq!(&dt.to_rfc3339(), "1950-01-01T07:00:00+00:00"); + + let dt = ruby + .eval::>(r#"Time.new(1971, 1, 1, 2, 2, 2.0000001, "-07:00")"#) + .unwrap(); + assert_eq!(&dt.to_rfc3339(), "1971-01-01T09:02:02.000000099+00:00"); let dt = ruby .eval::>( @@ -21,4 +34,15 @@ fn it_supports_chrono() { ) .unwrap(); assert_eq!(&dt.to_rfc3339(), "2022-05-31T09:08:00.123456789-07:00"); + rb_assert!(ruby, "!dt.utc?", dt); + rb_assert!(ruby, "dt.utc_offset == -25200", dt); + + let dt = ruby + .eval::>( + r#"Time.new(2022, 5, 31, 9, 8, 123456789/1000000000r, "+05:30")"#, + ) + .unwrap(); + assert_eq!(&dt.to_rfc3339(), "2022-05-31T09:08:00.123456789+05:30"); + rb_assert!(ruby, "!dt.utc?", dt); + rb_assert!(ruby, "dt.utc_offset == 19800", dt); }