use date_tuple::DateTuple;
use date_utils;
use regex::Regex;
use std::cmp::Ordering;
use std::convert::From;
use std::fmt;
use std::str::FromStr;
const MONTH_STRINGS: [&str; 12] = [
"Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec",
];
pub type Month = MonthTuple;
#[derive(PartialEq, Eq, Debug, Copy, Clone)]
pub struct MonthTuple {
y: u16,
m: u8,
}
impl MonthTuple {
pub fn new(y: u16, m: u8) -> Result<MonthTuple, String> {
if 1 <= m && m <= 12 {
if y <= 9999 {
Ok(MonthTuple { y, m })
} else {
Err(format!(
"Invalid year in MonthTuple: {:?}\nYear must be <= 9999.",
MonthTuple { y, m }
))
}
} else {
Err(format!(
"Invalid month in MonthTuple: {:?}\nMonth must be between 1 and 12; Note that months are ONE-BASED since version 2.0.0.",
MonthTuple { y, m }
))
}
}
pub fn this_month() -> MonthTuple {
date_utils::now_as_monthtuple()
}
pub fn get_year(&self) -> u16 {
self.y
}
pub fn get_month(&self) -> u8 {
self.m
}
pub fn next_month(self) -> MonthTuple {
if self.y == 9999 && self.m == 12 {
return self;
}
if self.m == 12 {
MonthTuple {
y: self.y + 1,
m: 1,
}
} else {
MonthTuple {
y: self.y,
m: self.m + 1,
}
}
}
pub fn previous_month(self) -> MonthTuple {
if self.y == 0 && self.m == 1 {
return self;
}
if self.m == 1 {
MonthTuple {
y: self.y - 1,
m: 12,
}
} else {
MonthTuple {
y: self.y,
m: self.m - 1,
}
}
}
pub fn add_months(&mut self, months: u32) {
for _ in 0..months {
*self = self.next_month();
}
}
pub fn subtract_months(&mut self, months: u32) {
for _ in 0..months {
*self = self.previous_month();
}
}
pub fn add_years(&mut self, years: u16) {
let mut new_years = self.y + years;
if new_years > 9999 {
new_years = 9999;
}
self.y = new_years;
}
pub fn subtract_years(&mut self, years: u16) {
let mut new_years = self.y as i32 - years as i32;
if new_years < 0 {
new_years = 0;
}
self.y = new_years as u16;
}
pub fn to_readable_string(&self) -> String {
match MONTH_STRINGS.iter().skip(self.m as usize - 1).next() {
Some(s) => return format!("{} {:04}", s, self.y),
None => panic!("Invalid MonthTuple: {:?}", self),
}
}
}
impl fmt::Display for MonthTuple {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "{:04}-{:02}", self.y, self.m)
}
}
impl FromStr for MonthTuple {
type Err = String;
fn from_str(s: &str) -> Result<MonthTuple, Self::Err> {
let valid_format = Regex::new(r"^\d{4}-\d{2}$").unwrap();
let legacy_format = Regex::new(r"^\d{6}$").unwrap();
if valid_format.is_match(s) {
match MonthTuple::new(
u16::from_str(&s[0..4]).unwrap(),
u8::from_str(&s[5..7]).unwrap(),
) {
Ok(m) => Ok(m),
Err(e) => Err(format!("Invalid month passed to from_str: {}", e)),
}
} else if legacy_format.is_match(s) {
let (s1, s2) = s.split_at(4);
match MonthTuple::new(u16::from_str(s1).unwrap(), u8::from_str(s2).unwrap()) {
Ok(m) => Ok(m),
Err(e) => Err(format!("Invalid month passed to from_str: {}", e)),
}
} else {
Err(format!(
"Invalid str formatting of MonthTuple: {}\nExpects a string formatted like 2018-11",
s
))
}
}
}
impl PartialOrd for MonthTuple {
fn partial_cmp(&self, other: &MonthTuple) -> Option<Ordering> {
if self.y == other.y {
self.m.partial_cmp(&other.m)
} else {
self.y.partial_cmp(&other.y)
}
}
}
impl Ord for MonthTuple {
fn cmp(&self, other: &MonthTuple) -> Ordering {
if self.y == other.y {
self.m.cmp(&other.m)
} else {
self.y.cmp(&other.y)
}
}
}
impl From<DateTuple> for MonthTuple {
fn from(date: DateTuple) -> Self {
MonthTuple {
y: date.get_year(),
m: date.get_month(),
}
}
}
#[cfg(test)]
mod tests {
#[test]
fn test_component_too_large() {
assert!(super::MonthTuple::new(2000, 12).is_ok());
assert!(super::MonthTuple::new(2000, 13).is_err());
assert!(super::MonthTuple::new(10000, 5).is_err());
}
#[test]
fn test_next_month() {
let tuple1 = super::MonthTuple::new(2000, 5).unwrap();
let tuple2 = super::MonthTuple::new(2000, 12).unwrap();
let tuple3 = super::MonthTuple::new(9999, 12).unwrap();
assert_eq!(super::MonthTuple { y: 2000, m: 6 }, tuple1.next_month());
assert_eq!(super::MonthTuple { y: 2001, m: 1 }, tuple2.next_month());
assert_eq!(tuple3, tuple3.next_month());
}
#[test]
fn test_previous_month() {
let tuple1 = super::MonthTuple::new(2000, 5).unwrap();
let tuple2 = super::MonthTuple::new(2000, 1).unwrap();
let tuple3 = super::MonthTuple::new(0, 1).unwrap();
assert_eq!(super::MonthTuple { y: 2000, m: 4 }, tuple1.previous_month());
assert_eq!(
super::MonthTuple { y: 1999, m: 12 },
tuple2.previous_month()
);
assert_eq!(tuple3, tuple3.previous_month());
}
#[test]
fn test_to_readable_string() {
let tuple = super::MonthTuple::new(2000, 5).unwrap();
assert_eq!(String::from("May 2000"), tuple.to_readable_string());
}
#[test]
#[should_panic]
fn test_to_readable_string_panic() {
let tuple = super::MonthTuple { y: 2000, m: 13 };
tuple.to_readable_string();
}
#[test]
fn test_to_string() {
let tuple = super::MonthTuple::new(2000, 5).unwrap();
assert_eq!(String::from("2000-05"), tuple.to_string());
}
#[test]
fn test_equals() {
let tuple1 = super::MonthTuple::new(2000, 5).unwrap();
let tuple2 = super::MonthTuple::new(2000, 5).unwrap();
assert_eq!(tuple1, tuple2);
}
#[test]
fn test_comparisons() {
let tuple1 = super::MonthTuple::new(2000, 5).unwrap();
let tuple2 = super::MonthTuple::new(2000, 5).unwrap();
let tuple3 = super::MonthTuple::new(2000, 6).unwrap();
let tuple4 = super::MonthTuple::new(2001, 1).unwrap();
assert!(tuple1 <= tuple2);
assert!(!(tuple1 < tuple2));
assert!(tuple1 >= tuple2);
assert!(tuple1 < tuple3);
assert!(tuple3 < tuple4);
assert!(tuple4 > tuple2);
}
#[test]
fn test_from_date() {
let date = ::date_tuple::DateTuple::new(2000, 5, 10).unwrap();
assert_eq!(
super::MonthTuple { y: 2000, m: 5 },
super::MonthTuple::from(date)
);
}
#[test]
fn test_from_string() {
let tuple = super::MonthTuple::new(2000, 5).unwrap();
assert_eq!(tuple, str::parse("2000-05").unwrap());
assert_eq!(tuple, str::parse("200005").unwrap());
assert!(str::parse::<super::MonthTuple>("2000-15").is_err());
assert!(str::parse::<super::MonthTuple>("200015").is_err());
assert!(str::parse::<super::MonthTuple>("200O05").is_err());
}
#[test]
fn test_add_months() {
let mut tuple1 = super::MonthTuple::new(2000, 6).unwrap();
let tuple1_orig = super::MonthTuple::new(2000, 6).unwrap();
let mut tuple2 = super::MonthTuple::new(2000, 12).unwrap();
let tuple2_orig = super::MonthTuple::new(2000, 12).unwrap();
tuple1.add_months(1);
assert_eq!(tuple1, tuple1_orig.next_month());
tuple2.add_months(2);
assert_eq!(tuple2, tuple2_orig.next_month().next_month());
}
#[test]
fn test_subtract_months() {
let mut tuple1 = super::MonthTuple::new(2000, 6).unwrap();
let tuple1_orig = super::MonthTuple::new(2000, 6).unwrap();
let mut tuple2 = super::MonthTuple::new(2000, 12).unwrap();
let tuple2_orig = super::MonthTuple::new(2000, 12).unwrap();
tuple1.subtract_months(1);
assert_eq!(tuple1, tuple1_orig.previous_month());
tuple2.subtract_months(2);
assert_eq!(tuple2, tuple2_orig.previous_month().previous_month());
}
}