1use crate::convert::{to_int, to_str};
2use crate::extn::core::kernel::integer;
3use crate::extn::prelude::*;
4
5#[derive(Debug, Copy, Clone)]
6pub struct Args {
7 pub year: i32,
8 pub month: u8,
9 pub day: u8,
10 pub hour: u8,
11 pub minute: u8,
12 pub second: u8,
13 pub nanoseconds: u32,
14}
15
16impl Default for Args {
17 fn default() -> Self {
18 Self {
19 year: 0,
20 month: 1,
21 day: 1,
22 hour: 0,
23 minute: 0,
24 second: 0,
25 nanoseconds: 0,
26 }
27 }
28}
29
30impl TryConvertMut<&mut [Value], Args> for Artichoke {
31 type Error = Error;
32
33 fn try_convert_mut(&mut self, mut args: &mut [Value]) -> Result<Args, Self::Error> {
34 if let 0 | 9 | 11 = args.len() {
38 let mut message = b"wrong number of arguments (given ".to_vec();
39 message.extend(args.len().to_string().bytes());
40 message.extend_from_slice(b", expected 1..8)");
41 return Err(ArgumentError::from(message).into());
42 }
43
44 if args.len() == 10 {
48 args.swap(0, 5);
49 args.swap(1, 4);
50 args.swap(2, 3);
51 args = &mut args[..6];
54 }
55
56 let mut result = Args::default();
57
58 for (i, &arg) in args.iter().enumerate() {
59 match i {
60 0 => {
61 let arg: i64 = to_int(self, arg).and_then(|arg| arg.try_convert_into(self))?;
62
63 result.year = i32::try_from(arg).map_err(|_| ArgumentError::with_message("year out of range"))?;
64 }
65 1 if Ruby::Fixnum == arg.ruby_type() => {
67 let arg = to_int(self, arg)?;
68 let arg: i64 = arg.try_convert_into(self)?;
69
70 result.month = match u8::try_from(arg) {
71 Ok(month @ 1..=12) => month,
72 _ => return Err(ArgumentError::with_message("mon out of range").into()),
73 };
74 }
75 1 => {
76 let month: i64 = if let Ok(arg) = to_str(self, arg) {
95 let mut month_str: Vec<u8> = arg.try_convert_into_mut(self)?;
96
97 let month_str = month_str.get_mut(..3).unwrap_or_default();
102 month_str.make_ascii_lowercase();
103
104 match &*month_str {
105 b"jan" => 1,
106 b"feb" => 2,
107 b"mar" => 3,
108 b"apr" => 4,
109 b"may" => 5,
110 b"jun" => 6,
111 b"jul" => 7,
112 b"aug" => 8,
113 b"sep" => 9,
114 b"oct" => 10,
115 b"nov" => 11,
116 b"dec" => 12,
117 _ => {
118 let arg = integer(self, arg, None)?;
121 arg.try_convert_into(self)?
122 }
123 }
124 } else {
125 let arg = to_int(self, arg)?;
126 arg.try_convert_into(self)?
127 };
128
129 result.month = match u8::try_from(month) {
130 Ok(month @ 1..=12) => month,
131 _ => return Err(ArgumentError::with_message("mon out of range").into()),
132 };
133 }
134 2 => {
135 let arg = to_int(self, arg)?;
136 let arg: i64 = arg.try_convert_into(self)?;
137
138 result.day = match u8::try_from(arg) {
139 Ok(day @ 1..=31) => day,
140 _ => return Err(ArgumentError::with_message("mday out of range").into()),
141 };
142 }
143 3 => {
144 let arg = to_int(self, arg)?;
145 let arg: i64 = arg.try_convert_into(self)?;
146
147 result.hour = match u8::try_from(arg) {
148 Ok(hour @ 0..=59) => hour,
149 _ => return Err(ArgumentError::with_message("hour out of range").into()),
150 };
151 }
152 4 => {
153 let arg = to_int(self, arg)?;
154 let arg: i64 = arg.try_convert_into(self)?;
155
156 result.minute = match u8::try_from(arg) {
157 Ok(minute @ 0..=59) => minute,
158 _ => return Err(ArgumentError::with_message("min out of range").into()),
159 };
160 }
161 5 => {
162 let arg = to_int(self, arg)?;
169 let arg: i64 = arg.try_convert_into(self)?;
170
171 result.second = match u8::try_from(arg) {
172 Ok(second @ 0..=60) => second,
173 _ => return Err(ArgumentError::with_message("sec out of range").into()),
174 };
175 }
176 6 => {
177 let arg = to_int(self, arg)?;
178 let arg: i64 = arg.try_convert_into(self)?;
179
180 result.nanoseconds = match u32::try_from(arg) {
184 Ok(micros @ 0..=999_999) => micros * 1000,
185 _ => return Err(ArgumentError::with_message("subsecx out of range").into()),
190 };
191 }
192 7 => {
193 }
200 _ => {
201 unreachable!()
205 }
206 }
207 }
208
209 Ok(result)
210 }
211}
212
213#[cfg(test)]
214mod tests {
215 use bstr::ByteSlice;
216
217 use super::Args;
218 use crate::test::prelude::*;
219
220 #[test]
221 fn requires_at_least_one_param() {
222 let mut interp = interpreter();
223
224 let mut args = vec![];
225
226 let result: Result<Args, Error> = interp.try_convert_mut(args.as_mut_slice());
227 let error = result.unwrap_err();
228
229 assert_eq!(error.name(), "ArgumentError");
230 assert_eq!(
231 error.message().as_bstr(),
232 b"wrong number of arguments (given 0, expected 1..8)".as_bstr()
233 );
234 }
235
236 #[test]
237 fn eight_params() {
238 let mut interp = interpreter();
239
240 let args = interp.eval(b"[2022, 2, 3, 4, 5, 6, 7, nil]").unwrap();
241 let mut ary_args: Vec<Value> = interp.try_convert_mut(args).unwrap();
242 let result: Args = interp.try_convert_mut(ary_args.as_mut_slice()).unwrap();
243 assert_eq!(2022, result.year);
244 assert_eq!(2, result.month);
245 assert_eq!(3, result.day);
246 assert_eq!(4, result.hour);
247 assert_eq!(5, result.minute);
248 assert_eq!(6, result.second);
249 assert_eq!(7000, result.nanoseconds);
250 }
251
252 #[test]
253 fn seven_params() {
254 let mut interp = interpreter();
255
256 let args = interp.eval(b"[2022, 2, 3, 4, 5, 6, 7]").unwrap();
257 let mut ary_args: Vec<Value> = interp.try_convert_mut(args).unwrap();
258 let result: Args = interp.try_convert_mut(ary_args.as_mut_slice()).unwrap();
259 assert_eq!(2022, result.year);
260 assert_eq!(2, result.month);
261 assert_eq!(3, result.day);
262 assert_eq!(4, result.hour);
263 assert_eq!(5, result.minute);
264 assert_eq!(6, result.second);
265 assert_eq!(7000, result.nanoseconds);
266 }
267
268 #[test]
269 fn six_params() {
270 let mut interp = interpreter();
271
272 let args = interp.eval(b"[2022, 2, 3, 4, 5, 6]").unwrap();
273 let mut ary_args: Vec<Value> = interp.try_convert_mut(args).unwrap();
274 let result: Args = interp.try_convert_mut(ary_args.as_mut_slice()).unwrap();
275 assert_eq!(2022, result.year);
276 assert_eq!(2, result.month);
277 assert_eq!(3, result.day);
278 assert_eq!(4, result.hour);
279 assert_eq!(5, result.minute);
280 assert_eq!(6, result.second);
281 assert_eq!(0, result.nanoseconds);
282 }
283
284 #[test]
285 fn five_params() {
286 let mut interp = interpreter();
287
288 let args = interp.eval(b"[2022, 2, 3, 4, 5]").unwrap();
289 let mut ary_args: Vec<Value> = interp.try_convert_mut(args).unwrap();
290 let result: Args = interp.try_convert_mut(ary_args.as_mut_slice()).unwrap();
291 assert_eq!(2022, result.year);
292 assert_eq!(2, result.month);
293 assert_eq!(3, result.day);
294 assert_eq!(4, result.hour);
295 assert_eq!(5, result.minute);
296 assert_eq!(0, result.second);
297 assert_eq!(0, result.nanoseconds);
298 }
299
300 #[test]
301 fn four_params() {
302 let mut interp = interpreter();
303
304 let args = interp.eval(b"[2022, 2, 3, 4]").unwrap();
305 let mut ary_args: Vec<Value> = interp.try_convert_mut(args).unwrap();
306 let result: Args = interp.try_convert_mut(ary_args.as_mut_slice()).unwrap();
307 assert_eq!(2022, result.year);
308 assert_eq!(2, result.month);
309 assert_eq!(3, result.day);
310 assert_eq!(4, result.hour);
311 assert_eq!(0, result.minute);
312 assert_eq!(0, result.second);
313 assert_eq!(0, result.nanoseconds);
314 }
315
316 #[test]
317 fn three_params() {
318 let mut interp = interpreter();
319
320 let args = interp.eval(b"[2022, 2, 3]").unwrap();
321 let mut ary_args: Vec<Value> = interp.try_convert_mut(args).unwrap();
322 let result: Args = interp.try_convert_mut(ary_args.as_mut_slice()).unwrap();
323 assert_eq!(2022, result.year);
324 assert_eq!(2, result.month);
325 assert_eq!(3, result.day);
326 assert_eq!(0, result.hour);
327 assert_eq!(0, result.minute);
328 assert_eq!(0, result.second);
329 assert_eq!(0, result.nanoseconds);
330 }
331
332 #[test]
333 fn two_params() {
334 let mut interp = interpreter();
335
336 let args = interp.eval(b"[2022, 2]").unwrap();
337 let mut ary_args: Vec<Value> = interp.try_convert_mut(args).unwrap();
338 let result: Args = interp.try_convert_mut(ary_args.as_mut_slice()).unwrap();
339 assert_eq!(2022, result.year);
340 assert_eq!(2, result.month);
341 assert_eq!(1, result.day);
342 assert_eq!(0, result.hour);
343 assert_eq!(0, result.minute);
344 assert_eq!(0, result.second);
345 assert_eq!(0, result.nanoseconds);
346 }
347
348 #[test]
349 fn one_param() {
350 let mut interp = interpreter();
351
352 let args = interp.eval(b"[2022]").unwrap();
353 let mut ary_args: Vec<Value> = interp.try_convert_mut(args).unwrap();
354 let result: Args = interp.try_convert_mut(ary_args.as_mut_slice()).unwrap();
355 assert_eq!(2022, result.year);
356 assert_eq!(1, result.month);
357 assert_eq!(1, result.day);
358 assert_eq!(0, result.hour);
359 assert_eq!(0, result.minute);
360 assert_eq!(0, result.second);
361 assert_eq!(0, result.nanoseconds);
362 }
363
364 #[test]
365 fn month_supports_string_values() {
366 let mut interp = interpreter();
367
368 let table = [
369 (b"[2022, 'jan']", 1),
370 (b"[2022, 'feb']", 2),
371 (b"[2022, 'mar']", 3),
372 (b"[2022, 'apr']", 4),
373 (b"[2022, 'may']", 5),
374 (b"[2022, 'jun']", 6),
375 (b"[2022, 'jul']", 7),
376 (b"[2022, 'aug']", 8),
377 (b"[2022, 'sep']", 9),
378 (b"[2022, 'oct']", 10),
379 (b"[2022, 'nov']", 11),
380 (b"[2022, 'dec']", 12),
381 ];
382
383 for (input, expected_month) in table {
384 let args = interp.eval(input).unwrap();
385 let mut ary_args: Vec<Value> = interp.try_convert_mut(args).unwrap();
386 let result: Args = interp.try_convert_mut(ary_args.as_mut_slice()).unwrap();
387
388 assert_eq!(expected_month, result.month);
389 }
390 }
391
392 #[test]
393 fn month_strings_are_case_insensitive() {
394 let mut interp = interpreter();
395
396 let table = [
397 (b"[2022, 'Feb']", 2),
398 (b"[2022, 'fEb']", 2),
399 (b"[2022, 'feB']", 2),
400 (b"[2022, 'FEb']", 2),
401 (b"[2022, 'FeB']", 2),
402 (b"[2022, 'fEB']", 2),
403 (b"[2022, 'FEB']", 2),
404 ];
405
406 for (input, expected_month) in table {
407 let args = interp.eval(input).unwrap();
408 let mut ary_args: Vec<Value> = interp.try_convert_mut(args).unwrap();
409 let result: Args = interp.try_convert_mut(ary_args.as_mut_slice()).unwrap();
410
411 assert_eq!(expected_month, result.month);
412 assert_eq!(2022, result.year);
413 }
414 }
415
416 #[test]
417 fn month_supports_string_like_values() {
418 let mut interp = interpreter();
419
420 let args = interp
421 .eval(b"class A; def to_str; 'feb'; end; end; [2022, A.new]")
422 .unwrap();
423 let mut ary_args: Vec<Value> = interp.try_convert_mut(args).unwrap();
424 let result: Args = interp.try_convert_mut(ary_args.as_mut_slice()).unwrap();
425
426 assert_eq!(2, result.month);
427 }
428
429 #[test]
430 fn month_supports_int_like_values() {
431 let mut interp = interpreter();
432
433 let args = interp.eval(b"class A; def to_int; 2; end; end; [2022, A.new]").unwrap();
434 let mut ary_args: Vec<Value> = interp.try_convert_mut(args).unwrap();
435 let result: Args = interp.try_convert_mut(ary_args.as_mut_slice()).unwrap();
436
437 assert_eq!(2, result.month);
438 }
439
440 #[test]
441 fn month_string_can_be_integer_strings() {
442 let mut interp = interpreter();
443
444 let args = interp
445 .eval(b"class A; def to_str; '2'; end; end; [2022, A.new]")
446 .unwrap();
447 let mut ary_args: Vec<Value> = interp.try_convert_mut(args).unwrap();
448 let result: Args = interp.try_convert_mut(ary_args.as_mut_slice()).unwrap();
449
450 assert_eq!(2, result.month);
451 }
452
453 #[test]
454 fn invalid_month_string_responds_with_int_conversion_error() {
455 let mut interp = interpreter();
456
457 let args = interp
458 .eval(b"class A; def to_str; 'aaa'; end; end; [2022, A.new]")
459 .unwrap();
460 let mut ary_args: Vec<Value> = interp.try_convert_mut(args).unwrap();
461 let result: Result<Args, Error> = interp.try_convert_mut(ary_args.as_mut_slice());
462 let error = result.unwrap_err();
463
464 assert_eq!(
465 error.message().as_bstr(),
466 br#"invalid value for Integer(): "aaa""#.as_bstr()
467 );
468 assert_eq!(error.name(), "ArgumentError");
469 }
470
471 #[test]
472 fn month_downcase_shortcut_does_not_limit_call_to_integer() {
473 let mut interp = interpreter();
474
475 let args = interp
476 .eval(b"class I; def to_str; '0000000002'; end; end; [2022, I.new]")
477 .unwrap();
478 let mut ary_args: Vec<Value> = interp.try_convert_mut(args).unwrap();
479 let result: Args = interp.try_convert_mut(ary_args.as_mut_slice()).unwrap();
480
481 assert_eq!(2, result.month);
482 }
483
484 #[test]
485 fn subsec_is_valid_micros_not_nanos() {
486 let mut interp = interpreter();
487
488 let args = interp.eval(b"[2022, 1, 1, 0, 0, 0, 1]").unwrap();
489 let mut ary_args: Vec<Value> = interp.try_convert_mut(args).unwrap();
490 let result: Args = interp.try_convert_mut(ary_args.as_mut_slice()).unwrap();
491 let nanos = result.nanoseconds;
492 assert_eq!(1000, nanos);
493
494 let args = interp.eval(b"[2022, 1, 1, 0, 0, 0, 999_999]").unwrap();
495 let mut ary_args: Vec<Value> = interp.try_convert_mut(args).unwrap();
496 let result: Args = interp.try_convert_mut(ary_args.as_mut_slice()).unwrap();
497 let nanos = result.nanoseconds;
498 assert_eq!(999_999_000, nanos);
499 }
500
501 #[test]
502 fn subsec_does_not_wrap_around() {
503 let mut interp = interpreter();
504
505 let args = interp.eval(b"[2022, 1, 1, 0, 0, 0, -1]").unwrap();
506 let mut ary_args: Vec<Value> = interp.try_convert_mut(args).unwrap();
507 let result: Result<Args, Error> = interp.try_convert_mut(ary_args.as_mut_slice());
508 let error = result.unwrap_err();
509 assert_eq!(error.message().as_bstr(), b"subsecx out of range".as_bstr());
510
511 let args = interp.eval(b"[2022, 1, 1, 0, 0, 0, 1_000_000]").unwrap();
512 let mut ary_args: Vec<Value> = interp.try_convert_mut(args).unwrap();
513 let result: Result<Args, Error> = interp.try_convert_mut(ary_args.as_mut_slice());
514 let error = result.unwrap_err();
515 assert_eq!(error.message().as_bstr(), b"subsecx out of range".as_bstr());
516 }
517
518 #[test]
519 #[should_panic(expected = "not yet implemented")]
520 fn fractional_seconds_return_nanos() {
521 todo!("fractional seconds is not implemented")
532 }
533
534 #[test]
535 fn nine_args_not_supported() {
536 let mut interp = interpreter();
537
538 let args = interp.eval(b"[2022, 2, 3, 4, 5, 6, 7, nil, 0]").unwrap();
539 let mut ary_args: Vec<Value> = interp.try_convert_mut(args).unwrap();
540 let result: Result<Args, Error> = interp.try_convert_mut(ary_args.as_mut_slice());
541 let error = result.unwrap_err();
542
543 assert_eq!(
544 error.message().as_bstr(),
545 b"wrong number of arguments (given 9, expected 1..8)".as_bstr()
546 );
547 assert_eq!(error.name(), "ArgumentError");
548 }
549
550 #[test]
551 fn ten_args_changes_unit_order() {
552 let mut interp = interpreter();
553
554 let args = interp.eval(b"[1, 2, 3, 4, 5, 2022, nil, nil, nil, nil]").unwrap();
555 let mut ary_args: Vec<Value> = interp.try_convert_mut(args).unwrap();
556 let result: Args = interp.try_convert_mut(ary_args.as_mut_slice()).unwrap();
557
558 assert_eq!(1, result.second);
559 assert_eq!(2, result.minute);
560 assert_eq!(3, result.hour);
561 assert_eq!(4, result.day);
562 assert_eq!(5, result.month);
563 assert_eq!(2022, result.year);
564 }
565
566 #[test]
567 fn eleven_args_is_too_many() {
568 let mut interp = interpreter();
569
570 let args = interp.eval(b"[2022, 2, 3, 4, 5, 6, 7, nil, 0, 0, 0]").unwrap();
571 let mut ary_args: Vec<Value> = interp.try_convert_mut(args).unwrap();
572 let result: Result<Args, Error> = interp.try_convert_mut(ary_args.as_mut_slice());
573 let error = result.unwrap_err();
574
575 assert_eq!(
576 error.message().as_bstr(),
577 b"wrong number of arguments (given 11, expected 1..8)".as_bstr()
578 );
579 assert_eq!(error.name(), "ArgumentError");
580 }
581}