1use core::fmt::{self, Debug, Display, Formatter};
2use core::ops::{Add, Sub};
3use core::str::FromStr;
4use core::time::Duration;
5
6use crate::calendar::Iso;
7use crate::{AsMoment, AsTime, Calendar, Date, DateTime, PlainTime, TimeInterval, TimeZone};
8
9const SECONDS_IN_DAY: i64 = 86_400;
10
11#[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
93pub struct Moment {
94 pub(crate) secs: i64,
95 pub(crate) nsec: u32,
96}
97
98impl Moment {
100 pub const MAX: Self = Self {
112 secs: i64::MAX,
113 nsec: 999_999_999,
114 };
115
116 pub const MIN: Self = Self {
128 secs: i64::MIN,
129 nsec: 0,
130 };
131}
132
133impl Moment {
135 #[cfg(feature = "std")]
148 #[must_use]
149 pub fn now() -> Self {
150 std::time::SystemTime::now().into()
151 }
152}
153
154impl Moment {
156 #[must_use]
159 pub(crate) const fn from_date<C: Calendar>(date: Date<C>) -> Self {
160 Self {
161 secs: (date.as_days_since_unix_epoch() as i64) * SECONDS_IN_DAY,
162 nsec: 0,
163 }
164 }
165
166 #[must_use]
174 pub const fn from_unix(seconds: i64, nanoseconds: u32) -> Self {
175 let mut nanoseconds = nanoseconds as i128;
176 nanoseconds += seconds as i128 * 1_000_000_000;
177
178 Self::from_unix_nanoseconds(nanoseconds)
179 }
180
181 #[must_use]
196 pub const fn from_unix_seconds(seconds: i64) -> Self {
197 Self {
198 secs: seconds,
199 nsec: 0,
200 }
201 }
202
203 #[must_use]
206 pub const fn from_unix_milliseconds(milliseconds: i64) -> Self {
207 Self::from_unix_nanoseconds(milliseconds as i128 * 1_000_000)
208 }
209
210 #[must_use]
213 pub const fn from_unix_microseconds(microseconds: i128) -> Self {
214 Self::from_unix_nanoseconds(microseconds * 1_000)
215 }
216
217 #[must_use]
220 pub const fn from_unix_nanoseconds(nanoseconds: i128) -> Self {
221 let secs = nanoseconds.div_euclid(1_000_000_000) as i64;
222 let nsec = nanoseconds.rem_euclid(1_000_000_000) as u32;
223
224 Self { secs, nsec }
225 }
226
227 #[cfg(feature = "astronomy")]
236 pub fn from_julian_day(jd: f64) -> crate::Result<Self> {
237 crate::astronomy::from_julian_day(jd)
238 }
239
240 #[cfg(feature = "astronomy")]
247 pub fn from_julian_ephemeris_day(jde: f64) -> crate::Result<Self> {
248 let mut moment = Self::from_julian_day(jde)?;
249
250 let dt = crate::astronomy::delta_t(moment.to_julian_year());
252 moment.secs -= dt as i64;
253
254 Ok(moment)
255 }
256}
257
258impl Moment {
260 #[must_use]
264 pub(crate) fn at(self, tz: &TimeZone) -> DateTime<Iso> {
265 DateTime::from_moment(Iso, self).at(tz)
266 }
267
268 #[must_use]
271 pub(crate) const fn on<C: Calendar>(self, calendar: C) -> DateTime<C> {
272 DateTime::from_moment(calendar, self)
273 }
274
275 #[must_use]
278 pub(crate) const fn as_date<C: Calendar>(self, calendar: C) -> Date<C> {
279 Date::from_unix_timestamp(calendar, self.secs)
280 }
281}
282
283impl Moment {
285 #[must_use]
288 pub const fn to_unix(self) -> (i64, u32) {
289 (self.secs, self.nsec)
290 }
291
292 #[must_use]
295 pub const fn to_unix_seconds(self) -> i64 {
296 self.secs
297 }
298
299 #[must_use]
301 pub const fn to_unix_milliseconds(self) -> i64 {
302 self.to_unix_nanoseconds().div_euclid(1_000_000) as i64
303 }
304
305 #[must_use]
307 pub const fn to_unix_microseconds(self) -> i64 {
308 self.to_unix_nanoseconds().div_euclid(1_000) as i64
309 }
310
311 #[must_use]
313 pub const fn to_unix_nanoseconds(self) -> i128 {
314 ((self.secs as i128) * 1_000_000_000) + (self.nsec as i128)
315 }
316
317 #[cfg(feature = "astronomy")]
322 #[must_use]
323 pub fn to_julian_day(self) -> f64 {
324 crate::astronomy::to_julian_day(self)
325 }
326
327 #[cfg(feature = "astronomy")]
336 #[must_use]
337 pub fn to_julian_year(self) -> f64 {
338 crate::astronomy::to_julian_year(self)
339 }
340}
341
342impl Add<TimeInterval> for Moment {
343 type Output = Self;
344
345 fn add(self, rhs: TimeInterval) -> Self::Output {
346 let nanoseconds = self
347 .to_unix_nanoseconds()
348 .checked_add(rhs.as_total_nanoseconds())
349 .expect("overflow adding `TimeInterval` to `Moment`");
350
351 Self::from_unix_nanoseconds(nanoseconds)
352 }
353}
354
355impl Add<Moment> for TimeInterval {
356 type Output = Moment;
357
358 #[inline]
359 fn add(self, rhs: Moment) -> Self::Output {
360 rhs + self
361 }
362}
363
364impl Sub<TimeInterval> for Moment {
365 type Output = Self;
366
367 fn sub(self, rhs: TimeInterval) -> Self::Output {
368 let nanoseconds = self
369 .to_unix_nanoseconds()
370 .checked_sub(rhs.as_total_nanoseconds())
371 .expect("overflow subtracting `TimeInterval` from `Moment`");
372
373 Self::from_unix_nanoseconds(nanoseconds)
374 }
375}
376
377impl Add<Duration> for Moment {
378 type Output = Self;
379
380 fn add(self, rhs: Duration) -> Self::Output {
381 let nanoseconds = i128::try_from(rhs.as_nanos())
382 .ok()
383 .and_then(|rhs| self.to_unix_nanoseconds().checked_add(rhs))
384 .expect("overflow adding `Duration` to `Moment`");
385
386 Self::from_unix_nanoseconds(nanoseconds)
387 }
388}
389
390impl Add<Moment> for Duration {
391 type Output = Moment;
392
393 #[inline]
394 fn add(self, rhs: Moment) -> Self::Output {
395 rhs + self
396 }
397}
398
399impl Sub<Duration> for Moment {
400 type Output = Self;
401
402 fn sub(self, rhs: Duration) -> Self::Output {
403 let nanoseconds = i128::try_from(rhs.as_nanos())
404 .ok()
405 .and_then(|rhs| self.to_unix_nanoseconds().checked_sub(rhs))
406 .expect("overflow subtracting `Duration` from `Moment`");
407
408 Self::from_unix_nanoseconds(nanoseconds)
409 }
410}
411
412#[cfg(feature = "std")]
413impl From<std::time::SystemTime> for Moment {
414 fn from(time: std::time::SystemTime) -> Self {
415 use std::time::SystemTime;
416
417 let (secs, nsec) = time.duration_since(SystemTime::UNIX_EPOCH).map_or_else(
418 |_| {
419 let ts = SystemTime::UNIX_EPOCH
420 .duration_since(time)
421 .unwrap_or_default();
422
423 (-(ts.as_secs() as i64), ts.subsec_nanos())
424 },
425 |ts| (ts.as_secs() as i64, ts.subsec_nanos()),
426 );
427
428 Self { secs, nsec }
429 }
430}
431
432#[cfg(feature = "std")]
433impl From<Moment> for std::time::SystemTime {
434 fn from(moment: Moment) -> Self {
435 let (secs, nsec) = moment.to_unix();
436 let duration = Duration::new(secs.unsigned_abs(), nsec);
437
438 if secs.is_negative() {
439 Self::UNIX_EPOCH - duration
440 } else {
441 Self::UNIX_EPOCH + duration
442 }
443 }
444}
445
446impl<C: Calendar> From<Date<C>> for Moment {
447 fn from(date: Date<C>) -> Self {
448 Self::from_date(date)
449 }
450}
451
452impl Display for Moment {
453 fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
454 f.pad(&self.format_rfc3339())
455 }
456}
457
458impl Debug for Moment {
459 fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
460 <Self as Display>::fmt(self, f)
461 }
462}
463
464impl FromStr for Moment {
465 type Err = crate::Error;
466
467 fn from_str(s: &str) -> Result<Self, Self::Err> {
468 Self::parse_rfc3339(s)
469 }
470}
471
472impl AsMoment for Moment {
473 fn as_moment(&self) -> Self {
474 *self
475 }
476}
477
478impl AsTime for Moment {
479 fn as_time(&self) -> PlainTime {
480 let (secs, nsec) = self.as_timestamp();
481 PlainTime::from_unix_timestamp(secs, nsec)
482 }
483
484 fn as_timestamp(&self) -> (i64, u32) {
485 self.to_unix()
486 }
487}
488
489impl Sub<Self> for Moment {
490 type Output = TimeInterval;
491
492 fn sub(self, rhs: Self) -> Self::Output {
493 TimeInterval::between(rhs, self)
496 }
497}
498
499#[cfg(test)]
500mod tests {
501 use core::time::Duration;
502
503 use test_case::test_case;
504
505 use crate::{AsMoment, DateTime, Moment};
506
507 #[cfg(feature = "astronomy")]
508 #[test_case(2443259.9, "1977-04-26T09:35:59.999991953Z")]
510 #[test_case(2451545.0, "2000-01-01T12:00:00Z")]
511 #[test_case(2415020.5, "1900-01-01T00:00:00Z")]
512 #[test_case(2299161.5, "1582-10-16T00:00:00Z")]
513 #[test_case(2299160.5, "1582-10-15T00:00:00Z")]
514 #[test_case(2299159.5, "1582-10-14T00:00:00Z")]
515 #[test_case(2026871.8, "0837-04-14T07:12:00.000004023Z")]
516 #[test_case(1355671.4, "-1001-08-07T21:35:59.999991953Z")]
517 #[test_case(0.0, "-4713-11-24T12:00:00Z")]
518 fn moment_from_julian_day(jd: f64, expected: &str) -> crate::Result<()> {
519 let moment = Moment::from_julian_day(jd)?;
520
521 assert_eq!(moment.format_rfc3339(), expected);
522 assert_eq!(moment.to_julian_day(), jd);
523
524 let dt = DateTime::parse_rfc3339(expected)?;
525
526 assert_eq!(dt.as_moment(), moment);
527
528 Ok(())
529 }
530
531 #[cfg(feature = "std")]
532 #[test_case(-1727313985 ; "negative 1727313985")]
533 #[test_case(0)]
534 #[test_case(1727313985)]
535 fn moment_from_system_time(timestamp: i64) {
536 use std::time::SystemTime;
537
538 let moment = Moment::from_unix_seconds(timestamp);
539 let st = SystemTime::from(moment);
540 let moment_2 = Moment::from(st);
541
542 assert_eq!(moment_2, moment);
543 }
544
545 #[test_case(1727313985, 86_400, "2024-09-27T01:26:25Z")]
546 #[test_case(1727313985, 18_000, "2024-09-26T06:26:25Z")]
547 #[test_case(1727313985, 600, "2024-09-26T01:36:25Z")]
548 fn moment_add_duration(timestamp: i64, seconds: u64, expected: &str) {
549 let mut moment = Moment::from_unix_seconds(timestamp);
550 moment = moment + Duration::from_secs(seconds);
551
552 assert_eq!(moment.format_rfc3339(), expected);
553 }
554
555 #[test_case(1727313985, 86_400, "2024-09-25T01:26:25Z")]
556 #[test_case(1727313985, 18_000, "2024-09-25T20:26:25Z")]
557 #[test_case(1727313985, 600, "2024-09-26T01:16:25Z")]
558 fn moment_sub_duration(timestamp: i64, seconds: u64, expected: &str) {
559 let mut moment = Moment::from_unix_seconds(timestamp);
560 moment = moment - Duration::from_secs(seconds);
561
562 assert_eq!(moment.format_rfc3339(), expected);
563 }
564
565 #[test_case(1727313985, 1727480105, (1, 22, 8, 40))]
566 fn moment_sub_moment(timestamp_start: i64, timestamp_end: i64, expected: (i64, i8, i8, i8)) {
567 let moment_start = Moment::from_unix_seconds(timestamp_start);
568 let moment_end = Moment::from_unix_seconds(timestamp_end);
569 let ti = moment_end - moment_start;
570
571 assert_eq!(ti.days(), expected.0);
572 assert_eq!(ti.hours(), expected.1);
573 assert_eq!(ti.minutes(), expected.2);
574 assert_eq!(ti.seconds(), expected.3);
575 }
576}