1use std::{
2 fmt,
3 ops::{Add, Rem},
4};
5
6use brk_error::Error;
7use jiff::Span;
8use schemars::JsonSchema;
9use serde::{Deserialize, Serialize};
10use vecdb::{CheckedSub, Formattable, FromCoarserIndex, Pco, PrintableIndex};
11
12use crate::{DecadeIndex, MonthIndex, QuarterIndex, SemesterIndex, WeekIndex, YearIndex};
13
14use super::Date;
15
16#[derive(
17 Debug,
18 Default,
19 Clone,
20 Copy,
21 PartialEq,
22 Eq,
23 PartialOrd,
24 Ord,
25 Serialize,
26 Deserialize,
27 Pco,
28 JsonSchema,
29)]
30pub struct DateIndex(u16);
31
32impl DateIndex {
33 pub const BYTES: usize = size_of::<Self>();
34}
35
36impl From<DateIndex> for usize {
37 #[inline]
38 fn from(value: DateIndex) -> Self {
39 value.0 as usize
40 }
41}
42
43impl From<DateIndex> for u64 {
44 #[inline]
45 fn from(value: DateIndex) -> Self {
46 value.0 as u64
47 }
48}
49
50impl From<usize> for DateIndex {
51 #[inline]
52 fn from(value: usize) -> Self {
53 Self(value as u16)
54 }
55}
56
57impl From<DateIndex> for i64 {
58 #[inline]
59 fn from(value: DateIndex) -> Self {
60 value.0 as i64
61 }
62}
63
64impl Add<usize> for DateIndex {
65 type Output = Self;
66 fn add(self, rhs: usize) -> Self::Output {
67 Self(self.0 + rhs as u16)
68 }
69}
70
71impl TryFrom<Date> for DateIndex {
72 type Error = Error;
73 fn try_from(value: Date) -> Result<Self, Self::Error> {
74 let value_ = jiff::civil::Date::from(value);
75 if value_ < Date::INDEX_ZERO_ {
76 Err(Error::UnindexableDate)
77 } else if value == Date::INDEX_ZERO {
78 Ok(Self(0))
79 } else if value_ < Date::INDEX_ONE_ {
80 Err(Error::UnindexableDate)
81 } else if value == Date::INDEX_ONE {
82 Ok(Self(1))
83 } else {
84 Ok(Self(Date::INDEX_ONE_.until(value_)?.get_days() as u16 + 1))
85 }
86 }
87}
88
89impl CheckedSub for DateIndex {
90 fn checked_sub(self, rhs: Self) -> Option<Self> {
91 self.0.checked_sub(rhs.0).map(Self)
92 }
93}
94
95impl Rem<usize> for DateIndex {
96 type Output = Self;
97 fn rem(self, rhs: usize) -> Self::Output {
98 Self(self.0 % rhs as u16)
99 }
100}
101
102impl FromCoarserIndex<WeekIndex> for DateIndex {
103 fn min_from(coarser: WeekIndex) -> usize {
104 let coarser = usize::from(coarser);
105 if coarser == 0 {
106 0
107 } else if coarser == 1 {
108 1
109 } else {
110 4 + (coarser - 2) * 7
111 }
112 }
113
114 fn max_from_(coarser: WeekIndex) -> usize {
115 let coarser = usize::from(coarser);
116 if coarser == 0 {
117 0
118 } else if coarser == 1 {
119 3
120 } else {
121 3 + (coarser - 1) * 7
122 }
123 }
124}
125
126impl FromCoarserIndex<MonthIndex> for DateIndex {
127 fn min_from(coarser: MonthIndex) -> usize {
128 let coarser = u16::from(coarser);
129 if coarser == 0 {
130 0
131 } else {
132 let d = Date::new(2009, 1, 1)
133 .into_jiff()
134 .checked_add(Span::new().months(coarser))
135 .unwrap();
136 DateIndex::try_from(Date::from(d)).unwrap().into()
137 }
138 }
139
140 fn max_from_(coarser: MonthIndex) -> usize {
141 let d = Date::new(2009, 1, 31)
142 .into_jiff()
143 .checked_add(Span::new().months(u16::from(coarser)))
144 .unwrap();
145 DateIndex::try_from(Date::from(d)).unwrap().into()
146 }
147}
148
149impl FromCoarserIndex<QuarterIndex> for DateIndex {
150 fn min_from(coarser: QuarterIndex) -> usize {
151 let coarser = u16::from(coarser);
152 if coarser == 0 {
153 0
154 } else {
155 let d = Date::new(2009, 1, 1)
156 .into_jiff()
157 .checked_add(Span::new().months(3 * coarser))
158 .unwrap();
159 DateIndex::try_from(Date::from(d)).unwrap().into()
160 }
161 }
162
163 fn max_from_(coarser: QuarterIndex) -> usize {
164 let d = Date::new(2009, 3, 31)
165 .into_jiff()
166 .checked_add(Span::new().months(3 * u16::from(coarser)))
167 .unwrap();
168 DateIndex::try_from(Date::from(d)).unwrap().into()
169 }
170}
171
172impl FromCoarserIndex<SemesterIndex> for DateIndex {
173 fn min_from(coarser: SemesterIndex) -> usize {
174 let coarser = u16::from(coarser);
175 if coarser == 0 {
176 0
177 } else {
178 let d = Date::new(2009, 1, 1)
179 .into_jiff()
180 .checked_add(Span::new().months(6 * coarser))
181 .unwrap();
182 DateIndex::try_from(Date::from(d)).unwrap().into()
183 }
184 }
185
186 fn max_from_(coarser: SemesterIndex) -> usize {
187 let d = Date::new(2009, 5, 31)
188 .into_jiff()
189 .checked_add(Span::new().months(1 + 6 * u16::from(coarser)))
190 .unwrap();
191 DateIndex::try_from(Date::from(d)).unwrap().into()
192 }
193}
194
195impl FromCoarserIndex<YearIndex> for DateIndex {
196 fn min_from(coarser: YearIndex) -> usize {
197 let coarser = u16::from(coarser);
198 if coarser == 0 {
199 0
200 } else {
201 Self::try_from(Date::new(2009 + coarser, 1, 1))
202 .unwrap()
203 .into()
204 }
205 }
206
207 fn max_from_(coarser: YearIndex) -> usize {
208 Self::try_from(Date::new(2009 + u16::from(coarser), 12, 31))
209 .unwrap()
210 .into()
211 }
212}
213
214impl FromCoarserIndex<DecadeIndex> for DateIndex {
215 fn min_from(coarser: DecadeIndex) -> usize {
216 let coarser = u16::from(coarser);
217 if coarser == 0 {
218 0
219 } else {
220 Self::try_from(Date::new(2000 + 10 * coarser, 1, 1))
221 .unwrap()
222 .into()
223 }
224 }
225
226 fn max_from_(coarser: DecadeIndex) -> usize {
227 let coarser = u16::from(coarser);
228 Self::try_from(Date::new(2009 + (10 * coarser), 12, 31))
229 .unwrap()
230 .into()
231 }
232}
233
234impl PrintableIndex for DateIndex {
235 fn to_string() -> &'static str {
236 "dateindex"
237 }
238
239 fn to_possible_strings() -> &'static [&'static str] {
240 &["d", "date", "dateindex"]
241 }
242}
243
244impl fmt::Display for DateIndex {
245 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
246 let mut buf = itoa::Buffer::new();
247 let str = buf.format(self.0);
248 f.write_str(str)
249 }
250}
251
252impl Formattable for DateIndex {
253 #[inline(always)]
254 fn may_need_escaping() -> bool {
255 false
256 }
257}