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 |