1use crate::datetime::DateTimeComponents;
6use crate::format::*;
7use crate::{EpochDays, ParseError, ParseResult};
8use std::fmt::{Debug, Display, Formatter};
9use std::str::FromStr;
10
11const OFFSET_BITS: u32 = 12;
12const MILLI_BITS: u32 = 10;
13const SECOND_BITS: u32 = 6;
14const MINUTE_BITS: u32 = 6;
15const HOUR_BITS: u32 = 5;
16const DAY_BITS: u32 = 5;
17const MONTH_BITS: u32 = 4;
18const YEAR_BITS: u32 = 64 - (MONTH_BITS + DAY_BITS + HOUR_BITS + MINUTE_BITS + SECOND_BITS + MILLI_BITS + OFFSET_BITS);
19
20const MIN_YEAR_INTERNAL: i32 = -(1 << (YEAR_BITS - 1));
21const MAX_YEAR_INTERNAL: i32 = (1 << (YEAR_BITS - 1)) - 1;
22const MIN_YEAR: i32 = -9999;
23const MAX_YEAR: i32 = 9999;
24
25const MIN_OFFSET_MINUTES_INTERNAL: i32 = -(1 << (OFFSET_BITS - 1));
26const MAX_OFFSET_MINUTES_INTERNAL: i32 = (1 << (OFFSET_BITS - 1)) - 1;
27
28const MAX_OFFSET_HOURS: i32 = 18;
29const MIN_OFFSET_HOURS: i32 = -18;
30const MIN_OFFSET_MINUTES: i32 = MIN_OFFSET_HOURS * 60;
31const MAX_OFFSET_MINUTES: i32 = MAX_OFFSET_HOURS * 60;
32
33#[allow(clippy::assertions_on_constants)]
34const _: () = {
35 assert!(MIN_YEAR_INTERNAL < MIN_YEAR || MAX_YEAR_INTERNAL > MAX_YEAR);
36 assert!(MIN_OFFSET_MINUTES_INTERNAL < MIN_OFFSET_MINUTES || MAX_OFFSET_MINUTES_INTERNAL > MAX_OFFSET_MINUTES);
37};
38
39#[derive(PartialEq, Clone, Copy, Ord, PartialOrd, Eq)]
40#[repr(transparent)]
41pub struct PackedTimestamp {
42 value: u64,
43}
44
45impl PackedTimestamp {
46 #[inline]
47 #[allow(clippy::too_many_arguments)]
48 pub fn new_utc(year: i32, month: u32, day: u32, hour: u32, minute: u32, second: u32, milli: u32) -> Self {
49 Self::new(year, month, day, hour, minute, second, milli, 0)
50 }
51
52 #[inline]
53 pub fn new_ymd_utc(year: i32, month: u32, day: u32) -> Self {
54 Self::new(year, month, day, 0, 0, 0, 0, 0)
55 }
56
57 #[inline]
58 #[allow(clippy::too_many_arguments)]
59 pub fn new(year: i32, month: u32, day: u32, hour: u32, minute: u32, second: u32, milli: u32, offset_minutes: i32) -> Self {
60 let value = ((((((((year as u64) << MONTH_BITS | month as u64) << DAY_BITS | day as u64) << HOUR_BITS | hour as u64)
61 << MINUTE_BITS
62 | minute as u64)
63 << SECOND_BITS
64 | second as u64)
65 << MILLI_BITS
66 | milli as u64)
67 << OFFSET_BITS)
68 | (offset_minutes & ((1 << OFFSET_BITS) - 1)) as u64;
69 Self { value }
70 }
71
72 #[inline]
73 pub fn from_value(value: u64) -> Self {
74 Self { value }
75 }
76
77 #[inline]
78 pub fn value(&self) -> u64 {
79 self.value
80 }
81
82 #[inline]
83 pub fn from_timestamp_millis(ts: i64) -> Self {
84 let components = DateTimeComponents::from_timestamp_millis(ts);
85
86 Self::new_utc(
87 components.year,
88 components.month as _,
89 components.day as _,
90 components.hour as _,
91 components.minute as _,
92 components.second as _,
93 components.millisecond,
94 )
95 }
96
97 #[inline]
98 pub fn to_timestamp_millis(&self) -> i64 {
99 let date_part = EpochDays::from_ymd(self.year() as i32, self.month() as i32, self.day() as i32).to_timestamp_millis();
100
101 let h = self.hour() as i64;
102 let m = self.minute() as i64;
103 let s = self.second() as i64;
104 let o = self.offset_minutes() as i64;
105 let seconds = h * 60 * 60 + m * 60 + s - o * 60;
106 let millis = self.millisecond() as i64;
107
108 let time_part = seconds * 1000 + millis;
109
110 date_part + time_part
111 }
112
113 pub fn from_rfc3339_bytes(input: &[u8]) -> ParseResult<Self> {
114 #[cfg(all(not(miri), target_feature = "sse4.1"))]
115 {
116 let ts = crate::parse::parse_simd(input)?;
117 Ok(ts.to_packed())
118 }
119 #[cfg(not(all(not(miri), target_feature = "sse4.1")))]
120 {
121 let ts = crate::parse::parse_scalar(input)?;
122 Ok(ts.to_packed())
123 }
124 }
125
126 pub fn from_rfc3339_str(input: &str) -> ParseResult<Self> {
127 Self::from_rfc3339_bytes(input.as_bytes())
128 }
129
130 #[inline]
131 pub fn year(&self) -> u32 {
132 (self.value >> (MONTH_BITS + DAY_BITS + HOUR_BITS + MINUTE_BITS + SECOND_BITS + MILLI_BITS + OFFSET_BITS)) as u32
133 }
134
135 #[inline]
136 pub fn month(&self) -> u32 {
137 ((self.value >> (DAY_BITS + HOUR_BITS + MINUTE_BITS + SECOND_BITS + MILLI_BITS + OFFSET_BITS)) & ((1 << MONTH_BITS) - 1))
138 as u32
139 }
140
141 #[inline]
142 pub fn day(&self) -> u32 {
143 ((self.value >> (HOUR_BITS + MINUTE_BITS + SECOND_BITS + MILLI_BITS + OFFSET_BITS)) & ((1 << DAY_BITS) - 1)) as u32
144 }
145
146 #[inline]
147 pub fn hour(&self) -> u32 {
148 ((self.value >> (MINUTE_BITS + SECOND_BITS + MILLI_BITS + OFFSET_BITS)) & ((1 << HOUR_BITS) - 1)) as u32
149 }
150
151 #[inline]
152 pub fn minute(&self) -> u32 {
153 ((self.value >> (SECOND_BITS + MILLI_BITS + OFFSET_BITS)) & ((1 << MINUTE_BITS) - 1)) as u32
154 }
155
156 #[inline]
157 pub fn second(&self) -> u32 {
158 ((self.value >> (MILLI_BITS + OFFSET_BITS)) & ((1 << SECOND_BITS) - 1)) as u32
159 }
160
161 #[inline]
162 pub fn millisecond(&self) -> u32 {
163 ((self.value >> (OFFSET_BITS)) & ((1 << MILLI_BITS) - 1)) as u32
164 }
165
166 #[inline]
167 pub fn offset_minutes(&self) -> i32 {
168 let bits = (self.value & ((1 << OFFSET_BITS) - 1)) as i32;
169 bits << (32 - OFFSET_BITS) >> (32 - OFFSET_BITS)
171 }
172
173 #[inline]
174 pub fn write_rfc3339_bytes<W: std::io::Write>(&self, mut writer: W) -> std::io::Result<()> {
175 let buffer = self.to_rfc3339_bytes();
176 writer.write_all(&buffer)
177 }
178
179 #[inline]
180 pub fn write_rfc3339_str<W: std::fmt::Write>(&self, mut writer: W) -> std::fmt::Result {
181 let buffer = self.to_rfc3339_bytes();
182 #[cfg(not(debug_assertions))]
183 {
184 writer.write_str(unsafe { std::str::from_utf8_unchecked(&buffer) })
185 }
186 #[cfg(debug_assertions)]
187 {
188 writer.write_str(std::str::from_utf8(&buffer).expect("utf8 string"))
189 }
190 }
191
192 #[inline]
193 pub fn to_rfc3339_bytes(&self) -> [u8; 24] {
194 format_to_rfc3339_utc_bytes(
195 self.year(),
196 self.month(),
197 self.day(),
198 self.hour(),
199 self.minute(),
200 self.second(),
201 self.millisecond(),
202 )
203 }
204
205 #[inline]
206 pub fn to_rfc3339_string(&self) -> String {
207 let buffer = self.to_rfc3339_bytes();
208 #[cfg(not(debug_assertions))]
209 {
210 unsafe { std::str::from_utf8_unchecked(&buffer).to_string() }
211 }
212 #[cfg(debug_assertions)]
213 {
214 std::str::from_utf8(&buffer).expect("utf8 string").to_string()
215 }
216 }
217}
218
219impl Display for PackedTimestamp {
220 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
221 self.write_rfc3339_str(f)
222 }
223}
224
225impl Debug for PackedTimestamp {
226 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
227 #[cfg(not(debug_assertions))]
228 {
229 self.write_rfc3339_str(f)
230 }
231 #[cfg(debug_assertions)]
232 {
233 f.write_fmt(format_args!(
234 "{:04}-{:02}-{:02}T{:02}:{:02}:{:02}.{:03}Z",
235 self.year(),
236 self.month(),
237 self.day(),
238 self.hour(),
239 self.minute(),
240 self.second(),
241 self.millisecond()
242 ))
243 }
244 }
245}
246
247impl From<EpochDays> for PackedTimestamp {
248 fn from(epoch_days: EpochDays) -> Self {
249 let (year, month, day) = epoch_days.to_ymd();
250 PackedTimestamp::new_ymd_utc(year, month as _, day as _)
251 }
252}
253
254impl TryFrom<&str> for PackedTimestamp {
255 type Error = ParseError;
256
257 fn try_from(s: &str) -> Result<Self, Self::Error> {
258 PackedTimestamp::from_rfc3339_str(s)
259 }
260}
261
262impl FromStr for PackedTimestamp {
263 type Err = ParseError;
264
265 fn from_str(s: &str) -> Result<Self, Self::Err> {
266 PackedTimestamp::from_rfc3339_str(s)
267 }
268}
269
270#[cfg(test)]
271pub mod tests {
272 use crate::{PackedTimestamp, ParseError};
273
274 #[test]
275 fn test_format() {
276 assert_eq!(
277 PackedTimestamp::new_utc(2022, 8, 21, 17, 30, 15, 0).to_rfc3339_string(),
278 "2022-08-21T17:30:15.000Z".to_owned()
279 );
280 assert_eq!(
281 PackedTimestamp::new_utc(2022, 8, 21, 17, 30, 15, 100).to_rfc3339_string(),
282 "2022-08-21T17:30:15.100Z".to_owned()
283 );
284 assert_eq!(
285 PackedTimestamp::new_utc(2022, 8, 21, 17, 30, 15, 123).to_rfc3339_string(),
286 "2022-08-21T17:30:15.123Z".to_owned()
287 );
288 assert_eq!(
289 PackedTimestamp::new_utc(2022, 8, 21, 17, 30, 15, 250).to_rfc3339_string(),
290 "2022-08-21T17:30:15.250Z".to_owned()
291 );
292 }
293
294 #[test]
295 fn test_parse() {
296 assert_eq!(
297 "2022-08-21T17:30:15.250Z".parse(),
298 Ok(PackedTimestamp::new_utc(2022, 8, 21, 17, 30, 15, 250))
299 );
300 assert_eq!(
301 "2022-08-21T17:30:15.25Z".parse(),
302 Ok(PackedTimestamp::new_utc(2022, 8, 21, 17, 30, 15, 250))
303 );
304 assert_eq!(
305 "2022-08-21 17:30:15.1Z".parse(),
306 Ok(PackedTimestamp::new_utc(2022, 8, 21, 17, 30, 15, 100))
307 );
308 assert_eq!(
309 "2022-08-21 17:30:15Z".parse(),
310 Ok(PackedTimestamp::new_utc(2022, 8, 21, 17, 30, 15, 0))
311 );
312 assert_eq!(
313 "2022-08-21T17:30:15.250+02:00".parse(),
314 Ok(PackedTimestamp::new(2022, 8, 21, 17, 30, 15, 250, 120))
315 );
316 assert_eq!(
317 "2022-08-21T17:30:15.250-02:00".parse(),
318 Ok(PackedTimestamp::new(2022, 8, 21, 17, 30, 15, 250, -120))
319 );
320 }
321
322 #[test]
323 fn test_offset_minutes() {
324 assert_eq!(
325 120,
326 PackedTimestamp::new(2022, 8, 21, 17, 30, 15, 250, 120).offset_minutes()
327 );
328 assert_eq!(
329 -120,
330 PackedTimestamp::new(2022, 8, 21, 17, 30, 15, 250, -120).offset_minutes()
331 );
332 }
333
334 #[test]
335 fn test_parse_error() {
336 assert_eq!(
337 PackedTimestamp::try_from("2022-08-21 FOO"),
338 Err(ParseError::InvalidLen(14))
339 );
340 assert_eq!(
341 PackedTimestamp::try_from("2022-08-21 00:00"),
342 Err(ParseError::InvalidLen(16))
343 );
344 assert_eq!(
345 PackedTimestamp::try_from("2022-08-21 XX:YY::ZZZ"),
346 Err(ParseError::InvalidChar(11))
347 );
348 }
349
350 #[test]
351 fn test_packed() {
352 let ts = PackedTimestamp::new_utc(2020, 9, 10, 17, 30, 15, 123);
353 assert_eq!(2020, ts.year());
354 assert_eq!(9, ts.month());
355 assert_eq!(10, ts.day());
356 assert_eq!(17, ts.hour());
357 assert_eq!(30, ts.minute());
358 assert_eq!(15, ts.second());
359 assert_eq!(123, ts.millisecond());
360 }
361
362 #[test]
363 fn test_from_timestamp_millis() {
364 assert_eq!(
365 PackedTimestamp::from_timestamp_millis(0),
366 PackedTimestamp::new_utc(1970, 1, 1, 0, 0, 0, 0)
367 );
368
369 assert_eq!(
370 PackedTimestamp::from_timestamp_millis(1000),
371 PackedTimestamp::new_utc(1970, 1, 1, 0, 0, 1, 0)
372 );
373
374 assert_eq!(
375 PackedTimestamp::from_timestamp_millis(24 * 60 * 60 * 1000),
376 PackedTimestamp::new_utc(1970, 1, 2, 0, 0, 0, 0)
377 );
378
379 assert_eq!(
380 PackedTimestamp::from_timestamp_millis(-1),
381 PackedTimestamp::new_utc(1969, 12, 31, 23, 59, 59, 999)
382 );
383
384 assert_eq!(
385 PackedTimestamp::from_timestamp_millis(-1000),
386 PackedTimestamp::new_utc(1969, 12, 31, 23, 59, 59, 0)
387 );
388
389 assert_eq!(
390 PackedTimestamp::from_timestamp_millis(-24 * 60 * 60 * 1000),
391 PackedTimestamp::new_utc(1969, 12, 31, 0, 0, 0, 0)
392 );
393 }
394
395 #[test]
396 fn test_to_timestamp_millis() {
397 assert_eq!(
398 PackedTimestamp::new_utc(1970, 1, 1, 0, 0, 0, 0).to_timestamp_millis(),
399 0
400 );
401 assert_eq!(
402 PackedTimestamp::new_utc(2023, 7, 3, 22, 55, 30, 123).to_timestamp_millis(),
403 1688424930123
404 );
405
406 assert_eq!(
407 PackedTimestamp::new(2023, 7, 3, 22, 55, 30, 123, 120).to_timestamp_millis(),
408 1688417730123
409 );
410 assert_eq!(
411 PackedTimestamp::new(2023, 7, 3, 22, 55, 30, 123, -120).to_timestamp_millis(),
412 1688432130123
413 );
414 }
415}