1use std::any::Any;
2use std::cell::{Cell, RefCell};
3use std::rc::Rc;
4
5use mrubyedge::{
6 Error,
7 yamrb::{
8 helpers::{mrb_define_class_cmethod, mrb_define_cmethod, mrb_funcall},
9 value::{RData, RHashMap, RObject, RType, RValue},
10 vm::VM,
11 },
12};
13
14type DateTimeParts = (i32, u32, u32, u32, u32, u32, u32);
16
17#[derive(Debug, Clone)]
20pub struct RTimeData {
21 pub sec: i64,
23 pub nsec: u32,
25 pub utc_offset: i32,
27 cached_parts: Cell<Option<DateTimeParts>>,
29}
30
31impl RTimeData {
32 pub fn new(sec: i64, nsec: u32, utc_offset: i32) -> Self {
33 RTimeData {
34 sec,
35 nsec,
36 utc_offset,
37 cached_parts: Cell::new(None),
38 }
39 }
40
41 fn local_sec(&self) -> i64 {
43 self.sec + self.utc_offset as i64
44 }
45
46 pub fn to_datetime_parts(&self) -> DateTimeParts {
50 if let Some(parts) = self.cached_parts.get() {
51 return parts;
52 }
53 let local = self.local_sec();
54
55 let sec_in_day = local.rem_euclid(86400) as u32;
57 let hour = sec_in_day / 3600;
58 let min = (sec_in_day % 3600) / 60;
59 let sec = sec_in_day % 60;
60
61 let days_from_epoch = local.div_euclid(86400);
63
64 let jdn = days_from_epoch + 2440588;
66
67 let l = jdn + 68569;
70 let n = (4 * l) / 146097;
71 let l = l - (146097 * n + 3) / 4;
72 let i = (4000 * (l + 1)) / 1461001;
73 let l = l - (1461 * i) / 4 + 31;
74 let j = (80 * l) / 2447;
75 let day = l - (2447 * j) / 80;
76 let l = j / 11;
77 let month = j + 2 - 12 * l;
78 let year = 100 * (n - 49) + i + l;
79
80 let wday = (jdn + 1).rem_euclid(7) as u32; let parts = (year as i32, month as u32, day as u32, wday, hour, min, sec);
85 self.cached_parts.set(Some(parts));
86 parts
87 }
88
89 pub fn to_s(&self) -> String {
91 let (year, month, day, _wday, hour, min, sec) = self.to_datetime_parts();
92 let offset_sign = if self.utc_offset >= 0 { '+' } else { '-' };
93 let abs_offset = self.utc_offset.unsigned_abs();
94 let offset_h = abs_offset / 3600;
95 let offset_m = (abs_offset % 3600) / 60;
96 format!(
97 "{:04}-{:02}-{:02} {:02}:{:02}:{:02} {}{:02}{:02}",
98 year, month, day, hour, min, sec, offset_sign, offset_h, offset_m
99 )
100 }
101}
102
103fn get_time_data(obj: &Rc<RObject>) -> Result<RTimeData, Error> {
105 match &obj.value {
106 RValue::Data(data) => {
107 let borrow = data.data.borrow();
108 let any_ref = borrow
109 .as_ref()
110 .ok_or_else(|| Error::RuntimeError("Invalid Time data".to_string()))?;
111 let time = any_ref
112 .downcast_ref::<RTimeData>()
113 .ok_or_else(|| Error::RuntimeError("Invalid Time data".to_string()))?;
114 Ok(time.clone())
115 }
116 _ => Err(Error::RuntimeError("Expected a Time object".to_string())),
117 }
118}
119
120fn make_time_object(vm: &mut VM, time_data: RTimeData) -> Rc<RObject> {
122 let time_class_obj = vm
123 .get_const_by_name("Time")
124 .expect("Time class not found; did you call init_time?");
125 let class = match &time_class_obj.value {
126 RValue::Class(c) => c.clone(),
127 _ => panic!("Time is not a class"),
128 };
129 let rdata = Rc::new(RData {
130 class,
131 data: RefCell::new(Some(Rc::new(Box::new(time_data) as Box<dyn Any>))),
132 ref_count: 1,
133 });
134 Rc::new(RObject {
135 tt: RType::Data,
136 value: RValue::Data(rdata),
137 object_id: Cell::new(u64::MAX),
138 singleton_class: RefCell::new(None),
139 ivar: RefCell::new(RHashMap::default()),
140 })
141}
142
143fn mrb_time_now(vm: &mut VM, _args: &[Rc<RObject>]) -> Result<Rc<RObject>, Error> {
151 let time_class_obj = vm
152 .get_const_by_name("Time")
153 .ok_or_else(|| Error::RuntimeError("Time class not found".to_string()))?;
154
155 let source = mrb_funcall(vm, Some(time_class_obj), "__source", &[])?;
157 let (sec, nsec) = source.as_ref().try_into()?;
158
159 Ok(make_time_object(vm, RTimeData::new(sec, nsec, 0)))
160}
161
162fn mrb_time_at(vm: &mut VM, args: &[Rc<RObject>]) -> Result<Rc<RObject>, Error> {
164 let args = strip_trailing_nil(args);
165 if args.is_empty() {
166 return Err(Error::ArgumentError(
167 "wrong number of arguments (given 0, expected 1+)".to_string(),
168 ));
169 }
170
171 let sec = get_integer_or_float_as_i64(&args[0])?;
172 let nsec = if args.len() >= 2 {
173 get_integer_or_float_as_u32(&args[1])?
174 } else {
175 0
176 };
177
178 Ok(make_time_object(vm, RTimeData::new(sec, nsec, 0)))
179}
180
181fn mrb_time_year(vm: &mut VM, _args: &[Rc<RObject>]) -> Result<Rc<RObject>, Error> {
187 let self_obj = vm.getself()?;
188 let t = get_time_data(&self_obj)?;
189 let (year, _, _, _, _, _, _) = t.to_datetime_parts();
190 Ok(RObject::integer(year as i64).to_refcount_assigned())
191}
192
193fn mrb_time_month(vm: &mut VM, _args: &[Rc<RObject>]) -> Result<Rc<RObject>, Error> {
195 let self_obj = vm.getself()?;
196 let t = get_time_data(&self_obj)?;
197 let (_, month, _, _, _, _, _) = t.to_datetime_parts();
198 Ok(RObject::integer(month as i64).to_refcount_assigned())
199}
200
201fn mrb_time_day(vm: &mut VM, _args: &[Rc<RObject>]) -> Result<Rc<RObject>, Error> {
203 let self_obj = vm.getself()?;
204 let t = get_time_data(&self_obj)?;
205 let (_, _, day, _, _, _, _) = t.to_datetime_parts();
206 Ok(RObject::integer(day as i64).to_refcount_assigned())
207}
208
209fn mrb_time_wday(vm: &mut VM, _args: &[Rc<RObject>]) -> Result<Rc<RObject>, Error> {
211 let self_obj = vm.getself()?;
212 let t = get_time_data(&self_obj)?;
213 let (_, _, _, wday, _, _, _) = t.to_datetime_parts();
214 Ok(RObject::integer(wday as i64).to_refcount_assigned())
215}
216
217fn mrb_time_hour(vm: &mut VM, _args: &[Rc<RObject>]) -> Result<Rc<RObject>, Error> {
219 let self_obj = vm.getself()?;
220 let t = get_time_data(&self_obj)?;
221 let (_, _, _, _, hour, _, _) = t.to_datetime_parts();
222 Ok(RObject::integer(hour as i64).to_refcount_assigned())
223}
224
225fn mrb_time_min(vm: &mut VM, _args: &[Rc<RObject>]) -> Result<Rc<RObject>, Error> {
227 let self_obj = vm.getself()?;
228 let t = get_time_data(&self_obj)?;
229 let (_, _, _, _, _, min, _) = t.to_datetime_parts();
230 Ok(RObject::integer(min as i64).to_refcount_assigned())
231}
232
233fn mrb_time_sec(vm: &mut VM, _args: &[Rc<RObject>]) -> Result<Rc<RObject>, Error> {
235 let self_obj = vm.getself()?;
236 let t = get_time_data(&self_obj)?;
237 let (_, _, _, _, _, _, sec) = t.to_datetime_parts();
238 Ok(RObject::integer(sec as i64).to_refcount_assigned())
239}
240
241fn mrb_time_nsec(vm: &mut VM, _args: &[Rc<RObject>]) -> Result<Rc<RObject>, Error> {
243 let self_obj = vm.getself()?;
244 let t = get_time_data(&self_obj)?;
245 Ok(RObject::integer(t.nsec as i64).to_refcount_assigned())
246}
247
248fn mrb_time_to_s(vm: &mut VM, _args: &[Rc<RObject>]) -> Result<Rc<RObject>, Error> {
250 let self_obj = vm.getself()?;
251 let t = get_time_data(&self_obj)?;
252 Ok(RObject::string(t.to_s()).to_refcount_assigned())
253}
254
255fn mrb_time_add(vm: &mut VM, args: &[Rc<RObject>]) -> Result<Rc<RObject>, Error> {
257 let args = strip_trailing_nil(args);
258 if args.is_empty() {
259 return Err(Error::ArgumentError(
260 "wrong number of arguments (given 0, expected 1)".to_string(),
261 ));
262 }
263 let self_obj = vm.getself()?;
264 let t = get_time_data(&self_obj)?;
265
266 let (delta_sec, delta_nsec) = float_to_sec_nsec(&args[0])?;
267 let new_nsec = t.nsec as i64 + delta_nsec as i64;
268 let carry = new_nsec.div_euclid(1_000_000_000);
269 let new_nsec = new_nsec.rem_euclid(1_000_000_000) as u32;
270 let new_sec = t.sec + delta_sec + carry;
271
272 Ok(make_time_object(
273 vm,
274 RTimeData::new(new_sec, new_nsec, t.utc_offset),
275 ))
276}
277
278fn mrb_time_sub(vm: &mut VM, args: &[Rc<RObject>]) -> Result<Rc<RObject>, Error> {
280 let args = strip_trailing_nil(args);
281 if args.is_empty() {
282 return Err(Error::ArgumentError(
283 "wrong number of arguments (given 0, expected 1)".to_string(),
284 ));
285 }
286 let self_obj = vm.getself()?;
287 let t = get_time_data(&self_obj)?;
288
289 if let RValue::Data(_) = &args[0].value
291 && let Ok(rhs) = get_time_data(&args[0])
292 {
293 let sec_diff =
295 (t.sec - rhs.sec) as f64 + (t.nsec as f64 - rhs.nsec as f64) / 1_000_000_000.0;
296 return Ok(RObject::float(sec_diff).to_refcount_assigned());
297 }
298
299 let (delta_sec, delta_nsec) = float_to_sec_nsec(&args[0])?;
300 let new_nsec = t.nsec as i64 - delta_nsec as i64;
301 let carry = new_nsec.div_euclid(1_000_000_000);
302 let new_nsec = new_nsec.rem_euclid(1_000_000_000) as u32;
303 let new_sec = t.sec - delta_sec + carry;
304
305 Ok(make_time_object(
306 vm,
307 RTimeData::new(new_sec, new_nsec, t.utc_offset),
308 ))
309}
310
311fn mrb_time_cmp(vm: &mut VM, args: &[Rc<RObject>]) -> Result<Rc<RObject>, Error> {
313 let args = strip_trailing_nil(args);
314 if args.is_empty() {
315 return Err(Error::ArgumentError(
316 "wrong number of arguments (given 0, expected 1)".to_string(),
317 ));
318 }
319 let self_obj = vm.getself()?;
320 let t = get_time_data(&self_obj)?;
321
322 let rhs = match get_time_data(&args[0]) {
323 Ok(r) => r,
324 Err(_) => return Ok(RObject::nil().to_refcount_assigned()),
325 };
326
327 let result = match t.sec.cmp(&rhs.sec) {
328 std::cmp::Ordering::Equal => t.nsec.cmp(&rhs.nsec),
329 other => other,
330 };
331
332 let int_val = match result {
333 std::cmp::Ordering::Less => -1i64,
334 std::cmp::Ordering::Equal => 0,
335 std::cmp::Ordering::Greater => 1,
336 };
337 Ok(RObject::integer(int_val).to_refcount_assigned())
338}
339
340fn mrb_time_utc_offset(vm: &mut VM, _args: &[Rc<RObject>]) -> Result<Rc<RObject>, Error> {
342 let self_obj = vm.getself()?;
343 let t = get_time_data(&self_obj)?;
344 Ok(RObject::integer(t.utc_offset as i64).to_refcount_assigned())
345}
346
347fn mrb_time_localtime(vm: &mut VM, args: &[Rc<RObject>]) -> Result<Rc<RObject>, Error> {
349 let args = strip_trailing_nil(args);
350 let self_obj = vm.getself()?;
351 let t = get_time_data(&self_obj)?;
352
353 let new_offset = if args.is_empty() {
354 0i32 } else {
356 get_integer_or_float_as_i64(&args[0])? as i32
357 };
358
359 Ok(make_time_object(
360 vm,
361 RTimeData::new(t.sec, t.nsec, new_offset),
362 ))
363}
364
365fn mrb_time_to_i(vm: &mut VM, _args: &[Rc<RObject>]) -> Result<Rc<RObject>, Error> {
367 let self_obj = vm.getself()?;
368 let t = get_time_data(&self_obj)?;
369 Ok(RObject::integer(t.sec).to_refcount_assigned())
370}
371
372fn mrb_time_to_f(vm: &mut VM, _args: &[Rc<RObject>]) -> Result<Rc<RObject>, Error> {
374 let self_obj = vm.getself()?;
375 let t = get_time_data(&self_obj)?;
376 let f = t.sec as f64 + t.nsec as f64 / 1_000_000_000.0;
377 Ok(RObject::float(f).to_refcount_assigned())
378}
379
380#[cfg(any(not(target_arch = "wasm32"), target_os = "wasi"))]
388fn mrb_time_source_default(_vm: &mut VM, _args: &[Rc<RObject>]) -> Result<Rc<RObject>, Error> {
389 use std::time::{SystemTime, UNIX_EPOCH};
390 let now = SystemTime::now();
391 let unixtime = now.duration_since(UNIX_EPOCH).map_err(|_| {
392 Error::RuntimeError(
393 "system time before UNIX EPOCH -- are you running this from the past?".to_string(),
394 )
395 })?;
396 let sec = unixtime.as_secs() as i64;
397 let nsec = unixtime.subsec_nanos() as i64;
398 let arr = vec![
399 RObject::integer(sec).to_refcount_assigned(),
400 RObject::integer(nsec).to_refcount_assigned(),
401 ];
402 Ok(RObject::array(arr).to_refcount_assigned())
403}
404
405fn strip_trailing_nil(args: &[Rc<RObject>]) -> &[Rc<RObject>] {
410 if !args.is_empty() && args[args.len() - 1].is_nil() {
411 &args[0..args.len() - 1]
412 } else {
413 args
414 }
415}
416
417fn get_integer_or_float_as_i64(obj: &RObject) -> Result<i64, Error> {
418 match &obj.value {
419 RValue::Integer(i) => Ok(*i),
420 RValue::Float(f) => Ok(*f as i64),
421 _ => Err(Error::ArgumentError(
422 "expected Integer or Float".to_string(),
423 )),
424 }
425}
426
427fn get_integer_or_float_as_u32(obj: &RObject) -> Result<u32, Error> {
428 match &obj.value {
429 RValue::Integer(i) => {
430 if *i < 0 {
431 return Err(Error::ArgumentError(
432 "nsec must be non-negative".to_string(),
433 ));
434 }
435 Ok(*i as u32)
436 }
437 RValue::Float(f) => {
438 if *f < 0.0 {
439 return Err(Error::ArgumentError(
440 "nsec must be non-negative".to_string(),
441 ));
442 }
443 Ok(*f as u32)
444 }
445 _ => Err(Error::ArgumentError(
446 "expected Integer or Float".to_string(),
447 )),
448 }
449}
450
451fn float_to_sec_nsec(obj: &RObject) -> Result<(i64, u32), Error> {
453 match &obj.value {
454 RValue::Integer(i) => Ok((*i, 0)),
455 RValue::Float(f) => {
456 let sec = f.trunc() as i64;
457 let nsec = (f.fract().abs() * 1_000_000_000.0).round() as u32;
458 Ok((sec, nsec))
459 }
460 _ => Err(Error::ArgumentError(
461 "expected Integer or Float".to_string(),
462 )),
463 }
464}
465
466pub fn init_time(vm: &mut VM) {
473 let time_class = vm.define_class("Time", None, None);
474
475 mrb_define_class_cmethod(vm, time_class.clone(), "now", Box::new(mrb_time_now));
477 mrb_define_class_cmethod(vm, time_class.clone(), "at", Box::new(mrb_time_at));
478
479 mrb_define_cmethod(vm, time_class.clone(), "year", Box::new(mrb_time_year));
481 mrb_define_cmethod(vm, time_class.clone(), "month", Box::new(mrb_time_month));
482 mrb_define_cmethod(vm, time_class.clone(), "mon", Box::new(mrb_time_month));
483 mrb_define_cmethod(vm, time_class.clone(), "day", Box::new(mrb_time_day));
484 mrb_define_cmethod(vm, time_class.clone(), "mday", Box::new(mrb_time_day));
485 mrb_define_cmethod(vm, time_class.clone(), "wday", Box::new(mrb_time_wday));
486 mrb_define_cmethod(vm, time_class.clone(), "hour", Box::new(mrb_time_hour));
487 mrb_define_cmethod(vm, time_class.clone(), "min", Box::new(mrb_time_min));
488 mrb_define_cmethod(vm, time_class.clone(), "sec", Box::new(mrb_time_sec));
489 mrb_define_cmethod(vm, time_class.clone(), "nsec", Box::new(mrb_time_nsec));
490 mrb_define_cmethod(vm, time_class.clone(), "to_s", Box::new(mrb_time_to_s));
491 mrb_define_cmethod(vm, time_class.clone(), "inspect", Box::new(mrb_time_to_s));
492 mrb_define_cmethod(vm, time_class.clone(), "+", Box::new(mrb_time_add));
493 mrb_define_cmethod(vm, time_class.clone(), "-", Box::new(mrb_time_sub));
494 mrb_define_cmethod(vm, time_class.clone(), "<=>", Box::new(mrb_time_cmp));
495 mrb_define_cmethod(
496 vm,
497 time_class.clone(),
498 "utc_offset",
499 Box::new(mrb_time_utc_offset),
500 );
501 mrb_define_cmethod(
502 vm,
503 time_class.clone(),
504 "gmt_offset",
505 Box::new(mrb_time_utc_offset),
506 );
507 mrb_define_cmethod(
508 vm,
509 time_class.clone(),
510 "localtime",
511 Box::new(mrb_time_localtime),
512 );
513 mrb_define_cmethod(vm, time_class.clone(), "to_i", Box::new(mrb_time_to_i));
514 mrb_define_cmethod(vm, time_class.clone(), "to_f", Box::new(mrb_time_to_f));
515
516 #[cfg(any(not(target_arch = "wasm32"), target_os = "wasi"))]
518 {
519 let _time_class_obj = RObject::class(time_class, vm);
520 let time_class_obj_for_source = vm
521 .get_const_by_name("Time")
522 .expect("Time class not found after definition");
523 mrb_define_class_cmethod_on_obj(
524 vm,
525 time_class_obj_for_source,
526 "__source",
527 Box::new(mrb_time_source_default),
528 );
529 }
530}
531
532#[cfg(any(not(target_arch = "wasm32"), target_os = "wasi"))]
534fn mrb_define_class_cmethod_on_obj(
535 vm: &mut VM,
536 class_obj: Rc<RObject>,
537 name: &str,
538 cmethod: mrubyedge::yamrb::value::RFn,
539) {
540 use mrubyedge::yamrb::helpers::mrb_define_singleton_cmethod;
541 mrb_define_singleton_cmethod(vm, class_obj, name, cmethod);
542}