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 |
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 | |
78 | impl 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 | |
105 | impl<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 | |
132 | impl<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 | |
164 | impl 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)] |
198 | pub struct RangedDate<D: Datelike>(D, D); |
199 | |
200 | impl<D: Datelike> From<Range<D>> for RangedDate<D> { |
201 | fn from(range: Range<D>) -> Self { |
202 | Self(range.start, range.end) |
203 | } |
204 | } |
205 | |
206 | impl<D> Ranged for RangedDate<D> |
207 | where |
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 | |
258 | impl<D> DiscreteRanged for RangedDate<D> |
259 | where |
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 | |
279 | impl<Z: TimeZone> AsRangedCoord for Range<Date<Z>> { |
280 | type CoordDescType = RangedDate<Date<Z>>; |
281 | type Value = Date<Z>; |
282 | } |
283 | |
284 | impl 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)] |
295 | pub struct Monthly<T: TimeValue>(Range<T>); |
296 | |
297 | impl<T: TimeValue + Datelike + Clone> ValueFormatter<T> for Monthly<T> { |
298 | fn format(value: &T) -> String { |
299 | format!("{}-{}" , value.year(), value.month()) |
300 | } |
301 | } |
302 | |
303 | impl<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 | |
396 | impl<T: TimeValue + Clone> Ranged for Monthly<T> |
397 | where |
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 | |
421 | impl<T: TimeValue + Clone> DiscreteRanged for Monthly<T> |
422 | where |
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)] |
474 | pub struct Yearly<T: TimeValue>(Range<T>); |
475 | |
476 | fn 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 | |
517 | impl<T: TimeValue + Datelike + Clone> ValueFormatter<T> for Yearly<T> { |
518 | fn format(value: &T) -> String { |
519 | format!("{}-{}" , value.year(), value.month()) |
520 | } |
521 | } |
522 | |
523 | impl<T: TimeValue + Clone> Ranged for Yearly<T> |
524 | where |
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 | |
572 | impl<T: TimeValue + Clone> DiscreteRanged for Yearly<T> |
573 | where |
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 |
603 | pub 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 |
609 | pub trait IntoYearly<T: TimeValue> { |
610 | /// Converts a normal date coord into a yearly one |
611 | fn yearly(self) -> Yearly<T>; |
612 | } |
613 | |
614 | impl<T: TimeValue> IntoMonthly<T> for Range<T> { |
615 | fn monthly(self) -> Monthly<T> { |
616 | Monthly(self) |
617 | } |
618 | } |
619 | |
620 | impl<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)] |
628 | pub struct RangedDateTime<DT: Datelike + Timelike + TimeValue>(DT, DT); |
629 | |
630 | impl<Z: TimeZone> AsRangedCoord for Range<DateTime<Z>> { |
631 | type CoordDescType = RangedDateTime<DateTime<Z>>; |
632 | type Value = DateTime<Z>; |
633 | } |
634 | |
635 | impl<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 | |
641 | impl From<Range<NaiveDateTime>> for RangedDateTime<NaiveDateTime> { |
642 | fn from(range: Range<NaiveDateTime>) -> Self { |
643 | Self(range.start, range.end) |
644 | } |
645 | } |
646 | |
647 | impl<DT> Ranged for RangedDateTime<DT> |
648 | where |
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 | |
705 | impl<DT> ReversibleRanged for RangedDateTime<DT> |
706 | where |
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)] |
720 | pub struct RangedDuration(Duration, Duration); |
721 | |
722 | impl AsRangedCoord for Range<Duration> { |
723 | type CoordDescType = RangedDuration; |
724 | type Value = Duration; |
725 | } |
726 | |
727 | impl From<Range<Duration>> for RangedDuration { |
728 | fn from(range: Range<Duration>) -> Self { |
729 | Self(range.start, range.end) |
730 | } |
731 | } |
732 | |
733 | impl 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)] |
829 | fn 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)] |
899 | mod 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 | |