use core::fmt;
use std::error;
use chrono::{Duration, NaiveDate, NaiveDateTime, Utc};
mod iter;
pub use iter::DateRangeIterator;
#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)]
pub struct DateRange {
since_: NaiveDate,
until_: NaiveDate,
}
impl Default for DateRange {
fn default() -> Self {
Self {
since_: NaiveDate::from_ymd_opt(1970, 1, 1).expect(""),
until_: NaiveDate::from_ymd_opt(1970, 1, 1).expect(""),
}
}
}
impl DateRange {
pub fn new(since: NaiveDate, until: NaiveDate) -> Result<Self, Error> {
if since > until {
return Err(Error::SinceShouldLtUntil);
}
Ok(Self {
since_: since,
until_: until,
})
}
pub fn from_timestamp(
since: i64,
mut until: i64,
configuration: impl Into<Option<DateRangeFromTimestampConfiguration>>,
) -> Result<Self, Error> {
let configuration = configuration.into().unwrap_or_default();
if since > until {
return Err(Error::SinceShouldLtUntil);
}
if configuration.now_gteq_until {
let now = Utc::now().timestamp();
if until > now {
until = now;
if since > until {
return Err(Error::SinceShouldLtUntil);
}
}
}
let since = NaiveDateTime::from_timestamp_opt(since, 0).expect("");
let until = NaiveDateTime::from_timestamp_opt(until, 0).expect("");
let mut since = since.date();
let until = until.date();
if (until - since).num_days() > configuration.max_interval.num_days() {
since = until - configuration.max_interval;
}
Ok(Self {
since_: since,
until_: until,
})
}
pub fn since(&self) -> NaiveDate {
self.since_.to_owned()
}
pub fn until(&self) -> NaiveDate {
self.until_.to_owned()
}
pub fn since_datetime(&self) -> NaiveDateTime {
self.since().and_hms_opt(0, 0, 0).expect("")
}
pub fn until_datetime(&self) -> NaiveDateTime {
self.until().and_hms_opt(23, 59, 59).expect("")
}
pub fn prev(&self) -> Self {
let prev_until = self.since() - Duration::days(1);
let len = self.until() - self.since();
Self {
since_: prev_until - len,
until_: prev_until,
}
}
pub fn next(&self) -> Self {
let next_since = self.until() + Duration::days(1);
let len = self.until() - self.since();
Self {
since_: next_since,
until_: next_since + len,
}
}
pub fn num_days(&self) -> usize {
(self.until() - self.since()).num_days() as usize
}
}
#[derive(Debug, Copy, Clone)]
pub struct DateRangeFromTimestampConfiguration {
pub now_gteq_until: bool,
pub max_interval: Duration,
}
impl Default for DateRangeFromTimestampConfiguration {
fn default() -> Self {
Self {
now_gteq_until: true,
max_interval: Duration::days(2 * 365), }
}
}
impl DateRangeFromTimestampConfiguration {
pub fn new() -> Self {
Self::default()
}
}
#[derive(Debug, Copy, Clone)]
pub struct DateTimeRange {
since_: NaiveDateTime,
until_: NaiveDateTime,
}
impl DateTimeRange {
pub fn new(since: NaiveDateTime, until: NaiveDateTime) -> Result<Self, Error> {
if since > until {
return Err(Error::SinceShouldLtUntil);
}
Ok(Self {
since_: since,
until_: until,
})
}
pub fn since(&self) -> NaiveDateTime {
self.since_.to_owned()
}
pub fn until(&self) -> NaiveDateTime {
self.until_.to_owned()
}
}
impl From<&DateRange> for DateTimeRange {
fn from(dr: &DateRange) -> Self {
Self {
since_: dr.since_datetime(),
until_: dr.until_datetime(),
}
}
}
impl From<DateRange> for DateTimeRange {
fn from(dr: DateRange) -> Self {
Self {
since_: dr.since_datetime(),
until_: dr.until_datetime(),
}
}
}
#[derive(Debug)]
pub enum Error {
SinceShouldLtUntil,
}
impl fmt::Display for Error {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{self:?}")
}
}
impl error::Error for Error {}
#[cfg(test)]
mod tests {
use super::*;
use std::error;
#[test]
fn test_date_range_default() {
assert_eq!(
DateRange::default(),
DateRange::new("1970-01-01".parse().unwrap(), "1970-01-01".parse().unwrap()).unwrap()
)
}
#[test]
fn test_date_range_new() -> Result<(), Box<dyn error::Error>> {
match DateRange::new("2021-08-02".parse().unwrap(), "2021-08-02".parse().unwrap()) {
Ok(_) => {}
Err(err) => {
panic!("{}", err)
}
}
match DateRange::new("2021-08-02".parse().unwrap(), "2021-08-01".parse().unwrap()) {
Ok(_) => panic!(),
Err(Error::SinceShouldLtUntil) => {}
}
Ok(())
}
#[test]
fn test_date_range_from_timestamp() {
let now = Utc::now();
let date_range = DateRange::from_timestamp(
(now - Duration::days(7)).timestamp(),
(now + Duration::days(3)).timestamp(),
None,
)
.unwrap();
assert_eq!(
date_range.since(),
now.naive_utc().date() - Duration::days(7)
);
assert_eq!(date_range.until(), now.naive_utc().date());
let mut configuration = DateRangeFromTimestampConfiguration::new();
configuration.now_gteq_until = false;
let date_range = DateRange::from_timestamp(
(now - Duration::days(7)).timestamp(),
(now + Duration::days(3)).timestamp(),
configuration,
)
.unwrap();
assert_eq!(
date_range.since(),
now.naive_utc().date() - Duration::days(7)
);
assert_eq!(
date_range.until(),
now.naive_utc().date() + Duration::days(3)
);
let date_range = DateRange::from_timestamp(
(now - Duration::days(10)).timestamp(),
now.timestamp(),
Some(DateRangeFromTimestampConfiguration {
now_gteq_until: true,
max_interval: Duration::days(7),
}),
)
.unwrap();
assert_eq!(
date_range.since(),
now.naive_utc().date() - Duration::days(7)
);
assert_eq!(date_range.until(), now.naive_utc().date());
}
#[test]
fn test_date_range_since_datetime_and_until_datetime() {
let date_range =
DateRange::new("2021-08-09".parse().unwrap(), "2021-08-15".parse().unwrap()).unwrap();
assert_eq!(
date_range.since_datetime(),
"2021-08-09T00:00:00".parse().unwrap()
);
assert_eq!(
date_range.until_datetime(),
"2021-08-15T23:59:59".parse().unwrap()
);
}
#[test]
fn test_date_range_prev_and_next() {
let date_range =
DateRange::new("2021-08-09".parse().unwrap(), "2021-08-15".parse().unwrap()).unwrap();
let prev_date_range = date_range.prev();
assert_eq!(prev_date_range.since(), "2021-08-02".parse().unwrap());
assert_eq!(prev_date_range.until(), "2021-08-08".parse().unwrap());
let next_date_range = date_range.next();
assert_eq!(next_date_range.since(), "2021-08-16".parse().unwrap());
assert_eq!(next_date_range.until(), "2021-08-22".parse().unwrap());
}
#[test]
fn test_date_range_num_days() {
let date_range =
DateRange::new("2022-02-23".parse().unwrap(), "2022-02-23".parse().unwrap()).unwrap();
assert_eq!(date_range.num_days(), 0);
let date_range =
DateRange::new("2022-02-21".parse().unwrap(), "2022-02-23".parse().unwrap()).unwrap();
assert_eq!(date_range.num_days(), 2);
let now = Utc::now();
let date_range =
DateRange::from_timestamp((now - Duration::days(7)).timestamp(), now.timestamp(), None)
.unwrap();
assert_eq!(date_range.num_days(), 7);
}
}