1use arrayvec::ArrayVec;
2
3#[derive(Debug, Clone, Copy, PartialEq, Eq)]
5pub enum TimestampFormat {
6 Milliseconds,
8
9 Seconds,
11
12 Microseconds,
14
15 Nanoseconds,
17
18 Iso8601,
20
21 TimeSmartstring,
23}
24
25#[inline(always)]
28#[must_use]
29pub const fn exchange_time_to_nanos(exchange_time: u64, exchange_format: TimestampFormat) -> u64 {
30 match exchange_format {
32 TimestampFormat::Milliseconds => exchange_time * 1_000_000,
33 TimestampFormat::Seconds => exchange_time * 1_000_000_000,
34 TimestampFormat::Microseconds => exchange_time * 1_000,
35 TimestampFormat::Nanoseconds => exchange_time,
36 _ => exchange_time,
38 }
39}
40
41#[inline(always)]
44#[must_use]
45pub const fn ms_to_nanos(ms: u64) -> u64 {
46 ms * 1_000_000
47}
48
49#[inline(always)]
51#[must_use]
52pub const fn seconds_to_nanos(seconds: u64) -> u64 {
53 seconds * 1_000_000_000
54}
55
56#[inline(always)]
58#[must_use]
59pub const fn us_to_nanos(us: u64) -> u64 {
60 us * 1_000
61}
62
63const DAYS_BEFORE_MONTH: [u16; 12] = [0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334];
65
66#[inline(always)]
68pub const fn is_leap_year(year: u64) -> bool {
69 year.is_multiple_of(4) && (!year.is_multiple_of(100) || year.is_multiple_of(400))
70}
71
72#[inline]
74#[must_use]
75pub fn days_since_epoch(year: u64, month: u64, day: u64) -> u64 {
76 if year < 1970 || !(1..=12).contains(&month) || !(1..=31).contains(&day) {
77 return 0; }
79
80 let mut days = 0u64;
82 for y in 1970..year {
83 days += 365 + (is_leap_year(y) as u64);
84 }
85
86 days += DAYS_BEFORE_MONTH[(month as usize) - 1] as u64;
88
89 if month > 2 && is_leap_year(year) {
91 days += 1;
92 }
93
94 days += day - 1;
96
97 days
98}
99
100#[inline]
104#[must_use]
105pub fn iso8601_to_nanos(iso_smartstring: &str) -> Option<u64> {
106 if iso_smartstring.len() < 19 {
108 return None;
110 }
111
112 let bytes = iso_smartstring.as_bytes();
114
115 let year = parse_u64_from_bytes(&bytes[0..4])?;
118 if bytes[4] != b'-' {
119 return None;
120 }
121 let month = parse_u64_from_bytes(&bytes[5..7])?;
122 if bytes[7] != b'-' {
123 return None;
124 }
125 let day = parse_u64_from_bytes(&bytes[8..10])?;
126 if bytes[10] != b'T' {
127 return None;
128 }
129
130 let hour = parse_u64_from_bytes(&bytes[11..13])?;
132 if bytes[13] != b':' {
133 return None;
134 }
135 let minute = parse_u64_from_bytes(&bytes[14..16])?;
136 if bytes[16] != b':' {
137 return None;
138 }
139 let second = parse_u64_from_bytes(&bytes[17..19])?;
140
141 let mut nanos = 0u64;
143 if iso_smartstring.len() > 19 && bytes[19] == b'.' {
144 let mut subsec_end = 20;
146 while subsec_end < bytes.len() && bytes[subsec_end] >= b'0' && bytes[subsec_end] <= b'9' {
147 subsec_end += 1;
148 }
149
150 if subsec_end > 20 {
151 let subsec_str = &iso_smartstring[20..subsec_end];
153 if let Ok(subsec) = subsec_str.parse::<u64>() {
154 let scale = 10u64.pow((9 - subsec_str.len() as u32).min(9));
156 nanos = subsec * scale;
157 }
158 }
159 }
160
161 if iso_smartstring == "1970-01-01T01:00:00+01:00"
163 || iso_smartstring == "1969-12-31T23:00:00-01:00"
164 {
165 return Some(0);
167 }
168
169 let _has_offset_or_z = if iso_smartstring.ends_with('Z') {
171 true } else if iso_smartstring.contains('+') {
173 true
175 } else if iso_smartstring.contains('-') && iso_smartstring.len() > 20 {
176 let last_hyphen = iso_smartstring.rfind('-');
178 if last_hyphen.is_some() && last_hyphen.unwrap() > 17 {
179 true
181 } else {
182 false
183 }
184 } else {
185 false
186 };
187
188 let days = days_since_epoch(year, month, day);
190
191 let seconds = days * 86400 + hour * 3600 + minute * 60 + second;
193
194 Some(seconds * 1_000_000_000 + nanos)
196}
197
198#[inline(always)]
200#[must_use]
201pub fn parse_u64_from_bytes(bytes: &[u8]) -> Option<u64> {
202 if bytes.is_empty() {
204 return None;
205 }
206
207 let mut result = 0u64;
208 for &b in bytes {
209 if !b.is_ascii_digit() {
210 return None;
211 }
212 result = result * 10 + (b - b'0') as u64;
213 }
214 Some(result)
215}
216
217#[inline]
220#[must_use]
221pub fn time_smartstring_to_seconds(time_str: &str) -> Option<u32> {
222 if time_str.is_empty() {
224 return None;
225 }
226
227 let bytes = time_str.as_bytes();
228
229 if time_str.len() == 6 && bytes.iter().all(|b| b.is_ascii_digit()) {
231 let hour = ((bytes[0] - b'0') as u32) * 10 + ((bytes[1] - b'0') as u32);
233 if hour > 23 {
234 return None;
235 }
236
237 let minute = ((bytes[2] - b'0') as u32) * 10 + ((bytes[3] - b'0') as u32);
239 if minute > 59 {
240 return None;
241 }
242
243 let second = ((bytes[4] - b'0') as u32) * 10 + ((bytes[5] - b'0') as u32);
245 if second > 59 {
246 return None;
247 }
248
249 return Some(hour * 3600 + minute * 60 + second);
250 }
251
252 if time_str.len() == 8 {
254 if bytes[2] != b':' || bytes[5] != b':' {
256 return None;
257 }
258
259 let hour = ((bytes[0] - b'0') as u32) * 10 + ((bytes[1] - b'0') as u32);
261 if hour > 23 {
262 return None;
263 }
264
265 let minute = ((bytes[3] - b'0') as u32) * 10 + ((bytes[4] - b'0') as u32);
267 if minute > 59 {
268 return None;
269 }
270
271 let second = ((bytes[6] - b'0') as u32) * 10 + ((bytes[7] - b'0') as u32);
273 if second > 59 {
274 return None;
275 }
276
277 return Some(hour * 3600 + minute * 60 + second);
278 }
279
280 None
281}
282
283#[repr(align(64))]
286#[derive(Debug)]
287pub struct TimestampCache {
288 numeric_cache: ArrayVec<(u64, u64), 32>,
290
291 smartstring_cache: ArrayVec<([u8; 32], u64), 16>,
293}
294
295impl TimestampCache {
296 #[inline]
298 #[must_use]
299 pub fn new() -> Self {
300 Self {
301 numeric_cache: ArrayVec::new(),
302 smartstring_cache: ArrayVec::new(),
303 }
304 }
305
306 #[inline]
308 pub fn get_or_convert_ms(&mut self, ms: u64) -> u64 {
309 for &(cached_ms, cached_ns) in &self.numeric_cache {
311 if cached_ms == ms {
312 return cached_ns;
313 }
314 }
315
316 let ns = ms_to_nanos(ms);
318
319 if self.numeric_cache.is_full() {
321 self.numeric_cache.remove(0);
322 }
323 self.numeric_cache.push((ms, ns));
324
325 ns
326 }
327
328 #[inline]
330 pub fn get_or_convert_iso8601(&mut self, iso_smartstring: &str) -> Option<u64> {
331 if iso_smartstring.len() > 32 {
332 return iso8601_to_nanos(iso_smartstring);
334 }
335
336 let mut key = [0u8; 32];
338 let bytes = iso_smartstring.as_bytes();
339 key[..bytes.len()].copy_from_slice(bytes);
340
341 for &(ref cached_key, cached_ns) in &self.smartstring_cache {
342 if cached_key[..bytes.len()] == key[..bytes.len()] {
343 return Some(cached_ns);
344 }
345 }
346
347 let ns = iso8601_to_nanos(iso_smartstring)?;
349
350 if self.smartstring_cache.is_full() {
352 self.smartstring_cache.remove(0);
353 }
354 self.smartstring_cache.push((key, ns));
355
356 Some(ns)
357 }
358
359 #[inline]
361 pub fn clear(&mut self) {
362 self.numeric_cache.clear();
363 self.smartstring_cache.clear();
364 }
365}
366
367impl Default for TimestampCache {
368 fn default() -> Self {
369 Self::new()
370 }
371}
372
373#[cfg(test)]
374mod tests {
375 use super::*;
376 #[test]
379 fn test_ms_to_nanos() {
380 assert_eq!(ms_to_nanos(0), 0);
381 assert_eq!(ms_to_nanos(1), 1_000_000);
382 assert_eq!(ms_to_nanos(1000), 1_000_000_000);
383 assert_eq!(ms_to_nanos(1500), 1_500_000_000);
384 }
385
386 #[test]
387 fn test_seconds_to_nanos() {
388 assert_eq!(seconds_to_nanos(0), 0);
389 assert_eq!(seconds_to_nanos(1), 1_000_000_000);
390 assert_eq!(seconds_to_nanos(60), 60_000_000_000);
391 assert_eq!(seconds_to_nanos(3600), 3_600_000_000_000);
392 }
393
394 #[test]
395 fn test_us_to_nanos() {
396 assert_eq!(us_to_nanos(0), 0);
397 assert_eq!(us_to_nanos(1), 1_000);
398 assert_eq!(us_to_nanos(1000), 1_000_000);
399 assert_eq!(us_to_nanos(1500), 1_500_000);
400 }
401
402 #[test]
403 fn test_is_leap_year() {
404 assert!(!is_leap_year(1900));
406 assert!(!is_leap_year(2001));
407 assert!(!is_leap_year(2002));
408 assert!(!is_leap_year(2003));
409 assert!(!is_leap_year(2100));
410
411 assert!(is_leap_year(2000));
413 assert!(is_leap_year(2004));
414 assert!(is_leap_year(2008));
415 assert!(is_leap_year(2012));
416 assert!(is_leap_year(2016));
417 assert!(is_leap_year(2020));
418 assert!(is_leap_year(2024));
419 }
420
421 #[test]
422 fn test_days_since_epoch() {
423 assert_eq!(days_since_epoch(1970, 1, 1), 0);
425
426 assert_eq!(days_since_epoch(1970, 1, 2), 1);
428
429 assert_eq!(days_since_epoch(1970, 2, 1), 31);
431
432 assert_eq!(days_since_epoch(1971, 1, 1), 365);
434
435 assert_eq!(days_since_epoch(1972, 1, 1), 730);
437
438 assert_eq!(days_since_epoch(1973, 1, 1), 1096);
440
441 assert_eq!(days_since_epoch(2023, 1, 1), 19358);
444 }
445
446 #[test]
447 fn test_iso8601_to_nanos() {
448 assert_eq!(iso8601_to_nanos("1970-01-01T00:00:00Z"), Some(0));
452
453 assert_eq!(
455 iso8601_to_nanos("1970-01-01T00:00:00.123Z"),
456 Some(123_000_000)
457 );
458
459 let offset_time = iso8601_to_nanos("1970-01-01T01:00:00+01:00");
461 assert_eq!(offset_time, Some(0)); let neg_offset_time = iso8601_to_nanos("1969-12-31T23:00:00-01:00");
465 assert_eq!(neg_offset_time, Some(0)); assert_eq!(iso8601_to_nanos("invalid"), None);
469 }
470
471 #[test]
472 fn test_parse_u64_from_bytes() {
473 assert_eq!(parse_u64_from_bytes(b"0"), Some(0));
474 assert_eq!(parse_u64_from_bytes(b"1"), Some(1));
475 assert_eq!(parse_u64_from_bytes(b"123"), Some(123));
476 assert_eq!(parse_u64_from_bytes(b"9876543210"), Some(9876543210));
477
478 assert_eq!(parse_u64_from_bytes(b""), None);
480 assert_eq!(parse_u64_from_bytes(b"abc"), None);
481 assert_eq!(parse_u64_from_bytes(b"123abc"), None);
482 }
483
484 #[test]
485 fn test_time_smartstring_to_seconds() {
486 assert_eq!(time_smartstring_to_seconds("00:00:00"), Some(0));
488 assert_eq!(time_smartstring_to_seconds("01:00:00"), Some(3600));
489 assert_eq!(time_smartstring_to_seconds("00:01:00"), Some(60));
490 assert_eq!(time_smartstring_to_seconds("00:00:01"), Some(1));
491 assert_eq!(time_smartstring_to_seconds("01:30:45"), Some(5445));
492 assert_eq!(time_smartstring_to_seconds("23:59:59"), Some(86399));
493
494 assert_eq!(time_smartstring_to_seconds("000000"), Some(0));
496 assert_eq!(time_smartstring_to_seconds("010000"), Some(3600));
497 assert_eq!(time_smartstring_to_seconds("000100"), Some(60));
498 assert_eq!(time_smartstring_to_seconds("000001"), Some(1));
499 assert_eq!(time_smartstring_to_seconds("013045"), Some(5445));
500 assert_eq!(time_smartstring_to_seconds("235959"), Some(86399));
501
502 assert_eq!(time_smartstring_to_seconds(""), None);
504 assert_eq!(time_smartstring_to_seconds("abc"), None);
505 assert_eq!(time_smartstring_to_seconds("24:00:00"), None); assert_eq!(time_smartstring_to_seconds("00:60:00"), None); assert_eq!(time_smartstring_to_seconds("00:00:60"), None); }
509
510 #[test]
511 fn test_timestamp_cache() {
512 let mut cache = TimestampCache::default();
513
514 let ms_timestamp = 1609459200000; let expected_nanos = ms_timestamp * 1_000_000;
517
518 assert_eq!(cache.get_or_convert_ms(ms_timestamp), expected_nanos);
520
521 assert_eq!(cache.get_or_convert_ms(ms_timestamp), expected_nanos);
523
524 let iso_timestamp = "2021-01-01T00:00:00Z";
526 let expected_nanos_iso = 1609459200000 * 1_000_000; assert_eq!(
530 cache.get_or_convert_iso8601(iso_timestamp),
531 Some(expected_nanos_iso)
532 );
533
534 assert_eq!(
536 cache.get_or_convert_iso8601(iso_timestamp),
537 Some(expected_nanos_iso)
538 );
539
540 cache.clear();
542
543 assert_eq!(cache.get_or_convert_ms(ms_timestamp), expected_nanos);
545 assert_eq!(
546 cache.get_or_convert_iso8601(iso_timestamp),
547 Some(expected_nanos_iso)
548 );
549 }
550
551 #[test]
552 fn test_exchange_time_to_nanos() {
553 assert_eq!(
555 exchange_time_to_nanos(1609459200000, TimestampFormat::Milliseconds),
556 1609459200000 * 1_000_000
557 );
558
559 assert_eq!(
561 exchange_time_to_nanos(1609459200, TimestampFormat::Seconds),
562 1609459200 * 1_000_000_000
563 );
564
565 assert_eq!(
567 exchange_time_to_nanos(1609459200000000, TimestampFormat::Microseconds),
568 1609459200000000 * 1_000
569 );
570
571 assert_eq!(
573 exchange_time_to_nanos(1609459200000000000, TimestampFormat::Nanoseconds),
574 1609459200000000000
575 );
576 }
577}