cs_datetime_parse/
lib.rs

1use constants::{MAX_TICKS, MIN_TICKS, TICKS_CEILING, TICKS_PER_DAY, TICK_MASK};
2use constants::{MIN_DATE_TIME, NANOSECONDS_PER_TICK, TICKS_PER_SECOND};
3#[cfg(feature = "serde")]
4use serde::{Deserialize, Serialize};
5use std::time::Duration;
6use thiserror::Error;
7use time::{error::IndeterminateOffset, OffsetDateTime, PrimitiveDateTime, UtcOffset};
8mod constants;
9
10#[derive(Error, Debug)]
11pub enum OutOfRangeError {
12	#[error("date time is too large")]
13	TooLarge,
14	#[error("date time is too small")]
15	TooSmall,
16}
17
18#[derive(Error, Debug)]
19pub enum ParseError {
20	#[error("can't determine offset for date time")]
21	IndeterminateOffset(IndeterminateOffset),
22	#[error("date time is out of range")]
23	OutOfRange(OutOfRangeError),
24}
25
26fn ticks_to_date_time(ticks: u64) -> PrimitiveDateTime {
27	MIN_DATE_TIME
28		+ Duration::new(
29			ticks / TICKS_PER_SECOND as u64,
30			(ticks % TICKS_PER_SECOND as u64 * NANOSECONDS_PER_TICK as u64) as u32,
31		)
32}
33
34fn round_division(dividend: i128, divisor: i128) -> i128 {
35	let double_remainder = (dividend % divisor) * 2;
36	dividend / divisor
37		+ if double_remainder.abs() >= divisor.abs() {
38			if dividend.is_negative() == divisor.is_negative() {
39				1
40			} else {
41				-1
42			}
43		} else {
44			0
45		}
46}
47
48fn date_time_to_tick(date_time: PrimitiveDateTime) -> i128 {
49	let nanoseconds = (date_time - MIN_DATE_TIME).whole_nanoseconds();
50	round_division(nanoseconds, 100)
51}
52
53#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
54#[derive(Debug, Clone, Copy, PartialEq, PartialOrd, Ord, Eq, Hash)]
55pub enum DateTimeCs {
56	Local(OffsetDateTime),
57	Unspecified(PrimitiveDateTime),
58	Utc(OffsetDateTime),
59}
60
61impl DateTimeCs {
62	pub fn from_binary(date_time_data: i64) -> Result<Self, ParseError> {
63		let kind = ((date_time_data >> 62) & 3) as u8;
64		let ticks = date_time_data as u64 & TICK_MASK;
65		if kind == 0 || kind == 1 {
66			if ticks > MAX_TICKS {
67				return Err(ParseError::OutOfRange(OutOfRangeError::TooLarge));
68			}
69
70			let date_time = ticks_to_date_time(ticks);
71			return Ok(if kind == 0 {
72				DateTimeCs::Unspecified(date_time)
73			} else {
74				DateTimeCs::Utc(date_time.assume_utc())
75			});
76		}
77
78		let mut ticks = ticks as i64;
79		if ticks > TICKS_CEILING as i64 - TICKS_PER_DAY as i64 {
80			ticks -= TICKS_CEILING as i64;
81		}
82
83		let offset = if ticks > MAX_TICKS as i64 {
84			UtcOffset::local_offset_at(constants::MAX_DATE_TIME.assume_utc())
85		} else if ticks < MIN_TICKS as i64 {
86			UtcOffset::local_offset_at(constants::MIN_DATE_TIME.assume_utc())
87		} else {
88			let date_time = ticks_to_date_time(ticks as u64).assume_utc();
89			UtcOffset::local_offset_at(date_time)
90		}
91		.map_err(ParseError::IndeterminateOffset)?;
92
93		ticks += offset.whole_seconds() as i64 * 10000000;
94		if ticks > MAX_TICKS as i64 {
95			return Err(ParseError::OutOfRange(OutOfRangeError::TooLarge));
96		}
97
98		Ok(DateTimeCs::Local(
99			ticks_to_date_time(ticks as u64).assume_offset(offset),
100		))
101	}
102
103	pub fn to_binary(&self) -> Result<i64, OutOfRangeError> {
104		let ticks = match self {
105			DateTimeCs::Utc(d) => date_time_to_tick(d.date().with_time(d.time())),
106			DateTimeCs::Unspecified(d) => date_time_to_tick(d.to_owned()),
107			DateTimeCs::Local(d) => {
108				let ticks = date_time_to_tick(d.date().with_time(d.time()));
109				if ticks < 0 {
110					ticks + TICKS_CEILING as i128
111				} else {
112					ticks
113				}
114			}
115		};
116
117		if ticks >= TICKS_CEILING as i128 {
118			return Err(OutOfRangeError::TooLarge);
119		}
120
121		if ticks < MIN_TICKS as i128 {
122			return Err(OutOfRangeError::TooSmall);
123		}
124
125		let kind: i64 = match self {
126			DateTimeCs::Unspecified(_) => 0,
127			DateTimeCs::Utc(_) => 1,
128			DateTimeCs::Local(_) => 2,
129		};
130
131		Ok(ticks as i64 | (kind << 62))
132	}
133}
134
135impl From<PrimitiveDateTime> for DateTimeCs {
136	fn from(value: PrimitiveDateTime) -> Self {
137		Self::Unspecified(value)
138	}
139}
140
141impl From<OffsetDateTime> for DateTimeCs {
142	fn from(value: OffsetDateTime) -> Self {
143		if value.offset().is_utc() {
144			Self::Utc(value)
145		} else {
146			Self::Local(value)
147		}
148	}
149}