| /// The datetime coordinates |
| use chrono::{Date, DateTime, Datelike, Duration, NaiveDate, NaiveDateTime, TimeZone, Timelike}; |
| use std::ops::{Add, Range, Sub}; |
| |
| use crate::coord::ranged1d::{ |
| AsRangedCoord, DefaultFormatting, DiscreteRanged, KeyPointHint, NoDefaultFormatting, Ranged, |
| ValueFormatter, |
| }; |
| |
| /// The trait that describe some time value. This is the uniformed abstraction that works |
| /// for both Date, DateTime and Duration, etc. |
| pub trait TimeValue: Eq { |
| type DateType: Datelike + PartialOrd; |
| |
| /// Returns the date that is no later than the time |
| fn date_floor(&self) -> Self::DateType; |
| /// Returns the date that is no earlier than the time |
| fn date_ceil(&self) -> Self::DateType; |
| /// Returns the maximum value that is earlier than the given date |
| fn earliest_after_date(date: Self::DateType) -> Self; |
| /// Returns the duration between two time value |
| fn subtract(&self, other: &Self) -> Duration; |
| /// Instantiate a date type for current time value; |
| fn ymd(&self, year: i32, month: u32, date: u32) -> Self::DateType; |
| /// Cast current date type into this type |
| fn from_date(date: Self::DateType) -> Self; |
| |
| /// Map the coord spec |
| fn map_coord(value: &Self, begin: &Self, end: &Self, limit: (i32, i32)) -> i32 { |
| let total_span = end.subtract(begin); |
| let value_span = value.subtract(begin); |
| |
| // First, lets try the nanoseconds precision |
| if let Some(total_ns) = total_span.num_nanoseconds() { |
| if let Some(value_ns) = value_span.num_nanoseconds() { |
| return (f64::from(limit.1 - limit.0) * value_ns as f64 / total_ns as f64) as i32 |
| + limit.0; |
| } |
| } |
| |
| // Yes, converting them to floating point may lose precision, but this is Ok. |
| // If it overflows, it means we have a time span nearly 300 years, we are safe to ignore the |
| // portion less than 1 day. |
| let total_days = total_span.num_days() as f64; |
| let value_days = value_span.num_days() as f64; |
| |
| (f64::from(limit.1 - limit.0) * value_days / total_days) as i32 + limit.0 |
| } |
| } |
| |
| impl TimeValue for NaiveDate { |
| type DateType = NaiveDate; |
| fn date_floor(&self) -> NaiveDate { |
| *self |
| } |
| fn date_ceil(&self) -> NaiveDate { |
| *self |
| } |
| fn earliest_after_date(date: NaiveDate) -> Self { |
| date |
| } |
| fn subtract(&self, other: &NaiveDate) -> Duration { |
| *self - *other |
| } |
| |
| fn ymd(&self, year: i32, month: u32, date: u32) -> Self::DateType { |
| NaiveDate::from_ymd(year, month, date) |
| } |
| |
| fn from_date(date: Self::DateType) -> Self { |
| date |
| } |
| } |
| |
| impl<Z: TimeZone> TimeValue for Date<Z> { |
| type DateType = Date<Z>; |
| fn date_floor(&self) -> Date<Z> { |
| self.clone() |
| } |
| fn date_ceil(&self) -> Date<Z> { |
| self.clone() |
| } |
| fn earliest_after_date(date: Date<Z>) -> Self { |
| date |
| } |
| fn subtract(&self, other: &Date<Z>) -> Duration { |
| self.clone() - other.clone() |
| } |
| |
| fn ymd(&self, year: i32, month: u32, date: u32) -> Self::DateType { |
| self.timezone().ymd(year, month, date) |
| } |
| |
| fn from_date(date: Self::DateType) -> Self { |
| date |
| } |
| } |
| |
| impl<Z: TimeZone> TimeValue for DateTime<Z> { |
| type DateType = Date<Z>; |
| fn date_floor(&self) -> Date<Z> { |
| self.date() |
| } |
| fn date_ceil(&self) -> Date<Z> { |
| if self.time().num_seconds_from_midnight() > 0 { |
| self.date() + Duration::days(1) |
| } else { |
| self.date() |
| } |
| } |
| fn earliest_after_date(date: Date<Z>) -> DateTime<Z> { |
| date.and_hms(0, 0, 0) |
| } |
| |
| fn subtract(&self, other: &DateTime<Z>) -> Duration { |
| self.clone() - other.clone() |
| } |
| |
| fn ymd(&self, year: i32, month: u32, date: u32) -> Self::DateType { |
| self.timezone().ymd(year, month, date) |
| } |
| |
| fn from_date(date: Self::DateType) -> Self { |
| date.and_hms(0, 0, 0) |
| } |
| } |
| |
| impl TimeValue for NaiveDateTime { |
| type DateType = NaiveDate; |
| fn date_floor(&self) -> NaiveDate { |
| self.date() |
| } |
| fn date_ceil(&self) -> NaiveDate { |
| if self.time().num_seconds_from_midnight() > 0 { |
| self.date() + Duration::days(1) |
| } else { |
| self.date() |
| } |
| } |
| fn earliest_after_date(date: NaiveDate) -> NaiveDateTime { |
| date.and_hms(0, 0, 0) |
| } |
| |
| fn subtract(&self, other: &NaiveDateTime) -> Duration { |
| *self - *other |
| } |
| |
| fn ymd(&self, year: i32, month: u32, date: u32) -> Self::DateType { |
| NaiveDate::from_ymd(year, month, date) |
| } |
| |
| fn from_date(date: Self::DateType) -> Self { |
| date.and_hms(0, 0, 0) |
| } |
| } |
| |
| /// The ranged coordinate for date |
| #[derive(Clone)] |
| pub struct RangedDate<D: Datelike>(D, D); |
| |
| impl<D: Datelike> From<Range<D>> for RangedDate<D> { |
| fn from(range: Range<D>) -> Self { |
| Self(range.start, range.end) |
| } |
| } |
| |
| impl<D> Ranged for RangedDate<D> |
| where |
| D: Datelike + TimeValue + Sub<D, Output = Duration> + Add<Duration, Output = D> + Clone, |
| { |
| type FormatOption = DefaultFormatting; |
| type ValueType = D; |
| |
| fn range(&self) -> Range<D> { |
| self.0.clone()..self.1.clone() |
| } |
| |
| fn map(&self, value: &Self::ValueType, limit: (i32, i32)) -> i32 { |
| TimeValue::map_coord(value, &self.0, &self.1, limit) |
| } |
| |
| fn key_points<HintType: KeyPointHint>(&self, hint: HintType) -> Vec<Self::ValueType> { |
| let max_points = hint.max_num_points(); |
| let mut ret = vec![]; |
| |
| let total_days = (self.1.clone() - self.0.clone()).num_days(); |
| let total_weeks = (self.1.clone() - self.0.clone()).num_weeks(); |
| |
| if total_days > 0 && total_days as usize <= max_points { |
| for day_idx in 0..=total_days { |
| ret.push(self.0.clone() + Duration::days(day_idx)); |
| } |
| return ret; |
| } |
| |
| if total_weeks > 0 && total_weeks as usize <= max_points { |
| for day_idx in 0..=total_weeks { |
| ret.push(self.0.clone() + Duration::weeks(day_idx)); |
| } |
| return ret; |
| } |
| |
| let week_per_point = ((total_weeks as f64) / (max_points as f64)).ceil() as usize; |
| |
| for idx in 0..=(total_weeks as usize / week_per_point) { |
| ret.push(self.0.clone() + Duration::weeks((idx * week_per_point) as i64)); |
| } |
| |
| ret |
| } |
| } |
| |
| impl<D> DiscreteRanged for RangedDate<D> |
| where |
| D: Datelike + TimeValue + Sub<D, Output = Duration> + Add<Duration, Output = D> + Clone, |
| { |
| fn size(&self) -> usize { |
| ((self.1.clone() - self.0.clone()).num_days().max(-1) + 1) as usize |
| } |
| |
| fn index_of(&self, value: &D) -> Option<usize> { |
| let ret = (value.clone() - self.0.clone()).num_days(); |
| if ret < 0 { |
| return None; |
| } |
| Some(ret as usize) |
| } |
| |
| fn from_index(&self, index: usize) -> Option<D> { |
| Some(self.0.clone() + Duration::days(index as i64)) |
| } |
| } |
| |
| impl<Z: TimeZone> AsRangedCoord for Range<Date<Z>> { |
| type CoordDescType = RangedDate<Date<Z>>; |
| type Value = Date<Z>; |
| } |
| |
| impl AsRangedCoord for Range<NaiveDate> { |
| type CoordDescType = RangedDate<NaiveDate>; |
| type Value = NaiveDate; |
| } |
| |
| /// Indicates the coord has a monthly resolution |
| /// |
| /// Note: since month doesn't have a constant duration. |
| /// We can't use a simple granularity to describe it. Thus we have |
| /// this axis decorator to make it yield monthly key-points. |
| #[derive(Clone)] |
| pub struct Monthly<T: TimeValue>(Range<T>); |
| |
| impl<T: TimeValue + Datelike + Clone> ValueFormatter<T> for Monthly<T> { |
| fn format(value: &T) -> String { |
| format!("{}-{}", value.year(), value.month()) |
| } |
| } |
| |
| impl<T: TimeValue + Clone> Monthly<T> { |
| fn bold_key_points<H: KeyPointHint>(&self, hint: &H) -> Vec<T> { |
| let max_points = hint.max_num_points(); |
| let start_date = self.0.start.date_ceil(); |
| let end_date = self.0.end.date_floor(); |
| |
| let mut start_year = start_date.year(); |
| let mut start_month = start_date.month(); |
| let start_day = start_date.day(); |
| |
| let end_year = end_date.year(); |
| let end_month = end_date.month(); |
| |
| if start_day != 1 { |
| start_month += 1; |
| if start_month == 13 { |
| start_month = 1; |
| start_year += 1; |
| } |
| } |
| |
| let total_month = (end_year - start_year) * 12 + end_month as i32 - start_month as i32; |
| |
| fn generate_key_points<T: TimeValue>( |
| mut start_year: i32, |
| mut start_month: i32, |
| end_year: i32, |
| end_month: i32, |
| step: u32, |
| builder: &T, |
| ) -> Vec<T> { |
| let mut ret = vec![]; |
| while end_year > start_year || (end_year == start_year && end_month >= start_month) { |
| ret.push(T::earliest_after_date(builder.ymd( |
| start_year, |
| start_month as u32, |
| 1, |
| ))); |
| start_month += step as i32; |
| |
| if start_month >= 13 { |
| start_year += start_month / 12; |
| start_month %= 12; |
| } |
| } |
| |
| ret |
| } |
| |
| if total_month as usize <= max_points { |
| // Monthly |
| return generate_key_points( |
| start_year, |
| start_month as i32, |
| end_year, |
| end_month as i32, |
| 1, |
| &self.0.start, |
| ); |
| } else if total_month as usize <= max_points * 3 { |
| // Quarterly |
| return generate_key_points( |
| start_year, |
| start_month as i32, |
| end_year, |
| end_month as i32, |
| 3, |
| &self.0.start, |
| ); |
| } else if total_month as usize <= max_points * 6 { |
| // Biyearly |
| return generate_key_points( |
| start_year, |
| start_month as i32, |
| end_year, |
| end_month as i32, |
| 6, |
| &self.0.start, |
| ); |
| } |
| |
| // Otherwise we could generate the yearly keypoints |
| generate_yearly_keypoints( |
| max_points, |
| start_year, |
| start_month, |
| end_year, |
| end_month, |
| &self.0.start, |
| ) |
| } |
| } |
| |
| impl<T: TimeValue + Clone> Ranged for Monthly<T> |
| where |
| Range<T>: AsRangedCoord<Value = T>, |
| { |
| type FormatOption = NoDefaultFormatting; |
| type ValueType = T; |
| |
| fn range(&self) -> Range<T> { |
| self.0.start.clone()..self.0.end.clone() |
| } |
| |
| fn map(&self, value: &Self::ValueType, limit: (i32, i32)) -> i32 { |
| T::map_coord(value, &self.0.start, &self.0.end, limit) |
| } |
| |
| fn key_points<HintType: KeyPointHint>(&self, hint: HintType) -> Vec<Self::ValueType> { |
| if hint.weight().allow_light_points() && self.size() <= hint.bold_points() * 2 { |
| let coord: <Range<T> as AsRangedCoord>::CoordDescType = self.0.clone().into(); |
| let normal = coord.key_points(hint.max_num_points()); |
| return normal; |
| } |
| self.bold_key_points(&hint) |
| } |
| } |
| |
| impl<T: TimeValue + Clone> DiscreteRanged for Monthly<T> |
| where |
| Range<T>: AsRangedCoord<Value = T>, |
| { |
| fn size(&self) -> usize { |
| let (start_year, start_month) = { |
| let ceil = self.0.start.date_ceil(); |
| (ceil.year(), ceil.month()) |
| }; |
| let (end_year, end_month) = { |
| let floor = self.0.end.date_floor(); |
| (floor.year(), floor.month()) |
| }; |
| ((end_year - start_year).max(0) * 12 |
| + (1 - start_month as i32) |
| + (end_month as i32 - 1) |
| + 1) |
| .max(0) as usize |
| } |
| |
| fn index_of(&self, value: &T) -> Option<usize> { |
| let this_year = value.date_floor().year(); |
| let this_month = value.date_floor().month(); |
| |
| let start_year = self.0.start.date_ceil().year(); |
| let start_month = self.0.start.date_ceil().month(); |
| |
| let ret = (this_year - start_year).max(0) * 12 |
| + (1 - start_month as i32) |
| + (this_month as i32 - 1); |
| if ret >= 0 { |
| return Some(ret as usize); |
| } |
| None |
| } |
| |
| fn from_index(&self, index: usize) -> Option<T> { |
| if index == 0 { |
| return Some(T::earliest_after_date(self.0.start.date_ceil())); |
| } |
| let index_from_start_year = index + (self.0.start.date_ceil().month() - 1) as usize; |
| let year = self.0.start.date_ceil().year() + index_from_start_year as i32 / 12; |
| let month = index_from_start_year % 12; |
| Some(T::earliest_after_date(self.0.start.ymd( |
| year, |
| month as u32 + 1, |
| 1, |
| ))) |
| } |
| } |
| |
| /// Indicate the coord has a yearly granularity. |
| #[derive(Clone)] |
| pub struct Yearly<T: TimeValue>(Range<T>); |
| |
| fn generate_yearly_keypoints<T: TimeValue>( |
| max_points: usize, |
| mut start_year: i32, |
| start_month: u32, |
| mut end_year: i32, |
| end_month: u32, |
| builder: &T, |
| ) -> Vec<T> { |
| if start_month > end_month { |
| end_year -= 1; |
| } |
| |
| let mut exp10 = 1; |
| |
| while (end_year - start_year + 1) as usize / (exp10 * 10) > max_points { |
| exp10 *= 10; |
| } |
| |
| let mut freq = exp10; |
| |
| for try_freq in &[1, 2, 5, 10] { |
| freq = *try_freq * exp10; |
| if (end_year - start_year + 1) as usize / (exp10 * *try_freq) <= max_points { |
| break; |
| } |
| } |
| |
| let mut ret = vec![]; |
| |
| while start_year <= end_year { |
| ret.push(T::earliest_after_date(builder.ymd( |
| start_year, |
| start_month, |
| 1, |
| ))); |
| start_year += freq as i32; |
| } |
| |
| ret |
| } |
| |
| impl<T: TimeValue + Datelike + Clone> ValueFormatter<T> for Yearly<T> { |
| fn format(value: &T) -> String { |
| format!("{}-{}", value.year(), value.month()) |
| } |
| } |
| |
| impl<T: TimeValue + Clone> Ranged for Yearly<T> |
| where |
| Range<T>: AsRangedCoord<Value = T>, |
| { |
| type FormatOption = NoDefaultFormatting; |
| type ValueType = T; |
| |
| fn range(&self) -> Range<T> { |
| self.0.start.clone()..self.0.end.clone() |
| } |
| |
| fn map(&self, value: &Self::ValueType, limit: (i32, i32)) -> i32 { |
| T::map_coord(value, &self.0.start, &self.0.end, limit) |
| } |
| |
| fn key_points<HintType: KeyPointHint>(&self, hint: HintType) -> Vec<Self::ValueType> { |
| if hint.weight().allow_light_points() && self.size() <= hint.bold_points() * 2 { |
| return Monthly(self.0.clone()).key_points(hint); |
| } |
| let max_points = hint.max_num_points(); |
| let start_date = self.0.start.date_ceil(); |
| let end_date = self.0.end.date_floor(); |
| |
| let mut start_year = start_date.year(); |
| let mut start_month = start_date.month(); |
| let start_day = start_date.day(); |
| |
| let end_year = end_date.year(); |
| let end_month = end_date.month(); |
| |
| if start_day != 1 { |
| start_month += 1; |
| if start_month == 13 { |
| start_month = 1; |
| start_year += 1; |
| } |
| } |
| |
| generate_yearly_keypoints( |
| max_points, |
| start_year, |
| start_month, |
| end_year, |
| end_month, |
| &self.0.start, |
| ) |
| } |
| } |
| |
| impl<T: TimeValue + Clone> DiscreteRanged for Yearly<T> |
| where |
| Range<T>: AsRangedCoord<Value = T>, |
| { |
| fn size(&self) -> usize { |
| let year_start = self.0.start.date_ceil().year(); |
| let year_end = self.0.end.date_floor().year(); |
| ((year_end - year_start).max(-1) + 1) as usize |
| } |
| |
| fn index_of(&self, value: &T) -> Option<usize> { |
| let year_start = self.0.start.date_ceil().year(); |
| let year_value = value.date_floor().year(); |
| let ret = year_value - year_start; |
| if ret < 0 { |
| return None; |
| } |
| Some(ret as usize) |
| } |
| |
| fn from_index(&self, index: usize) -> Option<T> { |
| let year = self.0.start.date_ceil().year() + index as i32; |
| let ret = T::earliest_after_date(self.0.start.ymd(year, 1, 1)); |
| if ret.date_ceil() <= self.0.start.date_floor() { |
| return Some(self.0.start.clone()); |
| } |
| Some(ret) |
| } |
| } |
| |
| /// The trait that converts a normal date coord into a yearly one |
| pub trait IntoMonthly<T: TimeValue> { |
| fn monthly(self) -> Monthly<T>; |
| } |
| |
| /// The trait that converts a normal date coord into a yearly one |
| pub trait IntoYearly<T: TimeValue> { |
| fn yearly(self) -> Yearly<T>; |
| } |
| |
| impl<T: TimeValue> IntoMonthly<T> for Range<T> { |
| fn monthly(self) -> Monthly<T> { |
| Monthly(self) |
| } |
| } |
| |
| impl<T: TimeValue> IntoYearly<T> for Range<T> { |
| fn yearly(self) -> Yearly<T> { |
| Yearly(self) |
| } |
| } |
| |
| /// The ranged coordinate for the date and time |
| #[derive(Clone)] |
| pub struct RangedDateTime<DT: Datelike + Timelike + TimeValue>(DT, DT); |
| |
| impl<Z: TimeZone> AsRangedCoord for Range<DateTime<Z>> { |
| type CoordDescType = RangedDateTime<DateTime<Z>>; |
| type Value = DateTime<Z>; |
| } |
| |
| impl<Z: TimeZone> From<Range<DateTime<Z>>> for RangedDateTime<DateTime<Z>> { |
| fn from(range: Range<DateTime<Z>>) -> Self { |
| Self(range.start, range.end) |
| } |
| } |
| |
| impl From<Range<NaiveDateTime>> for RangedDateTime<NaiveDateTime> { |
| fn from(range: Range<NaiveDateTime>) -> Self { |
| Self(range.start, range.end) |
| } |
| } |
| |
| impl<DT> Ranged for RangedDateTime<DT> |
| where |
| DT: Datelike + Timelike + TimeValue + Clone + PartialOrd, |
| DT: Add<Duration, Output = DT>, |
| DT: Sub<DT, Output = Duration>, |
| RangedDate<DT::DateType>: Ranged<ValueType = DT::DateType>, |
| { |
| type FormatOption = DefaultFormatting; |
| type ValueType = DT; |
| |
| fn range(&self) -> Range<DT> { |
| self.0.clone()..self.1.clone() |
| } |
| |
| fn map(&self, value: &Self::ValueType, limit: (i32, i32)) -> i32 { |
| TimeValue::map_coord(value, &self.0, &self.1, limit) |
| } |
| |
| fn key_points<HintType: KeyPointHint>(&self, hint: HintType) -> Vec<Self::ValueType> { |
| let max_points = hint.max_num_points(); |
| let total_span = self.1.clone() - self.0.clone(); |
| |
| if let Some(total_ns) = total_span.num_nanoseconds() { |
| if let Some(actual_ns_per_point) = |
| compute_period_per_point(total_ns as u64, max_points, true) |
| { |
| let start_time_ns = u64::from(self.0.num_seconds_from_midnight()) * 1_000_000_000 |
| + u64::from(self.0.nanosecond()); |
| |
| let mut start_time = DT::from_date(self.0.date_floor()) |
| + Duration::nanoseconds(if start_time_ns % actual_ns_per_point > 0 { |
| start_time_ns + (actual_ns_per_point - start_time_ns % actual_ns_per_point) |
| } else { |
| start_time_ns |
| } as i64); |
| |
| let mut ret = vec![]; |
| |
| while start_time < self.1 { |
| ret.push(start_time.clone()); |
| start_time = start_time + Duration::nanoseconds(actual_ns_per_point as i64); |
| } |
| |
| return ret; |
| } |
| } |
| |
| // Otherwise, it actually behaves like a date |
| let date_range = RangedDate(self.0.date_ceil(), self.1.date_floor()); |
| |
| date_range |
| .key_points(max_points) |
| .into_iter() |
| .map(DT::from_date) |
| .collect() |
| } |
| } |
| |
| /// The coordinate that for duration of time |
| #[derive(Clone)] |
| pub struct RangedDuration(Duration, Duration); |
| |
| impl AsRangedCoord for Range<Duration> { |
| type CoordDescType = RangedDuration; |
| type Value = Duration; |
| } |
| |
| impl From<Range<Duration>> for RangedDuration { |
| fn from(range: Range<Duration>) -> Self { |
| Self(range.start, range.end) |
| } |
| } |
| |
| impl Ranged for RangedDuration { |
| type FormatOption = DefaultFormatting; |
| type ValueType = Duration; |
| |
| fn range(&self) -> Range<Duration> { |
| self.0..self.1 |
| } |
| |
| fn map(&self, value: &Self::ValueType, limit: (i32, i32)) -> i32 { |
| let total_span = self.1 - self.0; |
| let value_span = *value - self.0; |
| |
| if let Some(total_ns) = total_span.num_nanoseconds() { |
| if let Some(value_ns) = value_span.num_nanoseconds() { |
| return limit.0 |
| + (f64::from(limit.1 - limit.0) * value_ns as f64 / total_ns as f64 + 1e-10) |
| as i32; |
| } |
| return limit.1; |
| } |
| |
| let total_days = total_span.num_days(); |
| let value_days = value_span.num_days(); |
| |
| limit.0 |
| + (f64::from(limit.1 - limit.0) * value_days as f64 / total_days as f64 + 1e-10) as i32 |
| } |
| |
| fn key_points<HintType: KeyPointHint>(&self, hint: HintType) -> Vec<Self::ValueType> { |
| let max_points = hint.max_num_points(); |
| let total_span = self.1 - self.0; |
| |
| if let Some(total_ns) = total_span.num_nanoseconds() { |
| if let Some(period) = compute_period_per_point(total_ns as u64, max_points, false) { |
| let mut start_ns = self.0.num_nanoseconds().unwrap(); |
| |
| if start_ns as u64 % period > 0 { |
| if start_ns > 0 { |
| start_ns += period as i64 - (start_ns % period as i64); |
| } else { |
| start_ns -= start_ns % period as i64; |
| } |
| } |
| |
| let mut current = Duration::nanoseconds(start_ns); |
| let mut ret = vec![]; |
| |
| while current < self.1 { |
| ret.push(current); |
| current = current + Duration::nanoseconds(period as i64); |
| } |
| |
| return ret; |
| } |
| } |
| |
| let begin_days = self.0.num_days(); |
| let end_days = self.1.num_days(); |
| |
| let mut days_per_tick = 1; |
| let mut idx = 0; |
| const MULTIPLIER: &[i32] = &[1, 2, 5]; |
| |
| while (end_days - begin_days) / i64::from(days_per_tick * MULTIPLIER[idx]) |
| > max_points as i64 |
| { |
| idx += 1; |
| if idx == MULTIPLIER.len() { |
| idx = 0; |
| days_per_tick *= 10; |
| } |
| } |
| |
| days_per_tick *= MULTIPLIER[idx]; |
| |
| let mut ret = vec![]; |
| |
| let mut current = Duration::days( |
| self.0.num_days() |
| + if Duration::days(self.0.num_days()) != self.0 { |
| 1 |
| } else { |
| 0 |
| }, |
| ); |
| |
| while current < self.1 { |
| ret.push(current); |
| current = current + Duration::days(i64::from(days_per_tick)); |
| } |
| |
| ret |
| } |
| } |
| |
| #[allow(clippy::inconsistent_digit_grouping)] |
| fn compute_period_per_point(total_ns: u64, max_points: usize, sub_daily: bool) -> Option<u64> { |
| let min_ns_per_point = total_ns as f64 / max_points as f64; |
| let actual_ns_per_point: u64 = (10u64).pow((min_ns_per_point as f64).log10().floor() as u32); |
| |
| fn determine_actual_ns_per_point( |
| total_ns: u64, |
| mut actual_ns_per_point: u64, |
| units: &[u64], |
| base: u64, |
| max_points: usize, |
| ) -> u64 { |
| let mut unit_per_point_idx = 0; |
| while total_ns / actual_ns_per_point > max_points as u64 * units[unit_per_point_idx] { |
| unit_per_point_idx += 1; |
| if unit_per_point_idx == units.len() { |
| unit_per_point_idx = 0; |
| actual_ns_per_point *= base; |
| } |
| } |
| units[unit_per_point_idx] * actual_ns_per_point |
| } |
| |
| if actual_ns_per_point < 1_000_000_000 { |
| Some(determine_actual_ns_per_point( |
| total_ns as u64, |
| actual_ns_per_point, |
| &[1, 2, 5], |
| 10, |
| max_points, |
| )) |
| } else if actual_ns_per_point < 3600_000_000_000 { |
| Some(determine_actual_ns_per_point( |
| total_ns as u64, |
| 1_000_000_000, |
| &[1, 2, 5, 10, 15, 20, 30], |
| 60, |
| max_points, |
| )) |
| } else if actual_ns_per_point < 3600_000_000_000 * 24 { |
| Some(determine_actual_ns_per_point( |
| total_ns as u64, |
| 3600_000_000_000, |
| &[1, 2, 4, 8, 12], |
| 24, |
| max_points, |
| )) |
| } else if !sub_daily { |
| if actual_ns_per_point < 3600_000_000_000 * 24 * 10 { |
| Some(determine_actual_ns_per_point( |
| total_ns as u64, |
| 3600_000_000_000 * 24, |
| &[1, 2, 5, 7], |
| 10, |
| max_points, |
| )) |
| } else { |
| Some(determine_actual_ns_per_point( |
| total_ns as u64, |
| 3600_000_000_000 * 24 * 10, |
| &[1, 2, 5], |
| 10, |
| max_points, |
| )) |
| } |
| } else { |
| None |
| } |
| } |
| |
| #[cfg(test)] |
| mod test { |
| use super::*; |
| use chrono::{TimeZone, Utc}; |
| |
| #[test] |
| fn test_date_range_long() { |
| let range = Utc.ymd(1000, 1, 1)..Utc.ymd(2999, 1, 1); |
| |
| let ranged_coord = Into::<RangedDate<_>>::into(range); |
| |
| assert_eq!(ranged_coord.map(&Utc.ymd(1000, 8, 10), (0, 100)), 0); |
| assert_eq!(ranged_coord.map(&Utc.ymd(2999, 8, 10), (0, 100)), 100); |
| |
| let kps = ranged_coord.key_points(23); |
| |
| assert!(kps.len() <= 23); |
| let max = kps |
| .iter() |
| .zip(kps.iter().skip(1)) |
| .map(|(p, n)| (*n - *p).num_days()) |
| .max() |
| .unwrap(); |
| let min = kps |
| .iter() |
| .zip(kps.iter().skip(1)) |
| .map(|(p, n)| (*n - *p).num_days()) |
| .min() |
| .unwrap(); |
| assert_eq!(max, min); |
| assert_eq!(max % 7, 0); |
| } |
| |
| #[test] |
| fn test_date_range_short() { |
| let range = Utc.ymd(2019, 1, 1)..Utc.ymd(2019, 1, 21); |
| let ranged_coord = Into::<RangedDate<_>>::into(range); |
| |
| let kps = ranged_coord.key_points(4); |
| |
| assert_eq!(kps.len(), 3); |
| |
| let max = kps |
| .iter() |
| .zip(kps.iter().skip(1)) |
| .map(|(p, n)| (*n - *p).num_days()) |
| .max() |
| .unwrap(); |
| let min = kps |
| .iter() |
| .zip(kps.iter().skip(1)) |
| .map(|(p, n)| (*n - *p).num_days()) |
| .min() |
| .unwrap(); |
| assert_eq!(max, min); |
| assert_eq!(max, 7); |
| |
| let kps = ranged_coord.key_points(30); |
| assert_eq!(kps.len(), 21); |
| let max = kps |
| .iter() |
| .zip(kps.iter().skip(1)) |
| .map(|(p, n)| (*n - *p).num_days()) |
| .max() |
| .unwrap(); |
| let min = kps |
| .iter() |
| .zip(kps.iter().skip(1)) |
| .map(|(p, n)| (*n - *p).num_days()) |
| .min() |
| .unwrap(); |
| assert_eq!(max, min); |
| assert_eq!(max, 1); |
| } |
| |
| #[test] |
| fn test_yearly_date_range() { |
| use crate::coord::ranged1d::BoldPoints; |
| let range = Utc.ymd(1000, 8, 5)..Utc.ymd(2999, 1, 1); |
| let ranged_coord = range.yearly(); |
| |
| assert_eq!(ranged_coord.map(&Utc.ymd(1000, 8, 10), (0, 100)), 0); |
| assert_eq!(ranged_coord.map(&Utc.ymd(2999, 8, 10), (0, 100)), 100); |
| |
| let kps = ranged_coord.key_points(23); |
| |
| assert!(kps.len() <= 23); |
| let max = kps |
| .iter() |
| .zip(kps.iter().skip(1)) |
| .map(|(p, n)| (*n - *p).num_days()) |
| .max() |
| .unwrap(); |
| let min = kps |
| .iter() |
| .zip(kps.iter().skip(1)) |
| .map(|(p, n)| (*n - *p).num_days()) |
| .min() |
| .unwrap(); |
| assert!(max != min); |
| |
| assert!(kps.into_iter().all(|x| x.month() == 9 && x.day() == 1)); |
| |
| let range = Utc.ymd(2019, 8, 5)..Utc.ymd(2020, 1, 1); |
| let ranged_coord = range.yearly(); |
| let kps = ranged_coord.key_points(BoldPoints(23)); |
| assert!(kps.len() == 1); |
| } |
| |
| #[test] |
| fn test_monthly_date_range() { |
| let range = Utc.ymd(2019, 8, 5)..Utc.ymd(2020, 9, 1); |
| let ranged_coord = range.monthly(); |
| |
| use crate::coord::ranged1d::BoldPoints; |
| |
| let kps = ranged_coord.key_points(BoldPoints(15)); |
| |
| assert!(kps.len() <= 15); |
| assert!(kps.iter().all(|x| x.day() == 1)); |
| assert!(kps.into_iter().any(|x| x.month() != 9)); |
| |
| let kps = ranged_coord.key_points(BoldPoints(5)); |
| assert!(kps.len() <= 5); |
| assert!(kps.iter().all(|x| x.day() == 1)); |
| let kps: Vec<_> = kps.into_iter().map(|x| x.month()).collect(); |
| assert_eq!(kps, vec![9, 12, 3, 6, 9]); |
| |
| // TODO: Investigate why max_point = 1 breaks the contract |
| let kps = ranged_coord.key_points(3); |
| assert!(kps.len() == 3); |
| assert!(kps.iter().all(|x| x.day() == 1)); |
| let kps: Vec<_> = kps.into_iter().map(|x| x.month()).collect(); |
| assert_eq!(kps, vec![9, 3, 9]); |
| } |
| |
| #[test] |
| fn test_datetime_long_range() { |
| let coord: RangedDateTime<_> = |
| (Utc.ymd(1000, 1, 1).and_hms(0, 0, 0)..Utc.ymd(3000, 1, 1).and_hms(0, 0, 0)).into(); |
| |
| assert_eq!( |
| coord.map(&Utc.ymd(1000, 1, 1).and_hms(0, 0, 0), (0, 100)), |
| 0 |
| ); |
| assert_eq!( |
| coord.map(&Utc.ymd(3000, 1, 1).and_hms(0, 0, 0), (0, 100)), |
| 100 |
| ); |
| |
| let kps = coord.key_points(23); |
| |
| assert!(kps.len() <= 23); |
| let max = kps |
| .iter() |
| .zip(kps.iter().skip(1)) |
| .map(|(p, n)| (*n - *p).num_seconds()) |
| .max() |
| .unwrap(); |
| let min = kps |
| .iter() |
| .zip(kps.iter().skip(1)) |
| .map(|(p, n)| (*n - *p).num_seconds()) |
| .min() |
| .unwrap(); |
| assert!(max == min); |
| assert!(max % (24 * 3600 * 7) == 0); |
| } |
| |
| #[test] |
| fn test_datetime_medium_range() { |
| let coord: RangedDateTime<_> = |
| (Utc.ymd(2019, 1, 1).and_hms(0, 0, 0)..Utc.ymd(2019, 1, 11).and_hms(0, 0, 0)).into(); |
| |
| let kps = coord.key_points(23); |
| |
| assert!(kps.len() <= 23); |
| let max = kps |
| .iter() |
| .zip(kps.iter().skip(1)) |
| .map(|(p, n)| (*n - *p).num_seconds()) |
| .max() |
| .unwrap(); |
| let min = kps |
| .iter() |
| .zip(kps.iter().skip(1)) |
| .map(|(p, n)| (*n - *p).num_seconds()) |
| .min() |
| .unwrap(); |
| assert!(max == min); |
| assert_eq!(max, 12 * 3600); |
| } |
| |
| #[test] |
| fn test_datetime_short_range() { |
| let coord: RangedDateTime<_> = |
| (Utc.ymd(2019, 1, 1).and_hms(0, 0, 0)..Utc.ymd(2019, 1, 2).and_hms(0, 0, 0)).into(); |
| |
| let kps = coord.key_points(50); |
| |
| assert!(kps.len() <= 50); |
| let max = kps |
| .iter() |
| .zip(kps.iter().skip(1)) |
| .map(|(p, n)| (*n - *p).num_seconds()) |
| .max() |
| .unwrap(); |
| let min = kps |
| .iter() |
| .zip(kps.iter().skip(1)) |
| .map(|(p, n)| (*n - *p).num_seconds()) |
| .min() |
| .unwrap(); |
| assert!(max == min); |
| assert_eq!(max, 1800); |
| } |
| |
| #[test] |
| fn test_datetime_nano_range() { |
| let start = Utc.ymd(2019, 1, 1).and_hms(0, 0, 0); |
| let end = start.clone() + Duration::nanoseconds(100); |
| let coord: RangedDateTime<_> = (start..end).into(); |
| |
| let kps = coord.key_points(50); |
| |
| assert!(kps.len() <= 50); |
| let max = kps |
| .iter() |
| .zip(kps.iter().skip(1)) |
| .map(|(p, n)| (*n - *p).num_nanoseconds().unwrap()) |
| .max() |
| .unwrap(); |
| let min = kps |
| .iter() |
| .zip(kps.iter().skip(1)) |
| .map(|(p, n)| (*n - *p).num_nanoseconds().unwrap()) |
| .min() |
| .unwrap(); |
| assert!(max == min); |
| assert_eq!(max, 2); |
| } |
| |
| #[test] |
| fn test_duration_long_range() { |
| let coord: RangedDuration = (Duration::days(-1000000)..Duration::days(1000000)).into(); |
| |
| assert_eq!(coord.map(&Duration::days(-1000000), (0, 100)), 0); |
| assert_eq!(coord.map(&Duration::days(1000000), (0, 100)), 100); |
| |
| let kps = coord.key_points(23); |
| |
| assert!(kps.len() <= 23); |
| let max = kps |
| .iter() |
| .zip(kps.iter().skip(1)) |
| .map(|(p, n)| (*n - *p).num_seconds()) |
| .max() |
| .unwrap(); |
| let min = kps |
| .iter() |
| .zip(kps.iter().skip(1)) |
| .map(|(p, n)| (*n - *p).num_seconds()) |
| .min() |
| .unwrap(); |
| assert!(max == min); |
| assert!(max % (24 * 3600 * 10000) == 0); |
| } |
| |
| #[test] |
| fn test_duration_daily_range() { |
| let coord: RangedDuration = (Duration::days(0)..Duration::hours(25)).into(); |
| |
| let kps = coord.key_points(23); |
| |
| assert!(kps.len() <= 23); |
| let max = kps |
| .iter() |
| .zip(kps.iter().skip(1)) |
| .map(|(p, n)| (*n - *p).num_seconds()) |
| .max() |
| .unwrap(); |
| let min = kps |
| .iter() |
| .zip(kps.iter().skip(1)) |
| .map(|(p, n)| (*n - *p).num_seconds()) |
| .min() |
| .unwrap(); |
| assert!(max == min); |
| assert_eq!(max, 3600 * 2); |
| } |
| |
| #[test] |
| fn test_date_discrete() { |
| let coord: RangedDate<Date<_>> = (Utc.ymd(2019, 1, 1)..Utc.ymd(2019, 12, 31)).into(); |
| assert_eq!(coord.size(), 365); |
| assert_eq!(coord.index_of(&Utc.ymd(2019, 2, 28)), Some(31 + 28 - 1)); |
| assert_eq!(coord.from_index(364), Some(Utc.ymd(2019, 12, 31))); |
| } |
| |
| #[test] |
| fn test_monthly_discrete() { |
| let coord1 = (Utc.ymd(2019, 1, 10)..Utc.ymd(2019, 12, 31)).monthly(); |
| let coord2 = (Utc.ymd(2019, 1, 10)..Utc.ymd(2020, 1, 1)).monthly(); |
| assert_eq!(coord1.size(), 12); |
| assert_eq!(coord2.size(), 13); |
| |
| for i in 1..=12 { |
| assert_eq!(coord1.from_index(i - 1).unwrap().month(), i as u32); |
| assert_eq!( |
| coord1.index_of(&coord1.from_index(i - 1).unwrap()).unwrap(), |
| i - 1 |
| ); |
| } |
| } |
| |
| #[test] |
| fn test_yearly_discrete() { |
| let coord1 = (Utc.ymd(2000, 1, 10)..Utc.ymd(2019, 12, 31)).yearly(); |
| assert_eq!(coord1.size(), 20); |
| |
| for i in 0..20 { |
| assert_eq!(coord1.from_index(i).unwrap().year(), 2000 + i as i32); |
| assert_eq!(coord1.index_of(&coord1.from_index(i).unwrap()).unwrap(), i); |
| } |
| } |
| } |