1use alloc::vec::Vec;
2use core::convert::{TryFrom, TryInto};
3use core::fmt::Display;
4use core::ops::{Deref, DerefMut, RangeInclusive};
5
6use chrono::prelude::Datelike;
7use chrono::{Duration, NaiveDate};
8
9pub use chrono::Weekday;
11
12use crate::display::{write_days_offset, write_selector};
13
14fn wday_str(wday: Weekday) -> &'static str {
17 match wday {
18 Weekday::Mon => "Mo",
19 Weekday::Tue => "Tu",
20 Weekday::Wed => "We",
21 Weekday::Thu => "Th",
22 Weekday::Fri => "Fr",
23 Weekday::Sat => "Sa",
24 Weekday::Sun => "Su",
25 }
26}
27
28#[derive(Clone, Debug)]
31pub struct InvalidMonth;
32
33#[derive(Clone, Debug, Default, Hash, PartialEq, Eq)]
36pub struct DaySelector {
37 pub year: Vec<YearRange>,
38 pub monthday: Vec<MonthdayRange>,
39 pub week: Vec<WeekRange>,
40 pub weekday: Vec<WeekDayRange>,
41}
42
43impl DaySelector {
44 pub fn is_empty(&self) -> bool {
46 self.year.is_empty()
47 && self.monthday.is_empty()
48 && self.week.is_empty()
49 && self.weekday.is_empty()
50 }
51
52 pub(crate) fn display(
57 &self,
58 f: &mut core::fmt::Formatter<'_>,
59 force: bool,
60 ) -> core::fmt::Result {
61 if force && self.is_empty() {
62 return write!(f, "Mo-Su");
63 }
64
65 if !(self.year.is_empty() && self.monthday.is_empty() && self.week.is_empty()) {
66 write_selector(f, &self.year)?;
67 write_selector(f, &self.monthday)?;
68
69 if !self.week.is_empty() {
70 if !self.year.is_empty() || !self.monthday.is_empty() {
71 write!(f, " ")?;
72 }
73
74 write!(f, "week")?;
75 write_selector(f, &self.week)?;
76 }
77
78 if !self.weekday.is_empty() {
79 write!(f, " ")?;
80 }
81 }
82
83 write_selector(f, &self.weekday)
84 }
85}
86
87impl Display for DaySelector {
88 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
89 self.display(f, false)
90 }
91}
92
93#[derive(Copy, Clone, Debug, Hash, PartialEq, Eq, PartialOrd, Ord)]
96pub struct Year(pub u16);
97
98impl Deref for Year {
99 type Target = u16;
100
101 fn deref(&self) -> &Self::Target {
102 &self.0
103 }
104}
105
106impl DerefMut for Year {
107 fn deref_mut(&mut self) -> &mut Self::Target {
108 &mut self.0
109 }
110}
111
112#[derive(Clone, Debug, Hash, PartialEq, Eq)]
114pub struct YearRange {
115 pub range: RangeInclusive<Year>,
116 pub step: u16,
117}
118
119impl Display for YearRange {
120 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
121 write!(f, "{}", self.range.start().deref())?;
122
123 if self.range.start() != self.range.end() {
124 write!(f, "-{}", self.range.end().deref())?;
125 }
126
127 if self.step != 1 {
128 write!(f, "/{}", self.step)?;
129 }
130
131 Ok(())
132 }
133}
134
135#[derive(Clone, Debug, Hash, PartialEq, Eq)]
138pub enum MonthdayRange {
139 Month {
140 range: RangeInclusive<Month>,
141 year: Option<u16>,
142 },
143 Date {
144 start: (Date, DateOffset),
145 end: (Date, DateOffset),
146 },
147}
148
149impl Display for MonthdayRange {
150 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
151 match self {
152 Self::Month { range, year } => {
153 if let Some(year) = year {
154 write!(f, "{year}")?;
155 }
156
157 write!(f, "{}", range.start())?;
158
159 if range.start() != range.end() {
160 write!(f, "-{}", range.end())?;
161 }
162 }
163 Self::Date { start, end } => {
164 write!(f, "{}{}", start.0, start.1)?;
165
166 if start != end {
167 write!(f, "-{}{}", end.0, end.1)?;
168 }
169 }
170 }
171
172 Ok(())
173 }
174}
175
176#[derive(Clone, Copy, Debug, Hash, PartialEq, Eq)]
179pub enum Date {
180 Fixed {
181 year: Option<u16>,
182 month: Month,
183 day: u8,
184 },
185 Easter {
186 year: Option<u16>,
187 },
188}
189
190impl Date {
191 #[inline]
192 pub fn ymd(day: u8, month: Month, year: u16) -> Self {
193 Self::Fixed { day, month, year: Some(year) }
194 }
195
196 #[inline]
197 pub fn md(day: u8, month: Month) -> Self {
198 Self::Fixed { day, month, year: None }
199 }
200
201 #[inline]
202 pub fn has_year(&self) -> bool {
203 matches!(
204 self,
205 Self::Fixed { year: Some(_), .. } | Self::Easter { year: Some(_) }
206 )
207 }
208}
209
210impl Display for Date {
211 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
212 match self {
213 Date::Fixed { year, month, day } => {
214 if let Some(year) = year {
215 write!(f, "{year} ")?;
216 }
217
218 write!(f, "{month} {day}")?;
219 }
220 Date::Easter { year } => {
221 if let Some(year) = year {
222 write!(f, "{year} ")?;
223 }
224
225 write!(f, "easter")?;
226 }
227 }
228
229 Ok(())
230 }
231}
232
233#[derive(Clone, Copy, Debug, Default, Hash, PartialEq, Eq)]
236pub struct DateOffset {
237 pub wday_offset: WeekDayOffset,
238 pub day_offset: i64,
239}
240
241impl DateOffset {
242 #[inline]
243 pub fn apply(&self, mut date: NaiveDate) -> NaiveDate {
244 date += Duration::days(self.day_offset);
245
246 match self.wday_offset {
247 WeekDayOffset::None => {}
248 WeekDayOffset::Prev(target) => {
249 let diff = (7 + date.weekday().days_since(Weekday::Mon)
250 - target.days_since(Weekday::Mon))
251 % 7;
252
253 date -= Duration::days(diff.into());
254 debug_assert_eq!(date.weekday(), target);
255 }
256 WeekDayOffset::Next(target) => {
257 let diff = (7 + target.days_since(Weekday::Mon)
258 - date.weekday().days_since(Weekday::Mon))
259 % 7;
260
261 date += Duration::days(diff.into());
262 debug_assert_eq!(date.weekday(), target);
263 }
264 }
265
266 date
267 }
268}
269
270impl Display for DateOffset {
271 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
272 write!(f, "{}", self.wday_offset)?;
273 write_days_offset(f, self.day_offset)?;
274 Ok(())
275 }
276}
277
278#[derive(Clone, Copy, Debug, Hash, PartialEq, Eq)]
281pub enum WeekDayOffset {
282 None,
283 Next(Weekday),
284 Prev(Weekday),
285}
286
287impl Default for WeekDayOffset {
288 #[inline]
289 fn default() -> Self {
290 Self::None
291 }
292}
293
294impl Display for WeekDayOffset {
295 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
296 match self {
297 Self::None => {}
298 Self::Next(wday) => write!(f, "+{}", wday_str(*wday))?,
299 Self::Prev(wday) => write!(f, "-{}", wday_str(*wday))?,
300 }
301
302 Ok(())
303 }
304}
305
306#[derive(Clone, Debug, Hash, PartialEq, Eq)]
309pub enum WeekDayRange {
310 Fixed {
311 range: RangeInclusive<Weekday>,
312 offset: i64,
313 nth_from_start: [bool; 5],
314 nth_from_end: [bool; 5],
315 },
316 Holiday {
317 kind: HolidayKind,
318 offset: i64,
319 },
320}
321
322impl Display for WeekDayRange {
323 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
324 match self {
325 Self::Fixed { range, offset, nth_from_start, nth_from_end } => {
326 write!(f, "{}", wday_str(*range.start()))?;
327
328 if range.start() != range.end() {
329 write!(f, "-{}", wday_str(*range.end()))?;
330 }
331
332 if nth_from_start.contains(&false) || nth_from_end.contains(&false) {
333 let pos_weeknum_iter = nth_from_start
334 .iter()
335 .enumerate()
336 .filter(|(_, x)| **x)
337 .map(|(idx, _)| (idx + 1) as isize);
338
339 let neg_weeknum_iter = nth_from_end
340 .iter()
341 .enumerate()
342 .filter(|(_, x)| **x)
343 .map(|(idx, _)| -(idx as isize) - 1);
344
345 let mut weeknum_iter = pos_weeknum_iter.chain(neg_weeknum_iter);
346 write!(f, "[{}", weeknum_iter.next().unwrap())?;
347
348 for num in weeknum_iter {
349 write!(f, ",{num}")?;
350 }
351
352 write!(f, "]")?;
353 }
354
355 write_days_offset(f, *offset)?;
356 }
357 Self::Holiday { kind, offset } => {
358 write!(f, "{kind}")?;
359
360 if *offset != 0 {
361 write!(f, " {offset}")?;
362 }
363 }
364 }
365
366 Ok(())
367 }
368}
369
370#[derive(Clone, Copy, Debug, Hash, PartialEq, Eq)]
373pub enum HolidayKind {
374 Public,
375 School,
376}
377
378impl Display for HolidayKind {
379 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
380 match self {
381 Self::Public => write!(f, "PH"),
382 Self::School => write!(f, "SH"),
383 }
384 }
385}
386
387#[derive(Copy, Clone, Debug, Hash, PartialEq, Eq, PartialOrd, Ord)]
390pub struct WeekNum(pub u8);
391
392impl Deref for WeekNum {
393 type Target = u8;
394
395 fn deref(&self) -> &Self::Target {
396 &self.0
397 }
398}
399
400impl DerefMut for WeekNum {
401 fn deref_mut(&mut self) -> &mut Self::Target {
402 &mut self.0
403 }
404}
405
406#[derive(Clone, Debug, Hash, PartialEq, Eq)]
409pub struct WeekRange {
410 pub range: RangeInclusive<WeekNum>,
411 pub step: u8,
412}
413
414impl Display for WeekRange {
415 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
416 if self.range.start() == self.range.end() && self.step == 1 {
417 return write!(f, "{:02}", **self.range.start());
418 }
419
420 write!(f, "{:02}-{:02}", **self.range.start(), **self.range.end())?;
421
422 if self.step != 1 {
423 write!(f, "/{}", self.step)?;
424 }
425
426 Ok(())
427 }
428}
429
430#[derive(Copy, Clone, Debug, Hash, Eq, Ord, PartialEq, PartialOrd)]
433pub enum Month {
434 January = 1,
435 February = 2,
436 March = 3,
437 April = 4,
438 May = 5,
439 June = 6,
440 July = 7,
441 August = 8,
442 September = 9,
443 October = 10,
444 November = 11,
445 December = 12,
446}
447
448impl Month {
449 #[inline]
450 pub fn next(self) -> Self {
451 let num = self as u8;
452 ((num % 12) + 1).try_into().unwrap()
453 }
454
455 #[inline]
456 pub fn prev(self) -> Self {
457 let num = self as u8;
458 (((num + 10) % 12) + 1).try_into().unwrap()
459 }
460
461 #[inline]
463 pub fn from_date(date: impl Datelike) -> Self {
464 match date.month() {
465 1 => Self::January,
466 2 => Self::February,
467 3 => Self::March,
468 4 => Self::April,
469 5 => Self::May,
470 6 => Self::June,
471 7 => Self::July,
472 8 => Self::August,
473 9 => Self::September,
474 10 => Self::October,
475 11 => Self::November,
476 12 => Self::December,
477 other => unreachable!("Unexpected month for date `{other}`"),
478 }
479 }
480
481 #[inline]
483 pub fn as_str(self) -> &'static str {
484 match self {
485 Self::January => "January",
486 Self::February => "February",
487 Self::March => "March",
488 Self::April => "April",
489 Self::May => "May",
490 Self::June => "June",
491 Self::July => "July",
492 Self::August => "August",
493 Self::September => "September",
494 Self::October => "October",
495 Self::November => "November",
496 Self::December => "December",
497 }
498 }
499}
500
501impl Display for Month {
502 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
503 write!(f, "{}", &self.as_str()[..3])
504 }
505}
506
507macro_rules! impl_convert_for_month {
508 ( $from_type: ty ) => {
509 impl TryFrom<$from_type> for Month {
510 type Error = InvalidMonth;
511
512 #[inline]
513 fn try_from(value: $from_type) -> Result<Self, Self::Error> {
514 let value: u8 = value.try_into().map_err(|_| InvalidMonth)?;
515
516 Ok(match value {
517 1 => Self::January,
518 2 => Self::February,
519 3 => Self::March,
520 4 => Self::April,
521 5 => Self::May,
522 6 => Self::June,
523 7 => Self::July,
524 8 => Self::August,
525 9 => Self::September,
526 10 => Self::October,
527 11 => Self::November,
528 12 => Self::December,
529 _ => return Err(InvalidMonth),
530 })
531 }
532 }
533
534 impl From<Month> for $from_type {
535 fn from(val: Month) -> Self {
536 val as _
537 }
538 }
539 };
540 ( $from_type: ty, $( $tail: tt )+ ) => {
541 impl_convert_for_month!($from_type);
542 impl_convert_for_month!($($tail)+);
543 };
544}
545
546impl_convert_for_month!(u8, u16, u32, u64, u128, i8, i16, i32, i64, i128, usize, isize);