1mod calendar;
12mod date;
13mod time;
14mod timezone;
15
16pub use ::time::{Month, Weekday};
17
18pub use calendar::{CalendarMonth, CalendarWeek};
19pub use date::Date;
20pub use time::Time;
21pub use timezone::Timezone;
22
23const MILLISECONDS_PER_SECOND: i64 = 1000;
24const MILLISECONDS_PER_MINUTE: i64 = MILLISECONDS_PER_SECOND * 60;
25const MILLISECONDS_PER_HOUR: i64 = MILLISECONDS_PER_MINUTE * 60;
26const MILLISECONDS_PER_DAY: i64 = MILLISECONDS_PER_HOUR * 24;
27const DAYS_FROM_ZERO_TO_UNIX: i64 = 719528;
28
29pub type Result<T> = std::result::Result<T, Error>;
30
31#[derive(Debug, Clone, Copy, PartialEq, Eq)]
32pub enum Error {
33 OutOfRange,
34}
35
36#[derive(Debug, Clone, Copy, PartialEq, Eq)]
37pub struct DateTime(::time::UtcDateTime, Timezone);
38
39impl DateTime {
40 pub fn new(date: Date, time: Time) -> Self {
41 let Date(date) = date;
42 let Time(time, timezone) = time;
43 let dt = ::time::UtcDateTime::new(date, time);
44 Self(dt, timezone)
45 }
46
47 pub const fn from_unix_timestamp(timestamp: i64, timezone: Timezone) -> Result<Self> {
48 let timestamp = timestamp + timezone.utc_offset_millis();
49 let Ok(utc) = ::time::UtcDateTime::from_unix_timestamp_nanos(timestamp as i128 * 1_000_000)
50 else {
51 return Err(Error::OutOfRange);
52 };
53 Ok(Self(utc, timezone))
54 }
55
56 pub const fn to_timezone(self, timezone: Timezone) -> Result<Self> {
57 let timestamp = self.unix_timestamp() - self.1.utc_offset_millis();
58 Self::from_unix_timestamp(timestamp, timezone)
59 }
60
61 pub fn now(timezone: Timezone) -> Result<Self> {
62 #[cfg(target_arch = "wasm32")]
63 let timestamp = js_sys::Date::now() as i64;
64
65 #[cfg(not(target_arch = "wasm32"))]
66 let timestamp = std::time::SystemTime::now()
67 .duration_since(std::time::UNIX_EPOCH)
68 .expect("Time went backwards")
69 .as_millis() as i64;
70
71 Self::from_unix_timestamp(timestamp, timezone)
72 }
73
74 pub const fn into_parts(self) -> (Date, Time) {
75 (Date(self.0.date()), Time(self.0.time(), self.1))
76 }
77
78 pub const fn hour(&self) -> u8 {
79 self.0.hour()
80 }
81
82 pub const fn minute(&self) -> u8 {
83 self.0.minute()
84 }
85
86 pub const fn second(&self) -> u8 {
87 self.0.second()
88 }
89
90 pub const fn millisecond(&self) -> u16 {
91 self.0.millisecond()
92 }
93
94 pub const fn timezone(&self) -> Timezone {
95 self.1
96 }
97
98 pub const fn year(&self) -> i32 {
99 self.0.year()
100 }
101
102 pub const fn month(&self) -> Month {
103 self.0.month()
104 }
105
106 pub const fn day(&self) -> u8 {
107 self.0.day()
108 }
109
110 pub const fn weekday(&self) -> Weekday {
111 self.0.weekday()
112 }
113
114 pub const fn unix_timestamp(&self) -> i64 {
115 (self.0.unix_timestamp_nanos() / 1_000_000) as i64
116 }
117}
118
119#[cfg(test)]
120mod tests {
121 use std::i64;
122
123 use super::*;
124
125 #[test]
126 fn test_date_time() {
127 let timestamp = 1672531200000;
128 let dt = DateTime::from_unix_timestamp(timestamp, Timezone::UTC).unwrap();
129 let expected = DateTime::new(
130 Date::new(1, Month::January, 2023).unwrap(),
131 Time::new(0, 0, 0, 0, Timezone::UTC).unwrap(),
132 );
133 assert_eq!(dt, expected);
134
135 let dt = dt.to_timezone(Timezone::CET).unwrap();
136 let expected = DateTime::new(
137 Date::new(1, Month::January, 2023).unwrap(),
138 Time::new(1, 0, 0, 0, Timezone::CET).unwrap(),
139 );
140 assert_eq!(dt, expected);
141
142 let dt = dt.to_timezone(Timezone::HST).unwrap();
143 let expected = DateTime::new(
144 Date::new(31, Month::December, 2022).unwrap(),
145 Time::new(14, 0, 0, 0, Timezone::HST).unwrap(),
146 );
147 assert_eq!(dt, expected);
148
149 let timestamp = 0;
150 let dt = DateTime::from_unix_timestamp(timestamp, Timezone::UTC).unwrap();
151 let expected = DateTime::new(
152 Date::new(1, Month::January, 1970).unwrap(),
153 Time::new(0, 0, 0, 0, Timezone::UTC).unwrap(),
154 );
155 assert_eq!(dt, expected);
156
157 let timestamp = -3 * MILLISECONDS_PER_HOUR;
158 let dt = DateTime::from_unix_timestamp(timestamp, Timezone::CET).unwrap();
159 let expected = DateTime::new(
160 Date::new(31, Month::December, 1969).unwrap(),
161 Time::new(22, 0, 0, 0, Timezone::CET).unwrap(),
162 );
163 assert_eq!(dt, expected);
164
165 let timestamp = -25567 * MILLISECONDS_PER_DAY;
166 let dt = DateTime::from_unix_timestamp(timestamp, Timezone::UTC).unwrap();
167 let expected = DateTime::new(
168 Date::new(1, Month::January, 1900).unwrap(),
169 Time::new(0, 0, 0, 0, Timezone::UTC).unwrap(),
170 );
171 assert_eq!(dt, expected);
172
173 let now = std::time::SystemTime::now()
174 .duration_since(std::time::UNIX_EPOCH)
175 .expect("Time went backwards")
176 .as_secs() as i64;
177 let dt = DateTime::from_unix_timestamp(now, Timezone::UTC).unwrap();
178 let timestamp = dt.unix_timestamp();
179 assert_eq!(timestamp, now);
180 let dt2 = DateTime::new(
181 Date::new(dt.day(), dt.month(), dt.year()).unwrap(),
182 Time::new(
183 dt.hour(),
184 dt.minute(),
185 dt.second(),
186 dt.millisecond(),
187 dt.timezone(),
188 )
189 .unwrap(),
190 );
191 assert_eq!(dt, dt2);
192 }
193
194 #[test]
195 fn test_date_time_big_numbers() {
196 let timestamp = 99_998_287_287_821;
197 let dt = DateTime::from_unix_timestamp(timestamp, Timezone::UTC).unwrap();
198 let expected = DateTime::new(
199 Date::new(27, Month::October, 5138).unwrap(),
200 Time::new(14, 1, 27, 821, Timezone::UTC).unwrap(),
201 );
202 assert_eq!(dt, expected);
203
204 let timestamp = -99_998_287_287_821;
205 let dt = DateTime::from_unix_timestamp(timestamp, Timezone::UTC).unwrap();
206 let expected = DateTime::new(
207 Date::new(7, Month::March, -1199).unwrap(),
208 Time::new(9, 58, 32, 179, Timezone::UTC).unwrap(),
209 );
210 assert_eq!(dt, expected);
211 }
212}