1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
use constants::{MAX_TICKS, MIN_TICKS, TICKS_CEILING, TICKS_PER_DAY, TICK_MASK};
use constants::{MIN_DATE_TIME, NANOSECONDS_PER_TICK, TICKS_PER_SECOND};
#[cfg(feature = "serde")]
use serde::{Deserialize, Serialize};
use std::time::Duration;
use thiserror::Error;
use time::{error::IndeterminateOffset, OffsetDateTime, PrimitiveDateTime, UtcOffset};
mod constants;

#[derive(Error, Debug)]
pub enum OutOfRangeError {
	#[error("date time is too large")]
	TooLarge,
	#[error("date time is too small")]
	TooSmall,
}

#[derive(Error, Debug)]
pub enum ParseError {
	#[error("can't determine offset for date time")]
	IndeterminateOffset(IndeterminateOffset),
	#[error("date time is out of range")]
	OutOfRange(OutOfRangeError),
}

fn ticks_to_date_time(ticks: u64) -> PrimitiveDateTime {
	MIN_DATE_TIME
		+ Duration::new(
			ticks / TICKS_PER_SECOND as u64,
			(ticks % TICKS_PER_SECOND as u64 * NANOSECONDS_PER_TICK as u64) as u32,
		)
}

fn date_time_to_tick(date_time: PrimitiveDateTime) -> i128 {
	(date_time - MIN_DATE_TIME).whole_nanoseconds() / 100
}

#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
#[derive(Debug, Clone, Copy, PartialEq, PartialOrd, Ord, Eq, Hash)]
pub enum DateTimeCs {
	Local(OffsetDateTime),
	Unspecified(PrimitiveDateTime),
	Utc(OffsetDateTime),
}

impl DateTimeCs {
	pub fn from_binary(date_time_data: i64) -> Result<Self, ParseError> {
		let kind = ((date_time_data >> 62) & 3) as u8;
		let ticks = date_time_data as u64 & TICK_MASK;
		if kind == 0 || kind == 1 {
			if ticks > MAX_TICKS {
				return Err(ParseError::OutOfRange(OutOfRangeError::TooLarge));
			}

			let date_time = ticks_to_date_time(ticks);
			return Ok(if kind == 0 {
				DateTimeCs::Unspecified(date_time)
			} else {
				DateTimeCs::Utc(date_time.assume_utc())
			});
		}

		let mut ticks = ticks as i64;
		if ticks > TICKS_CEILING as i64 - TICKS_PER_DAY as i64 {
			ticks -= TICKS_CEILING as i64;
		}

		let offset = if ticks > MAX_TICKS as i64 {
			UtcOffset::local_offset_at(constants::MAX_DATE_TIME.assume_utc())
		} else if ticks < MIN_TICKS as i64 {
			UtcOffset::local_offset_at(constants::MIN_DATE_TIME.assume_utc())
		} else {
			let date_time = ticks_to_date_time(ticks as u64).assume_utc();
			UtcOffset::local_offset_at(date_time)
		}
		.map_err(ParseError::IndeterminateOffset)?;

		ticks += offset.whole_seconds() as i64 * 10000000;
		if ticks > MAX_TICKS as i64 {
			return Err(ParseError::OutOfRange(OutOfRangeError::TooLarge));
		}

		Ok(DateTimeCs::Local(
			ticks_to_date_time(ticks as u64).assume_offset(offset),
		))
	}

	pub fn to_binary(&self) -> Result<i64, OutOfRangeError> {
		let ticks = match self {
			DateTimeCs::Utc(d) => date_time_to_tick(d.date().with_time(d.time())),
			DateTimeCs::Unspecified(d) => date_time_to_tick(d.to_owned()),
			DateTimeCs::Local(d) => {
				let ticks = date_time_to_tick(d.date().with_time(d.time()));
				if ticks < 0 {
					ticks + TICKS_CEILING as i128
				} else {
					ticks
				}
			}
		};

		if ticks >= TICKS_CEILING as i128 {
			return Err(OutOfRangeError::TooLarge);
		}

		if ticks < MIN_TICKS as i128 {
			return Err(OutOfRangeError::TooSmall);
		}

		let kind: i64 = match self {
			DateTimeCs::Unspecified(_) => 0,
			DateTimeCs::Utc(_) => 1,
			DateTimeCs::Local(_) => 2,
		};

		Ok(ticks as i64 | (kind << 62))
	}
}

impl From<PrimitiveDateTime> for DateTimeCs {
	fn from(value: PrimitiveDateTime) -> Self {
		Self::Unspecified(value)
	}
}

impl From<OffsetDateTime> for DateTimeCs {
	fn from(value: OffsetDateTime) -> Self {
		if value.offset().is_utc() {
			Self::Utc(value)
		} else {
			Self::Local(value)
		}
	}
}