1use std::ops::{Deref, DerefMut};
8
9use chrono::offset::Utc;
10use chrono::{DateTime, TimeZone as _};
11use mozjs::jsapi::{ClippedTime, DateGetMsecSinceEpoch, DateIsValid, JSObject, NewDateObject, ObjectIsDate};
12use mozjs::rooted;
13
14use crate::{Context, Local};
15
16#[derive(Debug)]
19pub struct Date<'d> {
20 date: Local<'d, *mut JSObject>,
21}
22
23impl<'d> Date<'d> {
24 pub fn new(cx: &'d Context) -> Date<'d> {
26 Date::from_date(cx, Utc::now())
27 }
28
29 pub fn from_date(cx: &'d Context, time: DateTime<Utc>) -> Date<'d> {
31 let date = unsafe { NewDateObject(cx.as_ptr(), ClippedTime { t: time.timestamp_millis() as f64 }) };
32 Date { date: cx.root(date) }
33 }
34
35 pub fn from(cx: &Context, object: Local<'d, *mut JSObject>) -> Option<Date<'d>> {
38 Date::is_date(cx, &object).then_some(Date { date: object })
39 }
40
41 pub unsafe fn from_unchecked(object: Local<'d, *mut JSObject>) -> Date<'d> {
46 Date { date: object }
47 }
48
49 pub fn is_valid(&self, cx: &Context) -> bool {
51 let mut is_valid = true;
52 (unsafe { DateIsValid(cx.as_ptr(), self.date.handle().into(), &raw mut is_valid) }) && is_valid
53 }
54
55 pub fn to_date(&self, cx: &Context) -> Option<DateTime<Utc>> {
57 let mut milliseconds = f64::MAX;
58 if !unsafe { DateGetMsecSinceEpoch(cx.as_ptr(), self.date.handle().into(), &raw mut milliseconds) }
59 || milliseconds == f64::MAX
60 {
61 None
62 } else {
63 Utc.timestamp_millis_opt(milliseconds as i64).single()
64 }
65 }
66
67 pub fn is_date_raw(cx: &Context, object: *mut JSObject) -> bool {
69 rooted!(in(cx.as_ptr()) let object = object);
70 let mut is_date = false;
71 (unsafe { ObjectIsDate(cx.as_ptr(), object.handle().into(), &raw mut is_date) }) && is_date
72 }
73
74 pub fn is_date(cx: &Context, object: &Local<*mut JSObject>) -> bool {
76 let mut is_date = false;
77 (unsafe { ObjectIsDate(cx.as_ptr(), object.handle().into(), &raw mut is_date) }) && is_date
78 }
79}
80
81impl<'d> Deref for Date<'d> {
82 type Target = Local<'d, *mut JSObject>;
83
84 fn deref(&self) -> &Self::Target {
85 &self.date
86 }
87}
88
89impl DerefMut for Date<'_> {
90 fn deref_mut(&mut self) -> &mut Self::Target {
91 &mut self.date
92 }
93}
94
95#[cfg(test)]
96mod tests {
97 use chrono::{TimeZone as _, Utc};
98
99 use crate::Date;
100 use crate::utils::test::TestRuntime;
101
102 const EPOCH: i64 = 0; const POST_EPOCH: i64 = 1615766400; const PRE_EPOCH: i64 = -1615766400; #[test]
107 fn date() {
108 let rt = TestRuntime::new();
109 let cx = &rt.cx;
110
111 let epoch = Date::from_date(cx, Utc.timestamp_millis_opt(EPOCH).unwrap());
112 let post_epoch = Date::from_date(cx, Utc.timestamp_millis_opt(POST_EPOCH).unwrap());
113 let pre_epoch = Date::from_date(cx, Utc.timestamp_millis_opt(PRE_EPOCH).unwrap());
114
115 assert!(epoch.is_valid(cx));
116 assert!(post_epoch.is_valid(cx));
117 assert!(pre_epoch.is_valid(cx));
118
119 assert_eq!(Some(Utc.timestamp_millis_opt(EPOCH).unwrap()), epoch.to_date(cx));
120 assert_eq!(
121 Some(Utc.timestamp_millis_opt(POST_EPOCH).unwrap()),
122 post_epoch.to_date(cx)
123 );
124 assert_eq!(
125 Some(Utc.timestamp_millis_opt(PRE_EPOCH).unwrap()),
126 pre_epoch.to_date(cx)
127 );
128 }
129}