1/// The datetime coordinates
2use chrono::{Date, DateTime, Datelike, Duration, NaiveDate, NaiveDateTime, TimeZone, Timelike};
3use std::ops::{Add, Range, Sub};
4
5use crate::coord::ranged1d::{
6 AsRangedCoord, DefaultFormatting, DiscreteRanged, KeyPointHint, NoDefaultFormatting, Ranged,
7 ReversibleRanged, ValueFormatter,
8};
9
10/// The trait that describe some time value. This is the uniformed abstraction that works
11/// for both Date, DateTime and Duration, etc.
12pub trait TimeValue: Eq + Sized {
13 type DateType: Datelike + PartialOrd;
14
15 /// Returns the date that is no later than the time
16 fn date_floor(&self) -> Self::DateType;
17 /// Returns the date that is no earlier than the time
18 fn date_ceil(&self) -> Self::DateType;
19 /// Returns the maximum value that is earlier than the given date
20 fn earliest_after_date(date: Self::DateType) -> Self;
21 /// Returns the duration between two time value
22 fn subtract(&self, other: &Self) -> Duration;
23 /// Add duration to time value
24 fn add(&self, duration: &Duration) -> Self;
25 /// Instantiate a date type for current time value;
26 fn ymd(&self, year: i32, month: u32, date: u32) -> Self::DateType;
27 /// Cast current date type into this type
28 fn from_date(date: Self::DateType) -> Self;
29
30 /// Map the coord spec
31 fn map_coord(value: &Self, begin: &Self, end: &Self, limit: (i32, i32)) -> i32 {
32 let total_span = end.subtract(begin);
33 let value_span = value.subtract(begin);
34
35 // First, lets try the nanoseconds precision
36 if let Some(total_ns) = total_span.num_nanoseconds() {
37 if let Some(value_ns) = value_span.num_nanoseconds() {
38 return (f64::from(limit.1 - limit.0) * value_ns as f64 / total_ns as f64) as i32
39 + limit.0;
40 }
41 }
42
43 // Yes, converting them to floating point may lose precision, but this is Ok.
44 // If it overflows, it means we have a time span nearly 300 years, we are safe to ignore the
45 // portion less than 1 day.
46 let total_days = total_span.num_days() as f64;
47 let value_days = value_span.num_days() as f64;
48
49 (f64::from(limit.1 - limit.0) * value_days / total_days) as i32 + limit.0
50 }
51
52 /// Map pixel to coord spec
53 fn unmap_coord(point: i32, begin: &Self, end: &Self, limit: (i32, i32)) -> Self {
54 let total_span = end.subtract(begin);
55 let offset = (point - limit.0) as i64;
56
57 // Check if nanoseconds fit in i64
58 if let Some(total_ns) = total_span.num_nanoseconds() {
59 let pixel_span = (limit.1 - limit.0) as i64;
60 let factor = total_ns / pixel_span;
61 let remainder = total_ns % pixel_span;
62 if factor == 0
63 || i64::MAX / factor > offset.abs()
64 || (remainder == 0 && i64::MAX / factor >= offset.abs())
65 {
66 let nano_seconds = offset * factor + (remainder * offset) / pixel_span;
67 return begin.add(&Duration::nanoseconds(nano_seconds));
68 }
69 }
70
71 // Otherwise, use days
72 let total_days = total_span.num_days() as f64;
73 let days = (((offset as f64) * total_days) / ((limit.1 - limit.0) as f64)) as i64;
74 begin.add(&Duration::days(days))
75 }
76}
77
78impl TimeValue for NaiveDate {
79 type DateType = NaiveDate;
80 fn date_floor(&self) -> NaiveDate {
81 *self
82 }
83 fn date_ceil(&self) -> NaiveDate {
84 *self
85 }
86 fn earliest_after_date(date: NaiveDate) -> Self {
87 date
88 }
89 fn subtract(&self, other: &NaiveDate) -> Duration {
90 *self - *other
91 }
92 fn add(&self, other: &Duration) -> NaiveDate {
93 *self + *other
94 }
95
96 fn ymd(&self, year: i32, month: u32, date: u32) -> Self::DateType {
97 NaiveDate::from_ymd(year, month, date)
98 }
99
100 fn from_date(date: Self::DateType) -> Self {
101 date
102 }
103}
104
105impl<Z: TimeZone> TimeValue for Date<Z> {
106 type DateType = Date<Z>;
107 fn date_floor(&self) -> Date<Z> {
108 self.clone()
109 }
110 fn date_ceil(&self) -> Date<Z> {
111 self.clone()
112 }
113 fn earliest_after_date(date: Date<Z>) -> Self {
114 date
115 }
116 fn subtract(&self, other: &Date<Z>) -> Duration {
117 self.clone() - other.clone()
118 }
119 fn add(&self, other: &Duration) -> Date<Z> {
120 self.clone() + *other
121 }
122
123 fn ymd(&self, year: i32, month: u32, date: u32) -> Self::DateType {
124 self.timezone().ymd(year, month, date)
125 }
126
127 fn from_date(date: Self::DateType) -> Self {
128 date
129 }
130}
131
132impl<Z: TimeZone> TimeValue for DateTime<Z> {
133 type DateType = Date<Z>;
134 fn date_floor(&self) -> Date<Z> {
135 self.date()
136 }
137 fn date_ceil(&self) -> Date<Z> {
138 if self.time().num_seconds_from_midnight() > 0 {
139 self.date() + Duration::days(1)
140 } else {
141 self.date()
142 }
143 }
144 fn earliest_after_date(date: Date<Z>) -> DateTime<Z> {
145 date.and_hms(0, 0, 0)
146 }
147
148 fn subtract(&self, other: &DateTime<Z>) -> Duration {
149 self.clone() - other.clone()
150 }
151 fn add(&self, other: &Duration) -> DateTime<Z> {
152 self.clone() + *other
153 }
154
155 fn ymd(&self, year: i32, month: u32, date: u32) -> Self::DateType {
156 self.timezone().ymd(year, month, date)
157 }
158
159 fn from_date(date: Self::DateType) -> Self {
160 date.and_hms(0, 0, 0)
161 }
162}
163
164impl TimeValue for NaiveDateTime {
165 type DateType = NaiveDate;
166 fn date_floor(&self) -> NaiveDate {
167 self.date()
168 }
169 fn date_ceil(&self) -> NaiveDate {
170 if self.time().num_seconds_from_midnight() > 0 {
171 self.date() + Duration::days(1)
172 } else {
173 self.date()
174 }
175 }
176 fn earliest_after_date(date: NaiveDate) -> NaiveDateTime {
177 date.and_hms(0, 0, 0)
178 }
179
180 fn subtract(&self, other: &NaiveDateTime) -> Duration {
181 *self - *other
182 }
183 fn add(&self, other: &Duration) -> NaiveDateTime {
184 *self + *other
185 }
186
187 fn ymd(&self, year: i32, month: u32, date: u32) -> Self::DateType {
188 NaiveDate::from_ymd(year, month, date)
189 }
190
191 fn from_date(date: Self::DateType) -> Self {
192 date.and_hms(0, 0, 0)
193 }
194}
195
196/// The ranged coordinate for date
197#[derive(Clone)]
198pub struct RangedDate<D: Datelike>(D, D);
199
200impl<D: Datelike> From<Range<D>> for RangedDate<D> {
201 fn from(range: Range<D>) -> Self {
202 Self(range.start, range.end)
203 }
204}
205
206impl<D> Ranged for RangedDate<D>
207where
208 D: Datelike + TimeValue + Sub<D, Output = Duration> + Add<Duration, Output = D> + Clone,
209{
210 type FormatOption = DefaultFormatting;
211 type ValueType = D;
212
213 fn range(&self) -> Range<D> {
214 self.0.clone()..self.1.clone()
215 }
216
217 fn map(&self, value: &Self::ValueType, limit: (i32, i32)) -> i32 {
218 TimeValue::map_coord(value, &self.0, &self.1, limit)
219 }
220
221 fn key_points<HintType: KeyPointHint>(&self, hint: HintType) -> Vec<Self::ValueType> {
222 let max_points = hint.max_num_points();
223 let mut ret = vec![];
224
225 let total_days = (self.1.clone() - self.0.clone()).num_days();
226 let total_weeks = (self.1.clone() - self.0.clone()).num_weeks();
227
228 if total_days > 0 && total_days as usize <= max_points {
229 for day_idx in 0..=total_days {
230 ret.push(self.0.clone() + Duration::days(day_idx));
231 }
232 return ret;
233 }
234
235 if total_weeks > 0 && total_weeks as usize <= max_points {
236 for day_idx in 0..=total_weeks {
237 ret.push(self.0.clone() + Duration::weeks(day_idx));
238 }
239 return ret;
240 }
241
242 // When all data is in the same week, just plot properly.
243 if total_weeks == 0 {
244 ret.push(self.0.clone());
245 return ret;
246 }
247
248 let week_per_point = ((total_weeks as f64) / (max_points as f64)).ceil() as usize;
249
250 for idx in 0..=(total_weeks as usize / week_per_point) {
251 ret.push(self.0.clone() + Duration::weeks((idx * week_per_point) as i64));
252 }
253
254 ret
255 }
256}
257
258impl<D> DiscreteRanged for RangedDate<D>
259where
260 D: Datelike + TimeValue + Sub<D, Output = Duration> + Add<Duration, Output = D> + Clone,
261{
262 fn size(&self) -> usize {
263 ((self.1.clone() - self.0.clone()).num_days().max(-1) + 1) as usize
264 }
265
266 fn index_of(&self, value: &D) -> Option<usize> {
267 let ret = (value.clone() - self.0.clone()).num_days();
268 if ret < 0 {
269 return None;
270 }
271 Some(ret as usize)
272 }
273
274 fn from_index(&self, index: usize) -> Option<D> {
275 Some(self.0.clone() + Duration::days(index as i64))
276 }
277}
278
279impl<Z: TimeZone> AsRangedCoord for Range<Date<Z>> {
280 type CoordDescType = RangedDate<Date<Z>>;
281 type Value = Date<Z>;
282}
283
284impl AsRangedCoord for Range<NaiveDate> {
285 type CoordDescType = RangedDate<NaiveDate>;
286 type Value = NaiveDate;
287}
288
289/// Indicates the coord has a monthly resolution
290///
291/// Note: since month doesn't have a constant duration.
292/// We can't use a simple granularity to describe it. Thus we have
293/// this axis decorator to make it yield monthly key-points.
294#[derive(Clone)]
295pub struct Monthly<T: TimeValue>(Range<T>);
296
297impl<T: TimeValue + Datelike + Clone> ValueFormatter<T> for Monthly<T> {
298 fn format(value: &T) -> String {
299 format!("{}-{}", value.year(), value.month())
300 }
301}
302
303impl<T: TimeValue + Clone> Monthly<T> {
304 fn bold_key_points<H: KeyPointHint>(&self, hint: &H) -> Vec<T> {
305 let max_points = hint.max_num_points();
306 let start_date = self.0.start.date_ceil();
307 let end_date = self.0.end.date_floor();
308
309 let mut start_year = start_date.year();
310 let mut start_month = start_date.month();
311 let start_day = start_date.day();
312
313 let end_year = end_date.year();
314 let end_month = end_date.month();
315
316 if start_day != 1 {
317 start_month += 1;
318 if start_month == 13 {
319 start_month = 1;
320 start_year += 1;
321 }
322 }
323
324 let total_month = (end_year - start_year) * 12 + end_month as i32 - start_month as i32;
325
326 fn generate_key_points<T: TimeValue>(
327 mut start_year: i32,
328 mut start_month: i32,
329 end_year: i32,
330 end_month: i32,
331 step: u32,
332 builder: &T,
333 ) -> Vec<T> {
334 let mut ret = vec![];
335 while end_year > start_year || (end_year == start_year && end_month >= start_month) {
336 ret.push(T::earliest_after_date(builder.ymd(
337 start_year,
338 start_month as u32,
339 1,
340 )));
341 start_month += step as i32;
342
343 if start_month >= 13 {
344 start_year += start_month / 12;
345 start_month %= 12;
346 }
347 }
348
349 ret
350 }
351
352 if total_month as usize <= max_points {
353 // Monthly
354 return generate_key_points(
355 start_year,
356 start_month as i32,
357 end_year,
358 end_month as i32,
359 1,
360 &self.0.start,
361 );
362 } else if total_month as usize <= max_points * 3 {
363 // Quarterly
364 return generate_key_points(
365 start_year,
366 start_month as i32,
367 end_year,
368 end_month as i32,
369 3,
370 &self.0.start,
371 );
372 } else if total_month as usize <= max_points * 6 {
373 // Biyearly
374 return generate_key_points(
375 start_year,
376 start_month as i32,
377 end_year,
378 end_month as i32,
379 6,
380 &self.0.start,
381 );
382 }
383
384 // Otherwise we could generate the yearly keypoints
385 generate_yearly_keypoints(
386 max_points,
387 start_year,
388 start_month,
389 end_year,
390 end_month,
391 &self.0.start,
392 )
393 }
394}
395
396impl<T: TimeValue + Clone> Ranged for Monthly<T>
397where
398 Range<T>: AsRangedCoord<Value = T>,
399{
400 type FormatOption = NoDefaultFormatting;
401 type ValueType = T;
402
403 fn range(&self) -> Range<T> {
404 self.0.start.clone()..self.0.end.clone()
405 }
406
407 fn map(&self, value: &Self::ValueType, limit: (i32, i32)) -> i32 {
408 T::map_coord(value, &self.0.start, &self.0.end, limit)
409 }
410
411 fn key_points<HintType: KeyPointHint>(&self, hint: HintType) -> Vec<Self::ValueType> {
412 if hint.weight().allow_light_points() && self.size() <= hint.bold_points() * 2 {
413 let coord: <Range<T> as AsRangedCoord>::CoordDescType = self.0.clone().into();
414 let normal = coord.key_points(hint.max_num_points());
415 return normal;
416 }
417 self.bold_key_points(&hint)
418 }
419}
420
421impl<T: TimeValue + Clone> DiscreteRanged for Monthly<T>
422where
423 Range<T>: AsRangedCoord<Value = T>,
424{
425 fn size(&self) -> usize {
426 let (start_year, start_month) = {
427 let ceil = self.0.start.date_ceil();
428 (ceil.year(), ceil.month())
429 };
430 let (end_year, end_month) = {
431 let floor = self.0.end.date_floor();
432 (floor.year(), floor.month())
433 };
434 ((end_year - start_year).max(0) * 12
435 + (1 - start_month as i32)
436 + (end_month as i32 - 1)
437 + 1)
438 .max(0) as usize
439 }
440
441 fn index_of(&self, value: &T) -> Option<usize> {
442 let this_year = value.date_floor().year();
443 let this_month = value.date_floor().month();
444
445 let start_year = self.0.start.date_ceil().year();
446 let start_month = self.0.start.date_ceil().month();
447
448 let ret = (this_year - start_year).max(0) * 12
449 + (1 - start_month as i32)
450 + (this_month as i32 - 1);
451 if ret >= 0 {
452 return Some(ret as usize);
453 }
454 None
455 }
456
457 fn from_index(&self, index: usize) -> Option<T> {
458 if index == 0 {
459 return Some(T::earliest_after_date(self.0.start.date_ceil()));
460 }
461 let index_from_start_year = index + (self.0.start.date_ceil().month() - 1) as usize;
462 let year = self.0.start.date_ceil().year() + index_from_start_year as i32 / 12;
463 let month = index_from_start_year % 12;
464 Some(T::earliest_after_date(self.0.start.ymd(
465 year,
466 month as u32 + 1,
467 1,
468 )))
469 }
470}
471
472/// Indicate the coord has a yearly granularity.
473#[derive(Clone)]
474pub struct Yearly<T: TimeValue>(Range<T>);
475
476fn generate_yearly_keypoints<T: TimeValue>(
477 max_points: usize,
478 mut start_year: i32,
479 start_month: u32,
480 mut end_year: i32,
481 end_month: u32,
482 builder: &T,
483) -> Vec<T> {
484 if start_month > end_month {
485 end_year -= 1;
486 }
487
488 let mut exp10 = 1;
489
490 while (end_year - start_year + 1) as usize / (exp10 * 10) > max_points {
491 exp10 *= 10;
492 }
493
494 let mut freq = exp10;
495
496 for try_freq in &[1, 2, 5, 10] {
497 freq = *try_freq * exp10;
498 if (end_year - start_year + 1) as usize / (exp10 * *try_freq) <= max_points {
499 break;
500 }
501 }
502
503 let mut ret = vec![];
504
505 while start_year <= end_year {
506 ret.push(T::earliest_after_date(builder.ymd(
507 start_year,
508 start_month,
509 1,
510 )));
511 start_year += freq as i32;
512 }
513
514 ret
515}
516
517impl<T: TimeValue + Datelike + Clone> ValueFormatter<T> for Yearly<T> {
518 fn format(value: &T) -> String {
519 format!("{}-{}", value.year(), value.month())
520 }
521}
522
523impl<T: TimeValue + Clone> Ranged for Yearly<T>
524where
525 Range<T>: AsRangedCoord<Value = T>,
526{
527 type FormatOption = NoDefaultFormatting;
528 type ValueType = T;
529
530 fn range(&self) -> Range<T> {
531 self.0.start.clone()..self.0.end.clone()
532 }
533
534 fn map(&self, value: &Self::ValueType, limit: (i32, i32)) -> i32 {
535 T::map_coord(value, &self.0.start, &self.0.end, limit)
536 }
537
538 fn key_points<HintType: KeyPointHint>(&self, hint: HintType) -> Vec<Self::ValueType> {
539 if hint.weight().allow_light_points() && self.size() <= hint.bold_points() * 2 {
540 return Monthly(self.0.clone()).key_points(hint);
541 }
542 let max_points = hint.max_num_points();
543 let start_date = self.0.start.date_ceil();
544 let end_date = self.0.end.date_floor();
545
546 let mut start_year = start_date.year();
547 let mut start_month = start_date.month();
548 let start_day = start_date.day();
549
550 let end_year = end_date.year();
551 let end_month = end_date.month();
552
553 if start_day != 1 {
554 start_month += 1;
555 if start_month == 13 {
556 start_month = 1;
557 start_year += 1;
558 }
559 }
560
561 generate_yearly_keypoints(
562 max_points,
563 start_year,
564 start_month,
565 end_year,
566 end_month,
567 &self.0.start,
568 )
569 }
570}
571
572impl<T: TimeValue + Clone> DiscreteRanged for Yearly<T>
573where
574 Range<T>: AsRangedCoord<Value = T>,
575{
576 fn size(&self) -> usize {
577 let year_start = self.0.start.date_ceil().year();
578 let year_end = self.0.end.date_floor().year();
579 ((year_end - year_start).max(-1) + 1) as usize
580 }
581
582 fn index_of(&self, value: &T) -> Option<usize> {
583 let year_start = self.0.start.date_ceil().year();
584 let year_value = value.date_floor().year();
585 let ret = year_value - year_start;
586 if ret < 0 {
587 return None;
588 }
589 Some(ret as usize)
590 }
591
592 fn from_index(&self, index: usize) -> Option<T> {
593 let year = self.0.start.date_ceil().year() + index as i32;
594 let ret = T::earliest_after_date(self.0.start.ymd(year, 1, 1));
595 if ret.date_ceil() <= self.0.start.date_floor() {
596 return Some(self.0.start.clone());
597 }
598 Some(ret)
599 }
600}
601
602/// The trait that converts a normal date coord into a monthly one
603pub trait IntoMonthly<T: TimeValue> {
604 /// Converts a normal date coord into a monthly one
605 fn monthly(self) -> Monthly<T>;
606}
607
608/// The trait that converts a normal date coord into a yearly one
609pub trait IntoYearly<T: TimeValue> {
610 /// Converts a normal date coord into a yearly one
611 fn yearly(self) -> Yearly<T>;
612}
613
614impl<T: TimeValue> IntoMonthly<T> for Range<T> {
615 fn monthly(self) -> Monthly<T> {
616 Monthly(self)
617 }
618}
619
620impl<T: TimeValue> IntoYearly<T> for Range<T> {
621 fn yearly(self) -> Yearly<T> {
622 Yearly(self)
623 }
624}
625
626/// The ranged coordinate for the date and time
627#[derive(Clone)]
628pub struct RangedDateTime<DT: Datelike + Timelike + TimeValue>(DT, DT);
629
630impl<Z: TimeZone> AsRangedCoord for Range<DateTime<Z>> {
631 type CoordDescType = RangedDateTime<DateTime<Z>>;
632 type Value = DateTime<Z>;
633}
634
635impl<Z: TimeZone> From<Range<DateTime<Z>>> for RangedDateTime<DateTime<Z>> {
636 fn from(range: Range<DateTime<Z>>) -> Self {
637 Self(range.start, range.end)
638 }
639}
640
641impl From<Range<NaiveDateTime>> for RangedDateTime<NaiveDateTime> {
642 fn from(range: Range<NaiveDateTime>) -> Self {
643 Self(range.start, range.end)
644 }
645}
646
647impl<DT> Ranged for RangedDateTime<DT>
648where
649 DT: Datelike + Timelike + TimeValue + Clone + PartialOrd,
650 DT: Add<Duration, Output = DT>,
651 DT: Sub<DT, Output = Duration>,
652 RangedDate<DT::DateType>: Ranged<ValueType = DT::DateType>,
653{
654 type FormatOption = DefaultFormatting;
655 type ValueType = DT;
656
657 fn range(&self) -> Range<DT> {
658 self.0.clone()..self.1.clone()
659 }
660
661 fn map(&self, value: &Self::ValueType, limit: (i32, i32)) -> i32 {
662 TimeValue::map_coord(value, &self.0, &self.1, limit)
663 }
664
665 fn key_points<HintType: KeyPointHint>(&self, hint: HintType) -> Vec<Self::ValueType> {
666 let max_points = hint.max_num_points();
667 let total_span = self.1.clone() - self.0.clone();
668
669 if let Some(total_ns) = total_span.num_nanoseconds() {
670 if let Some(actual_ns_per_point) =
671 compute_period_per_point(total_ns as u64, max_points, true)
672 {
673 let start_time_ns = u64::from(self.0.num_seconds_from_midnight()) * 1_000_000_000
674 + u64::from(self.0.nanosecond());
675
676 let mut start_time = DT::from_date(self.0.date_floor())
677 + Duration::nanoseconds(if start_time_ns % actual_ns_per_point > 0 {
678 start_time_ns + (actual_ns_per_point - start_time_ns % actual_ns_per_point)
679 } else {
680 start_time_ns
681 } as i64);
682
683 let mut ret = vec![];
684
685 while start_time < self.1 {
686 ret.push(start_time.clone());
687 start_time = start_time + Duration::nanoseconds(actual_ns_per_point as i64);
688 }
689
690 return ret;
691 }
692 }
693
694 // Otherwise, it actually behaves like a date
695 let date_range = RangedDate(self.0.date_ceil(), self.1.date_floor());
696
697 date_range
698 .key_points(max_points)
699 .into_iter()
700 .map(DT::from_date)
701 .collect()
702 }
703}
704
705impl<DT> ReversibleRanged for RangedDateTime<DT>
706where
707 DT: Datelike + Timelike + TimeValue + Clone + PartialOrd,
708 DT: Add<Duration, Output = DT>,
709 DT: Sub<DT, Output = Duration>,
710 RangedDate<DT::DateType>: Ranged<ValueType = DT::DateType>,
711{
712 /// Perform the reverse mapping
713 fn unmap(&self, input: i32, limit: (i32, i32)) -> Option<Self::ValueType> {
714 Some(TimeValue::unmap_coord(input, &self.0, &self.1, limit))
715 }
716}
717
718/// The coordinate that for duration of time
719#[derive(Clone)]
720pub struct RangedDuration(Duration, Duration);
721
722impl AsRangedCoord for Range<Duration> {
723 type CoordDescType = RangedDuration;
724 type Value = Duration;
725}
726
727impl From<Range<Duration>> for RangedDuration {
728 fn from(range: Range<Duration>) -> Self {
729 Self(range.start, range.end)
730 }
731}
732
733impl Ranged for RangedDuration {
734 type FormatOption = DefaultFormatting;
735 type ValueType = Duration;
736
737 fn range(&self) -> Range<Duration> {
738 self.0..self.1
739 }
740
741 fn map(&self, value: &Self::ValueType, limit: (i32, i32)) -> i32 {
742 let total_span = self.1 - self.0;
743 let value_span = *value - self.0;
744
745 if let Some(total_ns) = total_span.num_nanoseconds() {
746 if let Some(value_ns) = value_span.num_nanoseconds() {
747 return limit.0
748 + (f64::from(limit.1 - limit.0) * value_ns as f64 / total_ns as f64 + 1e-10)
749 as i32;
750 }
751 return limit.1;
752 }
753
754 let total_days = total_span.num_days();
755 let value_days = value_span.num_days();
756
757 limit.0
758 + (f64::from(limit.1 - limit.0) * value_days as f64 / total_days as f64 + 1e-10) as i32
759 }
760
761 fn key_points<HintType: KeyPointHint>(&self, hint: HintType) -> Vec<Self::ValueType> {
762 let max_points = hint.max_num_points();
763 let total_span = self.1 - self.0;
764
765 if let Some(total_ns) = total_span.num_nanoseconds() {
766 if let Some(period) = compute_period_per_point(total_ns as u64, max_points, false) {
767 let mut start_ns = self.0.num_nanoseconds().unwrap();
768
769 if start_ns as u64 % period > 0 {
770 if start_ns > 0 {
771 start_ns += period as i64 - (start_ns % period as i64);
772 } else {
773 start_ns -= start_ns % period as i64;
774 }
775 }
776
777 let mut current = Duration::nanoseconds(start_ns);
778 let mut ret = vec![];
779
780 while current < self.1 {
781 ret.push(current);
782 current = current + Duration::nanoseconds(period as i64);
783 }
784
785 return ret;
786 }
787 }
788
789 let begin_days = self.0.num_days();
790 let end_days = self.1.num_days();
791
792 let mut days_per_tick = 1;
793 let mut idx = 0;
794 const MULTIPLIER: &[i32] = &[1, 2, 5];
795
796 while (end_days - begin_days) / i64::from(days_per_tick * MULTIPLIER[idx])
797 > max_points as i64
798 {
799 idx += 1;
800 if idx == MULTIPLIER.len() {
801 idx = 0;
802 days_per_tick *= 10;
803 }
804 }
805
806 days_per_tick *= MULTIPLIER[idx];
807
808 let mut ret = vec![];
809
810 let mut current = Duration::days(
811 self.0.num_days()
812 + if Duration::days(self.0.num_days()) != self.0 {
813 1
814 } else {
815 0
816 },
817 );
818
819 while current < self.1 {
820 ret.push(current);
821 current = current + Duration::days(i64::from(days_per_tick));
822 }
823
824 ret
825 }
826}
827
828#[allow(clippy::inconsistent_digit_grouping)]
829fn compute_period_per_point(total_ns: u64, max_points: usize, sub_daily: bool) -> Option<u64> {
830 let min_ns_per_point = total_ns as f64 / max_points as f64;
831 let actual_ns_per_point: u64 = (10u64).pow((min_ns_per_point as f64).log10().floor() as u32);
832
833 fn determine_actual_ns_per_point(
834 total_ns: u64,
835 mut actual_ns_per_point: u64,
836 units: &[u64],
837 base: u64,
838 max_points: usize,
839 ) -> u64 {
840 let mut unit_per_point_idx = 0;
841 while total_ns / actual_ns_per_point > max_points as u64 * units[unit_per_point_idx] {
842 unit_per_point_idx += 1;
843 if unit_per_point_idx == units.len() {
844 unit_per_point_idx = 0;
845 actual_ns_per_point *= base;
846 }
847 }
848 units[unit_per_point_idx] * actual_ns_per_point
849 }
850
851 if actual_ns_per_point < 1_000_000_000 {
852 Some(determine_actual_ns_per_point(
853 total_ns as u64,
854 actual_ns_per_point,
855 &[1, 2, 5],
856 10,
857 max_points,
858 ))
859 } else if actual_ns_per_point < 3600_000_000_000 {
860 Some(determine_actual_ns_per_point(
861 total_ns as u64,
862 1_000_000_000,
863 &[1, 2, 5, 10, 15, 20, 30],
864 60,
865 max_points,
866 ))
867 } else if actual_ns_per_point < 3600_000_000_000 * 24 {
868 Some(determine_actual_ns_per_point(
869 total_ns as u64,
870 3600_000_000_000,
871 &[1, 2, 4, 8, 12],
872 24,
873 max_points,
874 ))
875 } else if !sub_daily {
876 if actual_ns_per_point < 3600_000_000_000 * 24 * 10 {
877 Some(determine_actual_ns_per_point(
878 total_ns as u64,
879 3600_000_000_000 * 24,
880 &[1, 2, 5, 7],
881 10,
882 max_points,
883 ))
884 } else {
885 Some(determine_actual_ns_per_point(
886 total_ns as u64,
887 3600_000_000_000 * 24 * 10,
888 &[1, 2, 5],
889 10,
890 max_points,
891 ))
892 }
893 } else {
894 None
895 }
896}
897
898#[cfg(test)]
899mod test {
900 use super::*;
901 use chrono::{TimeZone, Utc};
902
903 #[test]
904 fn test_date_range_long() {
905 let range = Utc.ymd(1000, 1, 1)..Utc.ymd(2999, 1, 1);
906
907 let ranged_coord = Into::<RangedDate<_>>::into(range);
908
909 assert_eq!(ranged_coord.map(&Utc.ymd(1000, 8, 10), (0, 100)), 0);
910 assert_eq!(ranged_coord.map(&Utc.ymd(2999, 8, 10), (0, 100)), 100);
911
912 let kps = ranged_coord.key_points(23);
913
914 assert!(kps.len() <= 23);
915 let max = kps
916 .iter()
917 .zip(kps.iter().skip(1))
918 .map(|(p, n)| (*n - *p).num_days())
919 .max()
920 .unwrap();
921 let min = kps
922 .iter()
923 .zip(kps.iter().skip(1))
924 .map(|(p, n)| (*n - *p).num_days())
925 .min()
926 .unwrap();
927 assert_eq!(max, min);
928 assert_eq!(max % 7, 0);
929 }
930
931 #[test]
932 fn test_date_range_short() {
933 let range = Utc.ymd(2019, 1, 1)..Utc.ymd(2019, 1, 21);
934 let ranged_coord = Into::<RangedDate<_>>::into(range);
935
936 let kps = ranged_coord.key_points(4);
937
938 assert_eq!(kps.len(), 3);
939
940 let max = kps
941 .iter()
942 .zip(kps.iter().skip(1))
943 .map(|(p, n)| (*n - *p).num_days())
944 .max()
945 .unwrap();
946 let min = kps
947 .iter()
948 .zip(kps.iter().skip(1))
949 .map(|(p, n)| (*n - *p).num_days())
950 .min()
951 .unwrap();
952 assert_eq!(max, min);
953 assert_eq!(max, 7);
954
955 let kps = ranged_coord.key_points(30);
956 assert_eq!(kps.len(), 21);
957 let max = kps
958 .iter()
959 .zip(kps.iter().skip(1))
960 .map(|(p, n)| (*n - *p).num_days())
961 .max()
962 .unwrap();
963 let min = kps
964 .iter()
965 .zip(kps.iter().skip(1))
966 .map(|(p, n)| (*n - *p).num_days())
967 .min()
968 .unwrap();
969 assert_eq!(max, min);
970 assert_eq!(max, 1);
971 }
972
973 #[test]
974 fn test_yearly_date_range() {
975 use crate::coord::ranged1d::BoldPoints;
976 let range = Utc.ymd(1000, 8, 5)..Utc.ymd(2999, 1, 1);
977 let ranged_coord = range.yearly();
978
979 assert_eq!(ranged_coord.map(&Utc.ymd(1000, 8, 10), (0, 100)), 0);
980 assert_eq!(ranged_coord.map(&Utc.ymd(2999, 8, 10), (0, 100)), 100);
981
982 let kps = ranged_coord.key_points(23);
983
984 assert!(kps.len() <= 23);
985 let max = kps
986 .iter()
987 .zip(kps.iter().skip(1))
988 .map(|(p, n)| (*n - *p).num_days())
989 .max()
990 .unwrap();
991 let min = kps
992 .iter()
993 .zip(kps.iter().skip(1))
994 .map(|(p, n)| (*n - *p).num_days())
995 .min()
996 .unwrap();
997 assert!(max != min);
998
999 assert!(kps.into_iter().all(|x| x.month() == 9 && x.day() == 1));
1000
1001 let range = Utc.ymd(2019, 8, 5)..Utc.ymd(2020, 1, 1);
1002 let ranged_coord = range.yearly();
1003 let kps = ranged_coord.key_points(BoldPoints(23));
1004 assert!(kps.len() == 1);
1005 }
1006
1007 #[test]
1008 fn test_monthly_date_range() {
1009 let range = Utc.ymd(2019, 8, 5)..Utc.ymd(2020, 9, 1);
1010 let ranged_coord = range.monthly();
1011
1012 use crate::coord::ranged1d::BoldPoints;
1013
1014 let kps = ranged_coord.key_points(BoldPoints(15));
1015
1016 assert!(kps.len() <= 15);
1017 assert!(kps.iter().all(|x| x.day() == 1));
1018 assert!(kps.into_iter().any(|x| x.month() != 9));
1019
1020 let kps = ranged_coord.key_points(BoldPoints(5));
1021 assert!(kps.len() <= 5);
1022 assert!(kps.iter().all(|x| x.day() == 1));
1023 let kps: Vec<_> = kps.into_iter().map(|x| x.month()).collect();
1024 assert_eq!(kps, vec![9, 12, 3, 6, 9]);
1025
1026 // TODO: Investigate why max_point = 1 breaks the contract
1027 let kps = ranged_coord.key_points(3);
1028 assert!(kps.len() == 3);
1029 assert!(kps.iter().all(|x| x.day() == 1));
1030 let kps: Vec<_> = kps.into_iter().map(|x| x.month()).collect();
1031 assert_eq!(kps, vec![9, 3, 9]);
1032 }
1033
1034 #[test]
1035 fn test_datetime_long_range() {
1036 let coord: RangedDateTime<_> =
1037 (Utc.ymd(1000, 1, 1).and_hms(0, 0, 0)..Utc.ymd(3000, 1, 1).and_hms(0, 0, 0)).into();
1038
1039 assert_eq!(
1040 coord.map(&Utc.ymd(1000, 1, 1).and_hms(0, 0, 0), (0, 100)),
1041 0
1042 );
1043 assert_eq!(
1044 coord.map(&Utc.ymd(3000, 1, 1).and_hms(0, 0, 0), (0, 100)),
1045 100
1046 );
1047
1048 let kps = coord.key_points(23);
1049
1050 assert!(kps.len() <= 23);
1051 let max = kps
1052 .iter()
1053 .zip(kps.iter().skip(1))
1054 .map(|(p, n)| (*n - *p).num_seconds())
1055 .max()
1056 .unwrap();
1057 let min = kps
1058 .iter()
1059 .zip(kps.iter().skip(1))
1060 .map(|(p, n)| (*n - *p).num_seconds())
1061 .min()
1062 .unwrap();
1063 assert!(max == min);
1064 assert!(max % (24 * 3600 * 7) == 0);
1065 }
1066
1067 #[test]
1068 fn test_datetime_medium_range() {
1069 let coord: RangedDateTime<_> =
1070 (Utc.ymd(2019, 1, 1).and_hms(0, 0, 0)..Utc.ymd(2019, 1, 11).and_hms(0, 0, 0)).into();
1071
1072 let kps = coord.key_points(23);
1073
1074 assert!(kps.len() <= 23);
1075 let max = kps
1076 .iter()
1077 .zip(kps.iter().skip(1))
1078 .map(|(p, n)| (*n - *p).num_seconds())
1079 .max()
1080 .unwrap();
1081 let min = kps
1082 .iter()
1083 .zip(kps.iter().skip(1))
1084 .map(|(p, n)| (*n - *p).num_seconds())
1085 .min()
1086 .unwrap();
1087 assert!(max == min);
1088 assert_eq!(max, 12 * 3600);
1089 }
1090
1091 #[test]
1092 fn test_datetime_short_range() {
1093 let coord: RangedDateTime<_> =
1094 (Utc.ymd(2019, 1, 1).and_hms(0, 0, 0)..Utc.ymd(2019, 1, 2).and_hms(0, 0, 0)).into();
1095
1096 let kps = coord.key_points(50);
1097
1098 assert!(kps.len() <= 50);
1099 let max = kps
1100 .iter()
1101 .zip(kps.iter().skip(1))
1102 .map(|(p, n)| (*n - *p).num_seconds())
1103 .max()
1104 .unwrap();
1105 let min = kps
1106 .iter()
1107 .zip(kps.iter().skip(1))
1108 .map(|(p, n)| (*n - *p).num_seconds())
1109 .min()
1110 .unwrap();
1111 assert!(max == min);
1112 assert_eq!(max, 1800);
1113 }
1114
1115 #[test]
1116 fn test_datetime_nano_range() {
1117 let start = Utc.ymd(2019, 1, 1).and_hms(0, 0, 0);
1118 let end = start.clone() + Duration::nanoseconds(100);
1119 let coord: RangedDateTime<_> = (start..end).into();
1120
1121 let kps = coord.key_points(50);
1122
1123 assert!(kps.len() <= 50);
1124 let max = kps
1125 .iter()
1126 .zip(kps.iter().skip(1))
1127 .map(|(p, n)| (*n - *p).num_nanoseconds().unwrap())
1128 .max()
1129 .unwrap();
1130 let min = kps
1131 .iter()
1132 .zip(kps.iter().skip(1))
1133 .map(|(p, n)| (*n - *p).num_nanoseconds().unwrap())
1134 .min()
1135 .unwrap();
1136 assert!(max == min);
1137 assert_eq!(max, 2);
1138 }
1139
1140 #[test]
1141 fn test_duration_long_range() {
1142 let coord: RangedDuration = (Duration::days(-1000000)..Duration::days(1000000)).into();
1143
1144 assert_eq!(coord.map(&Duration::days(-1000000), (0, 100)), 0);
1145 assert_eq!(coord.map(&Duration::days(1000000), (0, 100)), 100);
1146
1147 let kps = coord.key_points(23);
1148
1149 assert!(kps.len() <= 23);
1150 let max = kps
1151 .iter()
1152 .zip(kps.iter().skip(1))
1153 .map(|(p, n)| (*n - *p).num_seconds())
1154 .max()
1155 .unwrap();
1156 let min = kps
1157 .iter()
1158 .zip(kps.iter().skip(1))
1159 .map(|(p, n)| (*n - *p).num_seconds())
1160 .min()
1161 .unwrap();
1162 assert!(max == min);
1163 assert!(max % (24 * 3600 * 10000) == 0);
1164 }
1165
1166 #[test]
1167 fn test_duration_daily_range() {
1168 let coord: RangedDuration = (Duration::days(0)..Duration::hours(25)).into();
1169
1170 let kps = coord.key_points(23);
1171
1172 assert!(kps.len() <= 23);
1173 let max = kps
1174 .iter()
1175 .zip(kps.iter().skip(1))
1176 .map(|(p, n)| (*n - *p).num_seconds())
1177 .max()
1178 .unwrap();
1179 let min = kps
1180 .iter()
1181 .zip(kps.iter().skip(1))
1182 .map(|(p, n)| (*n - *p).num_seconds())
1183 .min()
1184 .unwrap();
1185 assert!(max == min);
1186 assert_eq!(max, 3600 * 2);
1187 }
1188
1189 #[test]
1190 fn test_date_discrete() {
1191 let coord: RangedDate<Date<_>> = (Utc.ymd(2019, 1, 1)..Utc.ymd(2019, 12, 31)).into();
1192 assert_eq!(coord.size(), 365);
1193 assert_eq!(coord.index_of(&Utc.ymd(2019, 2, 28)), Some(31 + 28 - 1));
1194 assert_eq!(coord.from_index(364), Some(Utc.ymd(2019, 12, 31)));
1195 }
1196
1197 #[test]
1198 fn test_monthly_discrete() {
1199 let coord1 = (Utc.ymd(2019, 1, 10)..Utc.ymd(2019, 12, 31)).monthly();
1200 let coord2 = (Utc.ymd(2019, 1, 10)..Utc.ymd(2020, 1, 1)).monthly();
1201 assert_eq!(coord1.size(), 12);
1202 assert_eq!(coord2.size(), 13);
1203
1204 for i in 1..=12 {
1205 assert_eq!(coord1.from_index(i - 1).unwrap().month(), i as u32);
1206 assert_eq!(
1207 coord1.index_of(&coord1.from_index(i - 1).unwrap()).unwrap(),
1208 i - 1
1209 );
1210 }
1211 }
1212
1213 #[test]
1214 fn test_yearly_discrete() {
1215 let coord1 = (Utc.ymd(2000, 1, 10)..Utc.ymd(2019, 12, 31)).yearly();
1216 assert_eq!(coord1.size(), 20);
1217
1218 for i in 0..20 {
1219 assert_eq!(coord1.from_index(i).unwrap().year(), 2000 + i as i32);
1220 assert_eq!(coord1.index_of(&coord1.from_index(i).unwrap()).unwrap(), i);
1221 }
1222 }
1223
1224 #[test]
1225 fn test_datetime_with_unmap() {
1226 let start_time = Utc.ymd(2021, 1, 1).and_hms(8, 0, 0);
1227 let end_time = Utc.ymd(2023, 1, 1).and_hms(8, 0, 0);
1228 let mid = Utc.ymd(2022, 1, 1).and_hms(8, 0, 0);
1229 let coord: RangedDateTime<_> = (start_time..end_time).into();
1230 let pos = coord.map(&mid, (1000, 2000));
1231 assert_eq!(pos, 1500);
1232 let value = coord.unmap(pos, (1000, 2000));
1233 assert_eq!(value, Some(mid));
1234 }
1235
1236 #[test]
1237 fn test_naivedatetime_with_unmap() {
1238 let start_time = NaiveDate::from_ymd(2021, 1, 1).and_hms_milli(8, 0, 0, 0);
1239 let end_time = NaiveDate::from_ymd(2023, 1, 1).and_hms_milli(8, 0, 0, 0);
1240 let mid = NaiveDate::from_ymd(2022, 1, 1).and_hms_milli(8, 0, 0, 0);
1241 let coord: RangedDateTime<_> = (start_time..end_time).into();
1242 let pos = coord.map(&mid, (1000, 2000));
1243 assert_eq!(pos, 1500);
1244 let value = coord.unmap(pos, (1000, 2000));
1245 assert_eq!(value, Some(mid));
1246 }
1247
1248 #[test]
1249 fn test_date_with_unmap() {
1250 let start_date = Utc.ymd(2021, 1, 1);
1251 let end_date = Utc.ymd(2023, 1, 1);
1252 let mid = Utc.ymd(2022, 1, 1);
1253 let coord: RangedDate<Date<_>> = (start_date..end_date).into();
1254 let pos = coord.map(&mid, (1000, 2000));
1255 assert_eq!(pos, 1500);
1256 let value = coord.unmap(pos, (1000, 2000));
1257 assert_eq!(value, Some(mid));
1258 }
1259
1260 #[test]
1261 fn test_naivedate_with_unmap() {
1262 let start_date = NaiveDate::from_ymd(2021, 1, 1);
1263 let end_date = NaiveDate::from_ymd(2023, 1, 1);
1264 let mid = NaiveDate::from_ymd(2022, 1, 1);
1265 let coord: RangedDate<NaiveDate> = (start_date..end_date).into();
1266 let pos = coord.map(&mid, (1000, 2000));
1267 assert_eq!(pos, 1500);
1268 let value = coord.unmap(pos, (1000, 2000));
1269 assert_eq!(value, Some(mid));
1270 }
1271
1272 #[test]
1273 fn test_datetime_unmap_for_nanoseconds() {
1274 let start_time = Utc.ymd(2021, 1, 1).and_hms(8, 0, 0);
1275 let end_time = start_time + Duration::nanoseconds(1900);
1276 let mid = start_time + Duration::nanoseconds(950);
1277 let coord: RangedDateTime<_> = (start_time..end_time).into();
1278 let pos = coord.map(&mid, (1000, 2000));
1279 assert_eq!(pos, 1500);
1280 let value = coord.unmap(pos, (1000, 2000));
1281 assert_eq!(value, Some(mid));
1282 }
1283
1284 #[test]
1285 fn test_datetime_unmap_for_nanoseconds_small_period() {
1286 let start_time = Utc.ymd(2021, 1, 1).and_hms(8, 0, 0);
1287 let end_time = start_time + Duration::nanoseconds(400);
1288 let coord: RangedDateTime<_> = (start_time..end_time).into();
1289 let value = coord.unmap(2000, (1000, 2000));
1290 assert_eq!(value, Some(end_time));
1291 let mid = start_time + Duration::nanoseconds(200);
1292 let value = coord.unmap(500, (0, 1000));
1293 assert_eq!(value, Some(mid));
1294 }
1295}
1296