aboutsummaryrefslogtreecommitdiff
path: root/src/coord/ranged1d/types/datetime.rs
diff options
context:
space:
mode:
Diffstat (limited to 'src/coord/ranged1d/types/datetime.rs')
-rw-r--r--src/coord/ranged1d/types/datetime.rs1163
1 files changed, 1163 insertions, 0 deletions
diff --git a/src/coord/ranged1d/types/datetime.rs b/src/coord/ranged1d/types/datetime.rs
new file mode 100644
index 0000000..f6b5717
--- /dev/null
+++ b/src/coord/ranged1d/types/datetime.rs
@@ -0,0 +1,1163 @@
+/// 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);
+ }
+ }
+}