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