ion/object/
date.rs

1/*
2 * This Source Code Form is subject to the terms of the Mozilla Public
3 * License, v. 2.0. If a copy of the MPL was not distributed with this
4 * file, You can obtain one at http://mozilla.org/MPL/2.0/.
5 */
6
7use 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/// Represents a `Date` in the JavaScript Runtime.
17/// Refer to [MDN](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Date) for more details.
18#[derive(Debug)]
19pub struct Date<'d> {
20	date: Local<'d, *mut JSObject>,
21}
22
23impl<'d> Date<'d> {
24	/// Creates a new [Date] with the current time.
25	pub fn new(cx: &'d Context) -> Date<'d> {
26		Date::from_date(cx, Utc::now())
27	}
28
29	/// Creates a new [Date] with the given time.
30	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	/// Creates a [Date] from an object.
36	/// Returns [None] if it is not a [Date].
37	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	/// Creates a [Date] from an object.
42	///
43	/// ### Safety
44	/// Object must be a [Date].
45	pub unsafe fn from_unchecked(object: Local<'d, *mut JSObject>) -> Date<'d> {
46		Date { date: object }
47	}
48
49	/// Checks if the [Date] is a valid date.
50	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	/// Converts the [Date] to a [DateTime].
56	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	/// Checks if a [raw object](*mut JSObject) is a date.
68	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	/// Checks if an object is a date.
75	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; // 01 January 1970
103	const POST_EPOCH: i64 = 1615766400; // 15 March 2021
104	const PRE_EPOCH: i64 = -1615766400; // 20 October 1918
105
106	#[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}