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 u16);
105
106impl Deref for Year {
107 type Target = u16;
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)]
122pub struct YearRange {
123 pub range: RangeInclusive<Year>,
124 pub step: u16,
125}
126
127impl Display for YearRange {
128 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
129 write!(f, "{}", self.range.start().deref())?;
130
131 if self.range.start() != self.range.end() {
132 write!(f, "-{}", self.range.end().deref())?;
133
134 if self.step != 1 {
135 write!(f, "/{}", self.step)?;
136 }
137 }
138
139 Ok(())
140 }
141}
142
143#[derive(Clone, Debug, Hash, PartialEq, Eq)]
148pub enum MonthdayRange {
149 Month {
150 range: RangeInclusive<Month>,
151 year: Option<u16>,
152 },
153 Date {
154 start: (Date, DateOffset),
155 end: (Date, DateOffset),
156 },
157}
158
159impl Display for MonthdayRange {
160 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
161 match self {
162 Self::Month { range, year } => {
163 if let Some(year) = year {
164 write!(f, "{year}")?;
165 }
166
167 write!(f, "{}", range.start())?;
168
169 if range.start() != range.end() {
170 write!(f, "-{}", range.end())?;
171 }
172 }
173 Self::Date { start, end } => {
174 write!(f, "{}{}", start.0, start.1)?;
175
176 if start != end {
177 write!(f, "-{}{}", end.0, end.1)?;
178 }
179 }
180 }
181
182 Ok(())
183 }
184}
185
186#[derive(Clone, Copy, Debug, Hash, PartialEq, Eq)]
191pub enum Date {
192 Fixed {
193 year: Option<u16>,
194 month: Month,
195 day: u8,
196 },
197 Easter {
198 year: Option<u16>,
199 },
200}
201
202impl Date {
203 #[inline]
204 pub fn ymd(day: u8, month: Month, year: u16) -> Self {
205 Self::Fixed { day, month, year: Some(year) }
206 }
207
208 #[inline]
209 pub fn md(day: u8, month: Month) -> Self {
210 Self::Fixed { day, month, year: None }
211 }
212
213 #[inline]
214 pub fn has_year(&self) -> bool {
215 matches!(
216 self,
217 Self::Fixed { year: Some(_), .. } | Self::Easter { year: Some(_) }
218 )
219 }
220}
221
222impl Display for Date {
223 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
224 match self {
225 Date::Fixed { year, month, day } => {
226 if let Some(year) = year {
227 write!(f, "{year} ")?;
228 }
229
230 write!(f, "{month} {day}")?;
231 }
232 Date::Easter { year } => {
233 if let Some(year) = year {
234 write!(f, "{year} ")?;
235 }
236
237 write!(f, "easter")?;
238 }
239 }
240
241 Ok(())
242 }
243}
244
245#[derive(Clone, Copy, Debug, Default, Hash, PartialEq, Eq)]
250pub struct DateOffset {
251 pub wday_offset: WeekDayOffset,
252 pub day_offset: i64,
253}
254
255impl DateOffset {
256 #[inline]
257 pub fn apply(&self, mut date: NaiveDate) -> NaiveDate {
258 date += Duration::days(self.day_offset);
259
260 match self.wday_offset {
261 WeekDayOffset::None => {}
262 WeekDayOffset::Prev(target) => {
263 let diff = (7 + date.weekday().days_since(Weekday::Mon)
264 - target.days_since(Weekday::Mon))
265 % 7;
266
267 date -= Duration::days(diff.into());
268 debug_assert_eq!(date.weekday(), target);
269 }
270 WeekDayOffset::Next(target) => {
271 let diff = (7 + target.days_since(Weekday::Mon)
272 - date.weekday().days_since(Weekday::Mon))
273 % 7;
274
275 date += Duration::days(diff.into());
276 debug_assert_eq!(date.weekday(), target);
277 }
278 }
279
280 date
281 }
282}
283
284impl Display for DateOffset {
285 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
286 write!(f, "{}", self.wday_offset)?;
287 write_days_offset(f, self.day_offset)?;
288 Ok(())
289 }
290}
291
292#[derive(Clone, Copy, Debug, Hash, PartialEq, Eq)]
295pub enum WeekDayOffset {
296 None,
297 Next(Weekday),
298 Prev(Weekday),
299}
300
301impl Default for WeekDayOffset {
302 #[inline]
303 fn default() -> Self {
304 Self::None
305 }
306}
307
308impl Display for WeekDayOffset {
309 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
310 match self {
311 Self::None => {}
312 Self::Next(wday) => write!(f, "+{}", wday_str(*wday))?,
313 Self::Prev(wday) => write!(f, "-{}", wday_str(*wday))?,
314 }
315
316 Ok(())
317 }
318}
319
320#[derive(Clone, Debug, Hash, PartialEq, Eq)]
325pub enum WeekDayRange {
326 Fixed {
327 range: RangeInclusive<Weekday>,
328 offset: i64,
329 nth_from_start: [bool; 5],
330 nth_from_end: [bool; 5],
331 },
332 Holiday {
333 kind: HolidayKind,
334 offset: i64,
335 },
336}
337
338impl Display for WeekDayRange {
339 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
340 match self {
341 Self::Fixed { range, offset, nth_from_start, nth_from_end } => {
342 write!(f, "{}", wday_str(*range.start()))?;
343
344 if range.start() != range.end() {
345 write!(f, "-{}", wday_str(*range.end()))?;
346 }
347
348 if nth_from_start.contains(&false) || nth_from_end.contains(&false) {
349 let pos_weeknum_iter = nth_from_start
350 .iter()
351 .enumerate()
352 .filter(|(_, x)| **x)
353 .map(|(idx, _)| (idx + 1) as isize);
354
355 let neg_weeknum_iter = nth_from_end
356 .iter()
357 .enumerate()
358 .filter(|(_, x)| **x)
359 .map(|(idx, _)| -(idx as isize) - 1);
360
361 let mut weeknum_iter = pos_weeknum_iter.chain(neg_weeknum_iter);
362 write!(f, "[{}", weeknum_iter.next().unwrap())?;
363
364 for num in weeknum_iter {
365 write!(f, ",{num}")?;
366 }
367
368 write!(f, "]")?;
369 }
370
371 write_days_offset(f, *offset)?;
372 }
373 Self::Holiday { kind, offset } => {
374 write!(f, "{kind}")?;
375
376 if *offset != 0 {
377 write!(f, " {offset}")?;
378 }
379 }
380 }
381
382 Ok(())
383 }
384}
385
386#[derive(Clone, Copy, Debug, Hash, PartialEq, Eq)]
391pub enum HolidayKind {
392 Public,
393 School,
394}
395
396impl Display for HolidayKind {
397 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
398 match self {
399 Self::Public => write!(f, "PH"),
400 Self::School => write!(f, "SH"),
401 }
402 }
403}
404
405#[derive(Copy, Clone, Debug, Hash, PartialEq, Eq, PartialOrd, Ord)]
410pub struct WeekNum(pub u8);
411
412impl Deref for WeekNum {
413 type Target = u8;
414
415 fn deref(&self) -> &Self::Target {
416 &self.0
417 }
418}
419
420impl DerefMut for WeekNum {
421 fn deref_mut(&mut self) -> &mut Self::Target {
422 &mut self.0
423 }
424}
425
426#[derive(Clone, Debug, Hash, PartialEq, Eq)]
431pub struct WeekRange {
432 pub range: RangeInclusive<WeekNum>,
433 pub step: u8,
434}
435
436impl Display for WeekRange {
437 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
438 if self.range.start() == self.range.end() && self.step == 1 {
439 return write!(f, "{:02}", **self.range.start());
440 }
441
442 write!(f, "{:02}", **self.range.start())?;
443
444 if self.range.start() != self.range.end() {
445 write!(f, "-{:02}", **self.range.end())?;
446
447 if self.step != 1 {
448 write!(f, "/{}", self.step)?;
449 }
450 }
451
452 Ok(())
453 }
454}
455
456#[derive(Copy, Clone, Debug, Hash, Eq, Ord, PartialEq, PartialOrd)]
459pub enum Month {
460 January = 1,
461 February = 2,
462 March = 3,
463 April = 4,
464 May = 5,
465 June = 6,
466 July = 7,
467 August = 8,
468 September = 9,
469 October = 10,
470 November = 11,
471 December = 12,
472}
473
474impl Month {
475 #[inline]
476 pub fn next(self) -> Self {
477 let num = self as u8;
478 ((num % 12) + 1).try_into().unwrap()
479 }
480
481 #[inline]
482 pub fn prev(self) -> Self {
483 let num = self as u8;
484 (((num + 10) % 12) + 1).try_into().unwrap()
485 }
486
487 #[inline]
489 pub fn from_date(date: impl Datelike) -> Self {
490 match date.month() {
491 1 => Self::January,
492 2 => Self::February,
493 3 => Self::March,
494 4 => Self::April,
495 5 => Self::May,
496 6 => Self::June,
497 7 => Self::July,
498 8 => Self::August,
499 9 => Self::September,
500 10 => Self::October,
501 11 => Self::November,
502 12 => Self::December,
503 other => unreachable!("Unexpected month for date `{other}`"),
504 }
505 }
506
507 #[inline]
509 pub fn as_str(self) -> &'static str {
510 match self {
511 Self::January => "January",
512 Self::February => "February",
513 Self::March => "March",
514 Self::April => "April",
515 Self::May => "May",
516 Self::June => "June",
517 Self::July => "July",
518 Self::August => "August",
519 Self::September => "September",
520 Self::October => "October",
521 Self::November => "November",
522 Self::December => "December",
523 }
524 }
525}
526
527impl Display for Month {
528 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
529 write!(f, "{}", &self.as_str()[..3])
530 }
531}
532
533macro_rules! impl_convert_for_month {
534 ( $from_type: ty ) => {
535 impl TryFrom<$from_type> for Month {
536 type Error = InvalidMonth;
537
538 #[inline]
539 fn try_from(value: $from_type) -> Result<Self, Self::Error> {
540 let value: u8 = value.try_into().map_err(|_| InvalidMonth)?;
541
542 Ok(match value {
543 1 => Self::January,
544 2 => Self::February,
545 3 => Self::March,
546 4 => Self::April,
547 5 => Self::May,
548 6 => Self::June,
549 7 => Self::July,
550 8 => Self::August,
551 9 => Self::September,
552 10 => Self::October,
553 11 => Self::November,
554 12 => Self::December,
555 _ => return Err(InvalidMonth),
556 })
557 }
558 }
559
560 impl From<Month> for $from_type {
561 fn from(val: Month) -> Self {
562 val as _
563 }
564 }
565 };
566 ( $from_type: ty, $( $tail: tt )+ ) => {
567 impl_convert_for_month!($from_type);
568 impl_convert_for_month!($($tail)+);
569 };
570}
571
572impl_convert_for_month!(u8, u16, u32, u64, u128, i8, i16, i32, i64, i128, usize, isize);