Skip to content

Commit

Permalink
Merge pull request #2413 from artichoke/b-n/month-string-values
Browse files Browse the repository at this point in the history
Time Args support for month string values
  • Loading branch information
b-n committed Feb 19, 2023
2 parents 39f08b8 + 4196689 commit b3012e5
Show file tree
Hide file tree
Showing 2 changed files with 169 additions and 9 deletions.
2 changes: 2 additions & 0 deletions artichoke-backend/src/extn/core/kernel/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@ pub(in crate::extn) mod mruby;
pub mod require;
pub(super) mod trampoline;

pub use trampoline::integer;

#[derive(Debug, Clone, Copy)]
pub struct Kernel;

Expand Down
176 changes: 167 additions & 9 deletions artichoke-backend/src/extn/core/time/args.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
use crate::convert::to_int;
use crate::convert::{to_int, to_str};
use crate::extn::core::kernel::integer;
use crate::extn::prelude::*;

#[derive(Debug, Copy, Clone)]
Expand Down Expand Up @@ -57,21 +58,71 @@ impl TryConvertMut<&mut [Value], Args> for Artichoke {
for (i, &arg) in args.iter().enumerate() {
match i {
0 => {
let arg = to_int(self, arg)?;
let arg: i64 = arg.try_convert_into(self)?;
let arg: i64 = to_int(self, arg).and_then(|arg| arg.try_convert_into(self))?;

result.year = i32::try_from(arg).map_err(|_| ArgumentError::with_message("year out of range"))?;
}
1 => {
// TODO: This should support 3 letter month names
// as per the docs. https://ruby-doc.org/3.1.2/Time.html#method-c-new
// Short circuit month checking to avoid `to_str` checking.
1 if Ruby::Fixnum == arg.ruby_type() => {
let arg = to_int(self, arg)?;
let arg: i64 = arg.try_convert_into(self)?;

result.month = match u8::try_from(arg) {
Ok(month @ 1..=12) => Ok(month),
_ => Err(ArgumentError::with_message("mon out of range")),
}?;
Ok(month @ 1..=12) => month,
_ => return Err(ArgumentError::with_message("mon out of range").into()),
};
}
1 => {
// ```irb
// 3.1.2 => Time.utc(2022, 2).month
// => 2
// 3.1.2 => class I; def to_int; 2; end; end
// => :to_int
// 3.1.2 => Time.utc(2022, I.new).month
// => 2
// 3.1.2 > Time.utc(2022, "feb").month
// => 2
// 3.1.2 > class A; def to_str; "feb"; end; end
// => :to_str
// 3.1.2 > Time.utc(2022, A.new).month
// => 2
// 3.1.2 > class I; def to_str; "2"; end; end
// => :to_str
// 3.1.2 > Time.utc(2022, I.new).month
// => 2
// ```
let month: i64 = if let Ok(arg) = to_str(self, arg) {
let mut month_str: Vec<u8> = arg.try_convert_into_mut(self)?;
month_str.make_ascii_lowercase();
match month_str.as_slice() {
b"jan" => 1,
b"feb" => 2,
b"mar" => 3,
b"apr" => 4,
b"may" => 5,
b"jun" => 6,
b"jul" => 7,
b"aug" => 8,
b"sep" => 9,
b"oct" => 10,
b"nov" => 11,
b"dec" => 12,
_ => {
// Delegate to `Kernel#Integer` as last resort
// to handle Integer strings.
let arg = integer(self, arg, None)?;
arg.try_convert_into(self)?
}
}
} else {
let arg = to_int(self, arg)?;
arg.try_convert_into(self)?
};

result.month = match u8::try_from(month) {
Ok(month @ 1..=12) => month,
_ => return Err(ArgumentError::with_message("mon out of range").into()),
};
}
2 => {
let arg = to_int(self, arg)?;
Expand Down Expand Up @@ -300,6 +351,113 @@ mod tests {
assert_eq!(0, result.nanoseconds);
}

#[test]
fn month_supports_string_values() {
let mut interp = interpreter();

let table = [
(b"[2022, 'jan']", 1),
(b"[2022, 'feb']", 2),
(b"[2022, 'mar']", 3),
(b"[2022, 'apr']", 4),
(b"[2022, 'may']", 5),
(b"[2022, 'jun']", 6),
(b"[2022, 'jul']", 7),
(b"[2022, 'aug']", 8),
(b"[2022, 'sep']", 9),
(b"[2022, 'oct']", 10),
(b"[2022, 'nov']", 11),
(b"[2022, 'dec']", 12),
];

for (input, expected_month) in table {
let args = interp.eval(input).unwrap();
let mut ary_args: Vec<Value> = interp.try_convert_mut(args).unwrap();
let result: Args = interp.try_convert_mut(ary_args.as_mut_slice()).unwrap();

assert_eq!(expected_month, result.month);
}
}

#[test]
fn month_strings_are_case_insensitive() {
let mut interp = interpreter();

let table = [
(b"[2022, 'Feb']", 2),
(b"[2022, 'fEb']", 2),
(b"[2022, 'feB']", 2),
(b"[2022, 'FEb']", 2),
(b"[2022, 'FeB']", 2),
(b"[2022, 'fEB']", 2),
(b"[2022, 'FEB']", 2),
];

for (input, expected_month) in table {
let args = interp.eval(input).unwrap();
let mut ary_args: Vec<Value> = interp.try_convert_mut(args).unwrap();
let result: Args = interp.try_convert_mut(ary_args.as_mut_slice()).unwrap();

assert_eq!(expected_month, result.month);
assert_eq!(2022, result.year);
}
}

#[test]
fn month_supports_string_like_values() {
let mut interp = interpreter();

let args = interp
.eval(b"class A; def to_str; 'feb'; end; end; [2022, A.new]")
.unwrap();
let mut ary_args: Vec<Value> = interp.try_convert_mut(args).unwrap();
let result: Args = interp.try_convert_mut(ary_args.as_mut_slice()).unwrap();

assert_eq!(2, result.month);
}

#[test]
fn month_supports_int_like_values() {
let mut interp = interpreter();

let args = interp.eval(b"class A; def to_int; 2; end; end; [2022, A.new]").unwrap();
let mut ary_args: Vec<Value> = interp.try_convert_mut(args).unwrap();
let result: Args = interp.try_convert_mut(ary_args.as_mut_slice()).unwrap();

assert_eq!(2, result.month);
}

#[test]
fn month_string_can_be_integer_strings() {
let mut interp = interpreter();

let args = interp
.eval(b"class A; def to_str; '2'; end; end; [2022, A.new]")
.unwrap();
let mut ary_args: Vec<Value> = interp.try_convert_mut(args).unwrap();
let result: Args = interp.try_convert_mut(ary_args.as_mut_slice()).unwrap();

assert_eq!(2, result.month);
}

#[test]
fn invalid_month_string_responds_with_int_conversion_error() {
let mut interp = interpreter();

let args = interp
.eval(b"class A; def to_str; 'aaa'; end; end; [2022, A.new]")
.unwrap();
let mut ary_args: Vec<Value> = interp.try_convert_mut(args).unwrap();
let result: Result<Args, Error> = interp.try_convert_mut(ary_args.as_mut_slice());
let error = result.unwrap_err();

assert_eq!(
error.message().as_bstr(),
br#"invalid value for Integer(): "aaa""#.as_bstr()
);
assert_eq!(error.name(), "ArgumentError");
}

#[test]
fn subsec_is_valid_micros_not_nanos() {
let mut interp = interpreter();
Expand Down

0 comments on commit b3012e5

Please sign in to comment.