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> {
150 let time_class_obj = vm
151 .get_const_by_name("Time")
152 .ok_or_else(|| Error::RuntimeError("Time class not found".to_string()))?;
153
154 let source = mrb_funcall(vm, Some(time_class_obj), "__source", &[])?;
156 let (sec, nsec, utc_offset) = source.as_ref().try_into()?;
157
158 Ok(make_time_object(vm, RTimeData::new(sec, nsec, utc_offset)))
159}
160
161fn mrb_time_at(vm: &mut VM, args: &[Rc<RObject>]) -> Result<Rc<RObject>, Error> {
163 if args.is_empty() {
164 return Err(Error::ArgumentError(
165 "wrong number of arguments (given 0, expected 1+)".to_string(),
166 ));
167 }
168
169 let sec = get_integer_or_float_as_i64(&args[0])?;
170 let nsec = if args.len() >= 2 {
171 get_integer_or_float_as_u32(&args[1])?
172 } else {
173 0
174 };
175
176 Ok(make_time_object(vm, RTimeData::new(sec, nsec, 0)))
177}
178
179fn mrb_time_year(vm: &mut VM, _args: &[Rc<RObject>]) -> Result<Rc<RObject>, Error> {
185 let self_obj = vm.getself()?;
186 let t = get_time_data(&self_obj)?;
187 let (year, _, _, _, _, _, _) = t.to_datetime_parts();
188 Ok(RObject::integer(year as i64).to_refcount_assigned())
189}
190
191fn mrb_time_month(vm: &mut VM, _args: &[Rc<RObject>]) -> Result<Rc<RObject>, Error> {
193 let self_obj = vm.getself()?;
194 let t = get_time_data(&self_obj)?;
195 let (_, month, _, _, _, _, _) = t.to_datetime_parts();
196 Ok(RObject::integer(month as i64).to_refcount_assigned())
197}
198
199fn mrb_time_day(vm: &mut VM, _args: &[Rc<RObject>]) -> Result<Rc<RObject>, Error> {
201 let self_obj = vm.getself()?;
202 let t = get_time_data(&self_obj)?;
203 let (_, _, day, _, _, _, _) = t.to_datetime_parts();
204 Ok(RObject::integer(day as i64).to_refcount_assigned())
205}
206
207fn mrb_time_wday(vm: &mut VM, _args: &[Rc<RObject>]) -> Result<Rc<RObject>, Error> {
209 let self_obj = vm.getself()?;
210 let t = get_time_data(&self_obj)?;
211 let (_, _, _, wday, _, _, _) = t.to_datetime_parts();
212 Ok(RObject::integer(wday as i64).to_refcount_assigned())
213}
214
215fn mrb_time_hour(vm: &mut VM, _args: &[Rc<RObject>]) -> Result<Rc<RObject>, Error> {
217 let self_obj = vm.getself()?;
218 let t = get_time_data(&self_obj)?;
219 let (_, _, _, _, hour, _, _) = t.to_datetime_parts();
220 Ok(RObject::integer(hour as i64).to_refcount_assigned())
221}
222
223fn mrb_time_min(vm: &mut VM, _args: &[Rc<RObject>]) -> Result<Rc<RObject>, Error> {
225 let self_obj = vm.getself()?;
226 let t = get_time_data(&self_obj)?;
227 let (_, _, _, _, _, min, _) = t.to_datetime_parts();
228 Ok(RObject::integer(min as i64).to_refcount_assigned())
229}
230
231fn mrb_time_sec(vm: &mut VM, _args: &[Rc<RObject>]) -> Result<Rc<RObject>, Error> {
233 let self_obj = vm.getself()?;
234 let t = get_time_data(&self_obj)?;
235 let (_, _, _, _, _, _, sec) = t.to_datetime_parts();
236 Ok(RObject::integer(sec as i64).to_refcount_assigned())
237}
238
239fn mrb_time_nsec(vm: &mut VM, _args: &[Rc<RObject>]) -> Result<Rc<RObject>, Error> {
241 let self_obj = vm.getself()?;
242 let t = get_time_data(&self_obj)?;
243 Ok(RObject::integer(t.nsec as i64).to_refcount_assigned())
244}
245
246fn mrb_time_to_s(vm: &mut VM, _args: &[Rc<RObject>]) -> Result<Rc<RObject>, Error> {
248 let self_obj = vm.getself()?;
249 let t = get_time_data(&self_obj)?;
250 Ok(RObject::string(t.to_s()).to_refcount_assigned())
251}
252
253fn mrb_time_add(vm: &mut VM, args: &[Rc<RObject>]) -> Result<Rc<RObject>, Error> {
255 if args.is_empty() {
256 return Err(Error::ArgumentError(
257 "wrong number of arguments (given 0, expected 1)".to_string(),
258 ));
259 }
260 let self_obj = vm.getself()?;
261 let t = get_time_data(&self_obj)?;
262
263 let (delta_sec, delta_nsec) = float_to_sec_nsec(&args[0])?;
264 let new_nsec = t.nsec as i64 + delta_nsec as i64;
265 let carry = new_nsec.div_euclid(1_000_000_000);
266 let new_nsec = new_nsec.rem_euclid(1_000_000_000) as u32;
267 let new_sec = t.sec + delta_sec + carry;
268
269 Ok(make_time_object(
270 vm,
271 RTimeData::new(new_sec, new_nsec, t.utc_offset),
272 ))
273}
274
275fn mrb_time_sub(vm: &mut VM, args: &[Rc<RObject>]) -> Result<Rc<RObject>, Error> {
277 if args.is_empty() {
278 return Err(Error::ArgumentError(
279 "wrong number of arguments (given 0, expected 1)".to_string(),
280 ));
281 }
282 let self_obj = vm.getself()?;
283 let t = get_time_data(&self_obj)?;
284
285 if let RValue::Data(_) = &args[0].value
287 && let Ok(rhs) = get_time_data(&args[0])
288 {
289 let sec_diff =
291 (t.sec - rhs.sec) as f64 + (t.nsec as f64 - rhs.nsec as f64) / 1_000_000_000.0;
292 return Ok(RObject::float(sec_diff).to_refcount_assigned());
293 }
294
295 let (delta_sec, delta_nsec) = float_to_sec_nsec(&args[0])?;
296 let new_nsec = t.nsec as i64 - delta_nsec as i64;
297 let carry = new_nsec.div_euclid(1_000_000_000);
298 let new_nsec = new_nsec.rem_euclid(1_000_000_000) as u32;
299 let new_sec = t.sec - delta_sec + carry;
300
301 Ok(make_time_object(
302 vm,
303 RTimeData::new(new_sec, new_nsec, t.utc_offset),
304 ))
305}
306
307fn mrb_time_cmp(vm: &mut VM, args: &[Rc<RObject>]) -> Result<Rc<RObject>, Error> {
309 if args.is_empty() {
310 return Err(Error::ArgumentError(
311 "wrong number of arguments (given 0, expected 1)".to_string(),
312 ));
313 }
314 let self_obj = vm.getself()?;
315 let t = get_time_data(&self_obj)?;
316
317 let rhs = match get_time_data(&args[0]) {
318 Ok(r) => r,
319 Err(_) => return Ok(RObject::nil().to_refcount_assigned()),
320 };
321
322 let result = match t.sec.cmp(&rhs.sec) {
323 std::cmp::Ordering::Equal => t.nsec.cmp(&rhs.nsec),
324 other => other,
325 };
326
327 let int_val = match result {
328 std::cmp::Ordering::Less => -1i64,
329 std::cmp::Ordering::Equal => 0,
330 std::cmp::Ordering::Greater => 1,
331 };
332 Ok(RObject::integer(int_val).to_refcount_assigned())
333}
334
335fn mrb_time_utc_offset(vm: &mut VM, _args: &[Rc<RObject>]) -> Result<Rc<RObject>, Error> {
337 let self_obj = vm.getself()?;
338 let t = get_time_data(&self_obj)?;
339 Ok(RObject::integer(t.utc_offset as i64).to_refcount_assigned())
340}
341
342fn mrb_time_localtime(vm: &mut VM, args: &[Rc<RObject>]) -> Result<Rc<RObject>, Error> {
344 let self_obj = vm.getself()?;
345 let t = get_time_data(&self_obj)?;
346
347 let new_offset = if args.is_empty() {
348 0i32 } else {
350 get_integer_or_float_as_i64(&args[0])? as i32
351 };
352
353 Ok(make_time_object(
354 vm,
355 RTimeData::new(t.sec, t.nsec, new_offset),
356 ))
357}
358
359fn mrb_time_to_i(vm: &mut VM, _args: &[Rc<RObject>]) -> Result<Rc<RObject>, Error> {
361 let self_obj = vm.getself()?;
362 let t = get_time_data(&self_obj)?;
363 Ok(RObject::integer(t.sec).to_refcount_assigned())
364}
365
366fn mrb_time_to_f(vm: &mut VM, _args: &[Rc<RObject>]) -> Result<Rc<RObject>, Error> {
368 let self_obj = vm.getself()?;
369 let t = get_time_data(&self_obj)?;
370 let f = t.sec as f64 + t.nsec as f64 / 1_000_000_000.0;
371 Ok(RObject::float(f).to_refcount_assigned())
372}
373
374#[cfg(any(not(target_arch = "wasm32"), target_os = "wasi"))]
383fn mrb_time_source_default(_vm: &mut VM, _args: &[Rc<RObject>]) -> Result<Rc<RObject>, Error> {
384 use std::time::{SystemTime, UNIX_EPOCH};
385 let now = SystemTime::now();
386 let unixtime = now.duration_since(UNIX_EPOCH).map_err(|_| {
387 Error::RuntimeError(
388 "system time before UNIX EPOCH -- are you running this from the past?".to_string(),
389 )
390 })?;
391 let sec = unixtime.as_secs() as i64;
392 let nsec = unixtime.subsec_nanos() as i64;
393 let utc_offset = local_utc_offset_secs();
394 let arr = vec![
395 RObject::integer(sec).to_refcount_assigned(),
396 RObject::integer(nsec).to_refcount_assigned(),
397 RObject::integer(utc_offset as i64).to_refcount_assigned(),
398 ];
399 Ok(RObject::array(arr).to_refcount_assigned())
400}
401
402#[cfg(any(not(target_arch = "wasm32"), target_os = "wasi"))]
405fn local_utc_offset_secs() -> i32 {
406 use std::time::{SystemTime, UNIX_EPOCH};
407 let t = SystemTime::now()
408 .duration_since(UNIX_EPOCH)
409 .map(|d| d.as_secs() as libc::time_t)
410 .unwrap_or(0);
411 unsafe {
412 let mut tm: libc::tm = std::mem::zeroed();
413 libc::localtime_r(&t, &mut tm);
414 tm.tm_gmtoff as i32
415 }
416}
417
418fn get_integer_or_float_as_i64(obj: &RObject) -> Result<i64, Error> {
423 match &obj.value {
424 RValue::Integer(i) => Ok(*i),
425 RValue::Float(f) => Ok(*f as i64),
426 _ => Err(Error::ArgumentError(
427 "expected Integer or Float".to_string(),
428 )),
429 }
430}
431
432fn get_integer_or_float_as_u32(obj: &RObject) -> Result<u32, Error> {
433 match &obj.value {
434 RValue::Integer(i) => {
435 if *i < 0 {
436 return Err(Error::ArgumentError(
437 "nsec must be non-negative".to_string(),
438 ));
439 }
440 Ok(*i as u32)
441 }
442 RValue::Float(f) => {
443 if *f < 0.0 {
444 return Err(Error::ArgumentError(
445 "nsec must be non-negative".to_string(),
446 ));
447 }
448 Ok(*f as u32)
449 }
450 _ => Err(Error::ArgumentError(
451 "expected Integer or Float".to_string(),
452 )),
453 }
454}
455
456fn float_to_sec_nsec(obj: &RObject) -> Result<(i64, u32), Error> {
458 match &obj.value {
459 RValue::Integer(i) => Ok((*i, 0)),
460 RValue::Float(f) => {
461 let sec = f.trunc() as i64;
462 let nsec = (f.fract().abs() * 1_000_000_000.0).round() as u32;
463 Ok((sec, nsec))
464 }
465 _ => Err(Error::ArgumentError(
466 "expected Integer or Float".to_string(),
467 )),
468 }
469}
470
471pub fn init_time(vm: &mut VM) {
478 let time_class = vm.define_class("Time", None, None);
479
480 mrb_define_class_cmethod(vm, time_class.clone(), "now", Box::new(mrb_time_now));
482 mrb_define_class_cmethod(vm, time_class.clone(), "at", Box::new(mrb_time_at));
483
484 mrb_define_cmethod(vm, time_class.clone(), "year", Box::new(mrb_time_year));
486 mrb_define_cmethod(vm, time_class.clone(), "month", Box::new(mrb_time_month));
487 mrb_define_cmethod(vm, time_class.clone(), "mon", Box::new(mrb_time_month));
488 mrb_define_cmethod(vm, time_class.clone(), "day", Box::new(mrb_time_day));
489 mrb_define_cmethod(vm, time_class.clone(), "mday", Box::new(mrb_time_day));
490 mrb_define_cmethod(vm, time_class.clone(), "wday", Box::new(mrb_time_wday));
491 mrb_define_cmethod(vm, time_class.clone(), "hour", Box::new(mrb_time_hour));
492 mrb_define_cmethod(vm, time_class.clone(), "min", Box::new(mrb_time_min));
493 mrb_define_cmethod(vm, time_class.clone(), "sec", Box::new(mrb_time_sec));
494 mrb_define_cmethod(vm, time_class.clone(), "nsec", Box::new(mrb_time_nsec));
495 mrb_define_cmethod(vm, time_class.clone(), "to_s", Box::new(mrb_time_to_s));
496 mrb_define_cmethod(vm, time_class.clone(), "inspect", Box::new(mrb_time_to_s));
497 mrb_define_cmethod(vm, time_class.clone(), "+", Box::new(mrb_time_add));
498 mrb_define_cmethod(vm, time_class.clone(), "-", Box::new(mrb_time_sub));
499 mrb_define_cmethod(vm, time_class.clone(), "<=>", Box::new(mrb_time_cmp));
500 mrb_define_cmethod(
501 vm,
502 time_class.clone(),
503 "utc_offset",
504 Box::new(mrb_time_utc_offset),
505 );
506 mrb_define_cmethod(
507 vm,
508 time_class.clone(),
509 "gmt_offset",
510 Box::new(mrb_time_utc_offset),
511 );
512 mrb_define_cmethod(
513 vm,
514 time_class.clone(),
515 "localtime",
516 Box::new(mrb_time_localtime),
517 );
518 mrb_define_cmethod(vm, time_class.clone(), "to_i", Box::new(mrb_time_to_i));
519 mrb_define_cmethod(vm, time_class.clone(), "to_f", Box::new(mrb_time_to_f));
520
521 #[cfg(any(not(target_arch = "wasm32"), target_os = "wasi"))]
523 {
524 let _time_class_obj = RObject::class(time_class, vm);
525 let time_class_obj_for_source = vm
526 .get_const_by_name("Time")
527 .expect("Time class not found after definition");
528 mrb_define_class_cmethod_on_obj(
529 vm,
530 time_class_obj_for_source,
531 "__source",
532 Box::new(mrb_time_source_default),
533 );
534 }
535}
536
537#[cfg(any(not(target_arch = "wasm32"), target_os = "wasi"))]
539fn mrb_define_class_cmethod_on_obj(
540 vm: &mut VM,
541 class_obj: Rc<RObject>,
542 name: &str,
543 cmethod: mrubyedge::yamrb::value::RFn,
544) {
545 use mrubyedge::yamrb::helpers::mrb_define_singleton_cmethod;
546 mrb_define_singleton_cmethod(vm, class_obj, name, cmethod);
547}