packedtime_rs/
packed.rs

1//   MMMMdddddhhhhhmmmmmmssssss
2// MMMMdddddhhhhhmmmmmmssssss00
3// 3210765432107654321076543210
4
5use 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        // offset is the only field that can be negative and needs sign extension
170        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}