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 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274
#![warn(missing_docs)]
//! # holiday
//!
//! A library for defining annually repeating dates and holidays
//!
//! ## Create a new Holiday
//!
//! A `Holiday` can be either a fixed date like 'April 2nd' or an nth weekday of a month, like '1st Friday in April'.
//!
//! ```rust
//! use holiday::*;
//! use chrono::{Weekday, NaiveDate};
//!
//! // Regular `fixed` holiday
//! let holiday = Holiday::new_fixed("April 2nd", April, 2);
//! assert_eq!(holiday.in_year(2021), NaiveDate::from_ymd(2021, 4, 2));
//! assert_eq!(holiday, NaiveDate::from_ymd(2021, 4, 2));
//! assert_eq!(holiday, NaiveDate::from_ymd(2022, 4, 2));
//!
//! // Pastover: First Friday in April, an `nth` holiday
//! let pastover = Holiday::new_nth("Pastover", First, Weekday::Fri, April);
//! assert_eq!(pastover.in_year(2021), NaiveDate::from_ymd(2021, 4, 2));
//! assert_eq!(pastover, NaiveDate::from_ymd(2021, 4, 2));
//! assert_eq!(pastover, NaiveDate::from_ymd(2022, 4, 1));
//! ```
pub use chrono::{Local, Datelike, NaiveDate, Date, DateTime, Weekday};
mod eq;
pub mod before_after;
pub mod holidays;
pub mod iter;
pub use before_after::*;
pub use iter::*;
use HolidayDate::*;
pub use NthWeekday::*;
pub use Month::*;
/// An annually repeating calendar date.
/// Can be either a fixed date (e.g., April 1) or an nth weekday of the month (e.g., 4th Thursday
/// in November)
#[derive(Debug, Clone, Copy)]
pub struct Holiday<S: ToString> {
name: S,
date: HolidayDate,
}
impl<S: ToString> Holiday<S> {
/// Creates a new fixed date holiday
pub fn new_fixed<M: Into<Month>>(name: S, month: M, day: u32) -> Self {
Holiday {
name,
date: HolidayDate::FixedDate(DayOfMonth { month: month.into(), day }),
}
}
/// Creates a new nth weekday of the month Holiday
pub fn new_nth<N: Into<NthWeekday>, M: Into<Month>>(name: S, nth: N, weekday: Weekday, month: M) -> Self {
Holiday {
name,
date: HolidayDate::NthDate(NthWeekdayOfMonth::new(nth, weekday, month)),
}
}
/// Returns a reference to the Name of the Holiday
pub fn name(&self) -> &S {
&self.name
}
/// Returns an iterator over all the occurrences of a given Holiday starting at the earliest
/// representable date.
pub fn iter(&self) -> HolidayIter<Self> {
self.into_iter()
}
/// Determine the date of a Holiday in a given year
pub fn in_year(&self, year: i32) -> NaiveDate {
self.after(&NaiveDate::from_ymd(year, 1, 1))
}
}
#[test]
fn holiday_in_year() {
assert_eq!(holidays::global::CHRISTMAS.in_year(2020), NaiveDate::from_ymd(2020, 12, 25));
assert_eq!(holidays::united_states::THANKSGIVING.in_year(2020), NaiveDate::from_ymd(2020, 11, 26));
assert_eq!(holidays::global::NEW_YEARS_DAY.in_year(2020), NaiveDate::from_ymd(2020, 1, 1));
assert_eq!(holidays::global::NEW_YEARS_EVE.in_year(2020), NaiveDate::from_ymd(2020, 12, 31));
}
/// Holiday Date type
#[derive(Debug, PartialEq, Eq, Clone, Copy)]
pub enum HolidayDate {
/// Fixed date. Example: "October 31"
FixedDate(DayOfMonth),
/// Relative weekday in a month. Example: "4th Thursday in November"
NthDate(NthWeekdayOfMonth),
}
impl HolidayDate {
/// Returns an iterator over the ocurrences of the HolidayDate
pub fn iter(&self) -> HolidayIter<Self> {
self.into_iter()
}
}
/// A fixed day of the month (e.g.: March 31)
#[derive(Debug, PartialEq, Eq, Clone, Copy)]
pub struct DayOfMonth {
/// The day of the month
pub day: u32,
/// The month (January = 1)
pub month: Month,
}
impl DayOfMonth {
/// Create a new DayOfMonth
pub fn new<M: Into<Month>>(day: u32, month: M) -> Self {
DayOfMonth { day, month: month.into() }
}
/// Returns an iterator over the ocurrences of the DayOfMonth
pub fn iter(&self) -> HolidayIter<Self> {
self.into_iter()
}
}
/// Nth weekday of a month (e.g.: Second Tuesday in October)
#[derive(Debug, PartialEq, Eq, Clone, Copy)]
pub struct NthWeekdayOfMonth {
nth: NthWeekday,
weekday: Weekday,
month: Month,
}
impl NthWeekdayOfMonth {
/// Creates a new NthWeekdayOfMonth
pub fn new<N: Into<NthWeekday>, M: Into<Month>>(nth: N, weekday: Weekday, month: M) -> Self {
NthWeekdayOfMonth {
nth: nth.into(),
weekday,
month: month.into(),
}
}
/// Returns an iterator over the ocurrences of the NthWeekdayOfMonth
pub fn iter(&self) -> HolidayIter<Self> {
self.into_iter()
}
}
impl From<NaiveDate> for NthWeekdayOfMonth {
fn from(date: NaiveDate) -> Self {
let mut nth = 0;
let mut loop_date = date.clone().with_day(1).expect("invalid day of month");
loop {
if loop_date.weekday() == date.weekday() {
nth += 1;
}
if loop_date >= date {
break;
}
loop_date = loop_date.succ();
}
NthWeekdayOfMonth {
nth: nth.into(),
weekday: date.weekday(),
month: date.month().into(),
}
}
}
/// The nth ocurrence of a weekday in a month.
///
/// Using the `Fifth` explicitly may panic if you try
/// to create a date with it, as some months do not have 5 ocurrences of a given weekday.
#[allow(missing_docs)]
#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Copy, Clone)]
pub enum NthWeekday {
First = 1,
Second = 2,
Third = 3,
Fourth = 4,
Fifth = 5,
Last = 6,
}
impl From<u32> for NthWeekday {
fn from(u: u32) -> NthWeekday {
use NthWeekday::*;
match u {
0 => panic!("value must be non-zero"),
1 => First,
2 => Second,
3 => Third,
4 => Fourth,
5 => Fifth,
_ => Last,
}
}
}
impl From<NthWeekday> for u32 {
fn from(nth: NthWeekday) -> Self {
nth as u32
}
}
/// A convenience enum for specifiying the month (January = 1)
#[allow(missing_docs)]
#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Copy, Clone)]
pub enum Month {
January = 1,
February = 2,
March = 3,
April = 4,
May = 5,
June = 6,
July = 7,
August = 8,
September = 9,
October = 10,
November = 11,
December = 12,
}
impl Month {
/// Get the month as a `u32` where January = 0
pub fn from_zero(&self) -> u32 {
*self as u32 - 1
}
}
impl From<u32> for Month {
fn from(u: u32) -> Self {
match u {
1 => January,
2 => February,
3 => March,
4 => April,
5 => May,
6 => June,
7 => July,
8 => August,
9 => September,
10 => October,
11 => November,
12 => December,
u => panic!("Invalid month: '{}'", u),
}
}
}
impl From<Month> for u32 {
fn from(m: Month) -> Self {
m as u32
}
}
#[test]
fn tgives_nth_weekday_of_month() {
let tgives = NthWeekdayOfMonth::new(Fourth, Weekday::Thu, 11);
dbg!(tgives.after_today());
dbg!(tgives.before_today());
dbg!(NthWeekdayOfMonth::from(NaiveDate::from_ymd(2020, 6, 8)));
}