Skip to content

Commit

Permalink
Fixes for IntoValue for chrono::DateTime<Utc> and chrono::DateTime<Fi…
Browse files Browse the repository at this point in the history
…xedOffset>
  • Loading branch information
emwalker committed Jul 28, 2024
1 parent ab9b2a4 commit 3ad1e9d
Show file tree
Hide file tree
Showing 2 changed files with 77 additions and 22 deletions.
46 changes: 27 additions & 19 deletions src/time.rs
Original file line number Diff line number Diff line change
Expand Up @@ -346,7 +346,11 @@ impl IntoValue for chrono::DateTime<chrono::Utc> {
#[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 _)
let ts = Timespec {
tv_sec: delta.num_seconds(),
tv_nsec: delta.subsec_nanos() as _,
};
ruby.time_timespec_new(ts, Offset::utc())
.unwrap()
.as_value()
}
Expand All @@ -357,12 +361,15 @@ impl IntoValue for chrono::DateTime<chrono::Utc> {
impl IntoValue for chrono::DateTime<chrono::FixedOffset> {
#[inline]
fn into_value_with(self, ruby: &Ruby) -> Value {
use chrono::{DateTime, Utc};
let epoch = DateTime::<Utc>::UNIX_EPOCH.with_timezone(&self.timezone());
let delta = self.signed_duration_since(epoch);
ruby.time_nano_new(delta.num_seconds(), delta.subsec_nanos() as _)
.unwrap()
.as_value()
use chrono::{DateTime, FixedOffset, Utc};
let delta = self.signed_duration_since(DateTime::<Utc>::UNIX_EPOCH);
let ts = Timespec {
tv_sec: delta.num_seconds(),
tv_nsec: delta.subsec_nanos() as _,
};
let offset: FixedOffset = self.timezone().into();
let offset = Offset::from_secs(offset.local_minus_utc()).unwrap();
ruby.time_timespec_new(ts, offset).unwrap().as_value()
}
}

Expand Down Expand Up @@ -395,14 +402,18 @@ impl TryConvert for SystemTime {
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 _);
if timespec.tv_nsec >= 0 {
let mut duration = Duration::from_secs(timespec.tv_sec.abs() as _);
duration += Duration::from_nanos(timespec.tv_nsec as _);
Ok(Self::UNIX_EPOCH + duration)
if timespec.tv_sec >= 0 {
Ok(Self::UNIX_EPOCH + duration)
} else {
Ok(Self::UNIX_EPOCH - duration)
}
} else {
Err(Error::new(
Ruby::get_with(val).exception_arg_error(),
"time must not be negative",
"time nanos must not be negative",
))
}
}
Expand All @@ -420,15 +431,12 @@ impl TryConvert for chrono::DateTime<chrono::Utc> {
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",
))
"time out of range",
)),
}
}
}
Expand Down
53 changes: 50 additions & 3 deletions tests/time.rs
Original file line number Diff line number Diff line change
@@ -1,8 +1,30 @@
use magnus::rb_assert;
use magnus::{Error, Ruby};
use std::time::SystemTime;

#[test]
#[cfg(feature = "chrono")]
fn it_supports_chrono() {
fn test_all() {
magnus::Ruby::init(|ruby| {
test_supports_system_time(ruby)?;
#[cfg(feature = "chrono")]
test_supports_chrono(ruby)?;
Ok(())
})
.unwrap();
}

fn test_supports_system_time(ruby: &Ruby) -> Result<(), Error> {
let t = ruby.eval::<SystemTime>("Time.new(1971)").unwrap();
rb_assert!(ruby, "t.year == 1971", t);

let t = ruby.eval::<SystemTime>("Time.new(1960)").unwrap();
rb_assert!(ruby, "t.year == 1960", t);

Ok(())
}

fn test_supports_chrono(ruby: &Ruby) -> Result<(), Error> {
use chrono::{DateTime, Datelike, FixedOffset, Utc};
let ruby = unsafe { magnus::embed::init() };

let t = ruby.eval::<DateTime<Utc>>("Time.at(0, 10, :nsec)").unwrap();
assert_eq!(t.year(), 1970);
Expand All @@ -14,11 +36,36 @@ fn it_supports_chrono() {
.eval::<DateTime<Utc>>(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::<DateTime<Utc>>(r#"Time.new(1950, 1, 1, 0, 0, 0, "Z")"#)
.unwrap();
assert_eq!(&dt.to_rfc3339(), "1950-01-01T00:00:00+00:00");

let dt = ruby
.eval::<DateTime<Utc>>(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::<DateTime<FixedOffset>>(
r#"Time.new(2022, 5, 31, 9, 8, 123456789/1000000000r, "-07:00")"#,
)
.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::<DateTime<FixedOffset>>(
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);

Ok(())
}

0 comments on commit 3ad1e9d

Please sign in to comment.