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 {
19 match wday {
20 Weekday::Mon => "Mo",
21 Weekday::Tue => "Tu",
22 Weekday::Wed => "We",
23 Weekday::Thu => "Th",
24 Weekday::Fri => "Fr",
25 Weekday::Sat => "Sa",
26 Weekday::Sun => "Su",
27 }
28}
29
30#[derive(Clone, Debug)]
35pub struct InvalidMonth;
36
37#[derive(Clone, Debug, Default, Hash, PartialEq, Eq)]
42pub struct DaySelector {
43 pub year: Vec<YearRange>,
44 pub monthday: Vec<MonthdayRange>,
45 pub week: Vec<WeekRange>,
46 pub weekday: Vec<WeekDayRange>,
47}
48
49impl DaySelector {
50 pub fn is_empty(&self) -> bool {
52 self.year.is_empty()
53 && self.monthday.is_empty()
54 && self.week.is_empty()
55 && self.weekday.is_empty()
56 }
57
58 pub(crate) fn display(
63 &self,
64 f: &mut core::fmt::Formatter<'_>,
65 force: bool,
66 ) -> core::fmt::Result {
67 if force && self.is_empty() {
68 return write!(f, "Mo-Su");
69 }
70
71 if !(self.year.is_empty() && self.monthday.is_empty() && self.week.is_empty()) {
72 write_selector(f, &self.year)?;
73 write_selector(f, &self.monthday)?;
74
75 if !self.week.is_empty() {
76 if !self.year.is_empty() || !self.monthday.is_empty() {
77 write!(f, " ")?;
78 }
79
80 write!(f, "week")?;
81 write_selector(f, &self.week)?;
82 }
83
84 if !self.weekday.is_empty() {
85 write!(f, " ")?;
86 }
87 }
88
89 write_selector(f, &self.weekday)
90 }
91}
92
93impl Display for DaySelector {
94 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
95 self.display(f, false)
96 }
97}
98
99#[derive(Copy, Clone, Debug, Hash, PartialEq, Eq, PartialOrd, Ord)]
104pub struct Year(pub i32);
105
106impl Deref for Year {
107 type Target = i32;
108
109 fn deref(&self) -> &Self::Target {
110 &self.0
111 }
112}
113
114impl DerefMut for Year {
115 fn deref_mut(&mut self) -> &mut Self::Target {
116 &mut self.0
117 }
118}
119
120#[derive(Clone, Debug, Hash, PartialEq, Eq)]
124pub struct YearRange {
125 range: RangeInclusive<Year>,
126 step: u16,
127}
128
129impl YearRange {
130 pub fn new(range: RangeInclusive<Year>, mut step: u16) -> Option<Self> {
132 if range.start() > range.end() {
133 return None;
134 }
135
136 if range.start().abs_diff(**range.end()) < step.into() {
137 step = 1;
138 }
139
140 Some(Self { range, step })
141 }
142
143 pub fn into_parts(&self) -> (RangeInclusive<Year>, u16) {
145 (self.range.clone(), self.step)
146 }
147}
148
149impl Display for YearRange {
150 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
151 let start = **self.range.start();
152 let end = **self.range.end();
153
154 write!(f, "{start}")?;
155
156 if start != end {
157 write!(f, "-{end}")?;
158
159 if self.step != 1 {
160 write!(f, "/{}", self.step)?;
161 }
162 }
163
164 Ok(())
165 }
166}
167
168#[derive(Clone, Debug, Hash, PartialEq, Eq)]
173pub enum MonthdayRange {
174 Month {
175 range: RangeInclusive<Month>,
176 year: Option<Year>,
177 },
178 Date {
179 start: (Date, DateOffset),
180 end: (Date, DateOffset),
181 },
182}
183
184impl Display for MonthdayRange {
185 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
186 match self {
187 Self::Month { range, year } => {
188 if let Some(year) = year {
189 write!(f, "{}", **year)?;
190 }
191
192 write!(f, "{}", range.start())?;
193
194 if range.start() != range.end() {
195 write!(f, "-{}", range.end())?;
196 }
197 }
198 Self::Date { start, end } => {
199 write!(f, "{}{}", start.0, start.1)?;
200
201 if start != end {
202 write!(f, "-{}{}", end.0, end.1)?;
203 }
204 }
205 }
206
207 Ok(())
208 }
209}
210
211#[derive(Clone, Copy, Debug, Hash, PartialEq, Eq)]
216pub enum Date {
217 Fixed {
218 year: Option<Year>,
219 month: Month,
220 day: u8,
221 },
222 Weekday {
223 year: Option<Year>,
224 month: Month,
225 wday: Weekday,
226 nth: i8,
227 },
228 Easter {
229 year: Option<Year>,
230 },
231}
232
233impl Date {
234 #[inline]
235 pub fn ymd(day: u8, month: Month, year: Year) -> Self {
236 Self::Fixed { day, month, year: Some(year) }
237 }
238
239 #[inline]
240 pub fn md(day: u8, month: Month) -> Self {
241 Self::Fixed { day, month, year: None }
242 }
243
244 #[inline]
245 pub fn year(&self) -> Option<Year> {
246 match *self {
247 Self::Fixed { year, .. } | Self::Weekday { year, .. } | Self::Easter { year } => year,
248 }
249 }
250}
251
252impl Display for Date {
253 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
254 match self {
255 Self::Fixed { year, month, day } => {
256 if let Some(year) = year {
257 write!(f, "{} ", **year)?;
258 }
259
260 write!(f, "{month} {day}")
261 }
262 Self::Weekday { year, month, wday: weekday, nth } => {
263 if let Some(year) = year {
264 write!(f, "{} ", **year)?;
265 }
266
267 write!(f, "{month} {}", wday_str(*weekday))?;
268
269 if *nth != 0 {
270 write!(f, "[{nth}]")?;
271 }
272
273 Ok(())
274 }
275 Self::Easter { year } => {
276 if let Some(year) = year {
277 write!(f, "{} ", **year)?;
278 }
279
280 write!(f, "easter")
281 }
282 }
283 }
284}
285
286#[derive(Clone, Copy, Debug, Default, Hash, PartialEq, Eq)]
291pub struct DateOffset {
292 pub wday_offset: WeekDayOffset,
293 pub day_offset: i16,
294}
295
296impl DateOffset {
297 #[inline]
298 pub fn apply(&self, mut date: NaiveDate) -> NaiveDate {
299 date += Duration::days(self.day_offset.into());
300
301 match self.wday_offset {
302 WeekDayOffset::None => {}
303 WeekDayOffset::Prev(target) => {
304 let diff = (7 + date.weekday().days_since(Weekday::Mon)
305 - target.days_since(Weekday::Mon))
306 % 7;
307
308 date -= Duration::days(diff.into());
309 debug_assert_eq!(date.weekday(), target);
310 }
311 WeekDayOffset::Next(target) => {
312 let diff = (7 + target.days_since(Weekday::Mon)
313 - date.weekday().days_since(Weekday::Mon))
314 % 7;
315
316 date += Duration::days(diff.into());
317 debug_assert_eq!(date.weekday(), target);
318 }
319 }
320
321 date
322 }
323}
324
325impl Display for DateOffset {
326 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
327 write!(f, "{}", self.wday_offset)?;
328 write_days_offset(f, self.day_offset)?;
329 Ok(())
330 }
331}
332
333#[derive(Clone, Copy, Debug, Hash, PartialEq, Eq)]
336pub enum WeekDayOffset {
337 None,
338 Next(Weekday),
339 Prev(Weekday),
340}
341
342impl Default for WeekDayOffset {
343 #[inline]
344 fn default() -> Self {
345 Self::None
346 }
347}
348
349impl Display for WeekDayOffset {
350 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
351 match self {
352 Self::None => {}
353 Self::Next(wday) => write!(f, "+{}", wday_str(*wday))?,
354 Self::Prev(wday) => write!(f, "-{}", wday_str(*wday))?,
355 }
356
357 Ok(())
358 }
359}
360
361#[derive(Clone, Debug, Hash, PartialEq, Eq)]
366pub enum WeekDayRange {
367 Fixed {
368 range: RangeInclusive<Weekday>,
369 offset: i16,
370 nth_from_start: [bool; 5],
371 nth_from_end: [bool; 5],
372 },
373 Holiday {
374 kind: HolidayKind,
375 offset: i16,
376 },
377}
378
379impl Display for WeekDayRange {
380 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
381 match self {
382 Self::Fixed { range, offset, nth_from_start, nth_from_end } => {
383 write!(f, "{}", wday_str(*range.start()))?;
384
385 if range.start() != range.end() {
386 write!(f, "-{}", wday_str(*range.end()))?;
387 }
388
389 if nth_from_start.contains(&false) || nth_from_end.contains(&false) {
390 let pos_weeknum_iter = nth_from_start
391 .iter()
392 .enumerate()
393 .filter(|(_, x)| **x)
394 .map(|(idx, _)| (idx + 1) as isize);
395
396 let neg_weeknum_iter = nth_from_end
397 .iter()
398 .enumerate()
399 .filter(|(_, x)| **x)
400 .map(|(idx, _)| -(idx as isize) - 1);
401
402 let mut weeknum_iter = pos_weeknum_iter.chain(neg_weeknum_iter);
403
404 if let Some(first_num) = weeknum_iter.next() {
405 write!(f, "[{first_num}")?;
406
407 for num in weeknum_iter {
408 write!(f, ",{num}")?;
409 }
410
411 write!(f, "]")?;
412 }
413 }
414
415 write_days_offset(f, *offset)?;
416 }
417 Self::Holiday { kind, offset } => {
418 write!(f, "{kind}")?;
419
420 if *offset != 0 {
421 write!(f, " {offset}")?;
422 }
423 }
424 }
425
426 Ok(())
427 }
428}
429
430#[derive(Clone, Copy, Debug, Hash, PartialEq, Eq)]
435pub enum HolidayKind {
436 Public,
437 School,
438}
439
440impl Display for HolidayKind {
441 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
442 match self {
443 Self::Public => write!(f, "PH"),
444 Self::School => write!(f, "SH"),
445 }
446 }
447}
448
449#[derive(Copy, Clone, Debug, Hash, PartialEq, Eq, PartialOrd, Ord)]
454pub struct WeekNum(pub u8);
455
456impl Deref for WeekNum {
457 type Target = u8;
458
459 fn deref(&self) -> &Self::Target {
460 &self.0
461 }
462}
463
464impl DerefMut for WeekNum {
465 fn deref_mut(&mut self) -> &mut Self::Target {
466 &mut self.0
467 }
468}
469
470#[derive(Clone, Debug, Hash, PartialEq, Eq)]
476pub struct WeekRange {
477 range: RangeInclusive<WeekNum>,
478 step: u8,
479}
480
481impl WeekRange {
482 pub fn new(range: RangeInclusive<WeekNum>, mut step: u8) -> Option<Self> {
484 if range.start() > range.end() {
485 return None;
486 }
487
488 if range.start().abs_diff(**range.end()) < step {
489 step = 1;
490 }
491
492 Some(Self { range, step })
493 }
494
495 pub fn into_parts(&self) -> (RangeInclusive<WeekNum>, u8) {
497 (self.range.clone(), self.step)
498 }
499}
500
501impl Display for WeekRange {
502 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
503 if self.range.start() == self.range.end() && self.step == 1 {
504 return write!(f, "{:02}", **self.range.start());
505 }
506
507 write!(f, "{:02}", **self.range.start())?;
508
509 if self.range.start() != self.range.end() {
510 write!(f, "-{:02}", **self.range.end())?;
511
512 if self.step != 1 {
513 write!(f, "/{}", self.step)?;
514 }
515 }
516
517 Ok(())
518 }
519}
520
521#[derive(Copy, Clone, Debug, Hash, Eq, Ord, PartialEq, PartialOrd)]
524pub enum Month {
525 January = 1,
526 February = 2,
527 March = 3,
528 April = 4,
529 May = 5,
530 June = 6,
531 July = 7,
532 August = 8,
533 September = 9,
534 October = 10,
535 November = 11,
536 December = 12,
537}
538
539impl Month {
540 #[inline]
541 pub fn next(self) -> Self {
542 let num = self as u8;
543 #[allow(clippy::unwrap_used)] ((num % 12) + 1).try_into().unwrap()
545 }
546
547 #[inline]
548 pub fn prev(self) -> Self {
549 let num = self as u8;
550 #[allow(clippy::unwrap_used)] (((num + 10) % 12) + 1).try_into().unwrap()
552 }
553
554 #[inline]
556 pub fn from_date(date: impl Datelike) -> Self {
557 match date.month() {
558 1 => Self::January,
559 2 => Self::February,
560 3 => Self::March,
561 4 => Self::April,
562 5 => Self::May,
563 6 => Self::June,
564 7 => Self::July,
565 8 => Self::August,
566 9 => Self::September,
567 10 => Self::October,
568 11 => Self::November,
569 12 => Self::December,
570 other => unreachable!("Unexpected month for date `{other}`"),
571 }
572 }
573
574 #[inline]
576 pub fn as_str(self) -> &'static str {
577 match self {
578 Self::January => "January",
579 Self::February => "February",
580 Self::March => "March",
581 Self::April => "April",
582 Self::May => "May",
583 Self::June => "June",
584 Self::July => "July",
585 Self::August => "August",
586 Self::September => "September",
587 Self::October => "October",
588 Self::November => "November",
589 Self::December => "December",
590 }
591 }
592}
593
594impl Display for Month {
595 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
596 write!(f, "{}", &self.as_str()[..3])
597 }
598}
599
600macro_rules! impl_convert_for_month {
601 ( $from_type: ty ) => {
602 impl TryFrom<$from_type> for Month {
603 type Error = InvalidMonth;
604
605 #[inline]
606 fn try_from(value: $from_type) -> Result<Self, Self::Error> {
607 let value: u8 = value.try_into().map_err(|_| InvalidMonth)?;
608
609 Ok(match value {
610 1 => Self::January,
611 2 => Self::February,
612 3 => Self::March,
613 4 => Self::April,
614 5 => Self::May,
615 6 => Self::June,
616 7 => Self::July,
617 8 => Self::August,
618 9 => Self::September,
619 10 => Self::October,
620 11 => Self::November,
621 12 => Self::December,
622 _ => return Err(InvalidMonth),
623 })
624 }
625 }
626
627 impl From<Month> for $from_type {
628 fn from(val: Month) -> Self {
629 val as _
630 }
631 }
632 };
633 ( $from_type: ty, $( $tail: tt )+ ) => {
634 impl_convert_for_month!($from_type);
635 impl_convert_for_month!($($tail)+);
636 };
637}
638
639impl_convert_for_month!(u8, u16, u32, u64, u128, i8, i16, i32, i64, i128, usize, isize);