use core::fmt::{self, Debug, Display, Formatter};
use core::ops::{Add, Sub};
use core::str::FromStr;
use core::time::Duration;
use crate::calendar::Iso;
use crate::{AsMoment, AsTime, Calendar, Date, DateTime, PlainTime, TimeInterval, TimeZone};
const SECONDS_IN_DAY: i64 = 86_400;
#[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct Moment {
pub(crate) secs: i64,
pub(crate) nsec: u32,
}
impl Moment {
pub const MAX: Self = Self {
secs: i64::MAX,
nsec: 999_999_999,
};
pub const MIN: Self = Self {
secs: i64::MIN,
nsec: 0,
};
}
impl Moment {
#[cfg(feature = "std")]
#[must_use]
pub fn now() -> Self {
std::time::SystemTime::now().into()
}
}
impl Moment {
#[must_use]
pub(crate) const fn from_date<C: Calendar>(date: Date<C>) -> Self {
Self {
secs: (date.as_days_since_unix_epoch() as i64) * SECONDS_IN_DAY,
nsec: 0,
}
}
#[must_use]
pub const fn from_unix(seconds: i64, nanoseconds: u32) -> Self {
let mut nanoseconds = nanoseconds as i128;
nanoseconds += seconds as i128 * 1_000_000_000;
Self::from_unix_nanoseconds(nanoseconds)
}
#[must_use]
pub const fn from_unix_seconds(seconds: i64) -> Self {
Self {
secs: seconds,
nsec: 0,
}
}
#[must_use]
pub const fn from_unix_milliseconds(milliseconds: i64) -> Self {
Self::from_unix_nanoseconds(milliseconds as i128 * 1_000_000)
}
#[must_use]
pub const fn from_unix_microseconds(microseconds: i128) -> Self {
Self::from_unix_nanoseconds(microseconds * 1_000)
}
#[must_use]
pub const fn from_unix_nanoseconds(nanoseconds: i128) -> Self {
let secs = nanoseconds.div_euclid(1_000_000_000) as i64;
let nsec = nanoseconds.rem_euclid(1_000_000_000) as u32;
Self { secs, nsec }
}
#[cfg(feature = "astronomy")]
pub fn from_julian_day(jd: f64) -> crate::Result<Self> {
crate::astronomy::from_julian_day(jd)
}
#[cfg(feature = "astronomy")]
pub fn from_julian_ephemeris_day(jde: f64) -> crate::Result<Self> {
let mut moment = Self::from_julian_day(jde)?;
let dt = crate::astronomy::delta_t(moment.to_julian_year());
moment.secs -= dt as i64;
Ok(moment)
}
}
impl Moment {
#[must_use]
pub(crate) fn at(self, tz: &TimeZone) -> DateTime<Iso> {
DateTime::from_moment(Iso, self).at(tz)
}
#[must_use]
pub(crate) const fn on<C: Calendar>(self, calendar: C) -> DateTime<C> {
DateTime::from_moment(calendar, self)
}
#[must_use]
pub(crate) const fn as_date<C: Calendar>(self, calendar: C) -> Date<C> {
Date::from_unix_timestamp(calendar, self.secs)
}
}
impl Moment {
#[must_use]
pub const fn to_unix(self) -> (i64, u32) {
(self.secs, self.nsec)
}
#[must_use]
pub const fn to_unix_seconds(self) -> i64 {
self.secs
}
#[must_use]
pub const fn to_unix_milliseconds(self) -> i64 {
self.to_unix_nanoseconds().div_euclid(1_000_000) as i64
}
#[must_use]
pub const fn to_unix_microseconds(self) -> i64 {
self.to_unix_nanoseconds().div_euclid(1_000) as i64
}
#[must_use]
pub const fn to_unix_nanoseconds(self) -> i128 {
((self.secs as i128) * 1_000_000_000) + (self.nsec as i128)
}
#[cfg(feature = "astronomy")]
#[must_use]
pub fn to_julian_day(self) -> f64 {
crate::astronomy::to_julian_day(self)
}
#[cfg(feature = "astronomy")]
#[must_use]
pub fn to_julian_year(self) -> f64 {
crate::astronomy::to_julian_year(self)
}
}
impl Add<TimeInterval> for Moment {
type Output = Self;
fn add(self, rhs: TimeInterval) -> Self::Output {
let nanoseconds = self
.to_unix_nanoseconds()
.checked_add(rhs.as_total_nanoseconds())
.expect("overflow adding `TimeInterval` to `Moment`");
Self::from_unix_nanoseconds(nanoseconds)
}
}
impl Add<Moment> for TimeInterval {
type Output = Moment;
#[inline]
fn add(self, rhs: Moment) -> Self::Output {
rhs + self
}
}
impl Sub<TimeInterval> for Moment {
type Output = Self;
fn sub(self, rhs: TimeInterval) -> Self::Output {
let nanoseconds = self
.to_unix_nanoseconds()
.checked_sub(rhs.as_total_nanoseconds())
.expect("overflow subtracting `TimeInterval` from `Moment`");
Self::from_unix_nanoseconds(nanoseconds)
}
}
impl Add<Duration> for Moment {
type Output = Self;
fn add(self, rhs: Duration) -> Self::Output {
let nanoseconds = i128::try_from(rhs.as_nanos())
.ok()
.and_then(|rhs| self.to_unix_nanoseconds().checked_add(rhs))
.expect("overflow adding `Duration` to `Moment`");
Self::from_unix_nanoseconds(nanoseconds)
}
}
impl Add<Moment> for Duration {
type Output = Moment;
#[inline]
fn add(self, rhs: Moment) -> Self::Output {
rhs + self
}
}
impl Sub<Duration> for Moment {
type Output = Self;
fn sub(self, rhs: Duration) -> Self::Output {
let nanoseconds = i128::try_from(rhs.as_nanos())
.ok()
.and_then(|rhs| self.to_unix_nanoseconds().checked_sub(rhs))
.expect("overflow subtracting `Duration` from `Moment`");
Self::from_unix_nanoseconds(nanoseconds)
}
}
#[cfg(feature = "std")]
impl From<std::time::SystemTime> for Moment {
fn from(time: std::time::SystemTime) -> Self {
use std::time::SystemTime;
let (secs, nsec) = time.duration_since(SystemTime::UNIX_EPOCH).map_or_else(
|_| {
let ts = SystemTime::UNIX_EPOCH
.duration_since(time)
.unwrap_or_default();
(-(ts.as_secs() as i64), ts.subsec_nanos())
},
|ts| (ts.as_secs() as i64, ts.subsec_nanos()),
);
Self { secs, nsec }
}
}
#[cfg(feature = "std")]
impl From<Moment> for std::time::SystemTime {
fn from(moment: Moment) -> Self {
let (secs, nsec) = moment.to_unix();
let duration = Duration::new(secs.unsigned_abs(), nsec);
if secs.is_negative() {
Self::UNIX_EPOCH - duration
} else {
Self::UNIX_EPOCH + duration
}
}
}
impl<C: Calendar> From<Date<C>> for Moment {
fn from(date: Date<C>) -> Self {
Self::from_date(date)
}
}
impl Display for Moment {
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
f.pad(&self.format_rfc3339())
}
}
impl Debug for Moment {
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
<Self as Display>::fmt(self, f)
}
}
impl FromStr for Moment {
type Err = crate::Error;
fn from_str(s: &str) -> Result<Self, Self::Err> {
Self::parse_rfc3339(s)
}
}
impl AsMoment for Moment {
fn as_moment(&self) -> Self {
*self
}
}
impl AsTime for Moment {
fn as_time(&self) -> PlainTime {
let (secs, nsec) = self.as_timestamp();
PlainTime::from_unix_timestamp(secs, nsec)
}
fn as_timestamp(&self) -> (i64, u32) {
self.to_unix()
}
}
impl Sub<Self> for Moment {
type Output = TimeInterval;
fn sub(self, rhs: Self) -> Self::Output {
TimeInterval::between(rhs, self)
}
}
#[cfg(test)]
mod tests {
use core::time::Duration;
use test_case::test_case;
use crate::{AsMoment, DateTime, Moment};
#[cfg(feature = "astronomy")]
#[test_case(2443259.9, "1977-04-26T09:35:59.999991953Z")]
#[test_case(2451545.0, "2000-01-01T12:00:00Z")]
#[test_case(2415020.5, "1900-01-01T00:00:00Z")]
#[test_case(2299161.5, "1582-10-16T00:00:00Z")]
#[test_case(2299160.5, "1582-10-15T00:00:00Z")]
#[test_case(2299159.5, "1582-10-14T00:00:00Z")]
#[test_case(2026871.8, "0837-04-14T07:12:00.000004023Z")]
#[test_case(1355671.4, "-1001-08-07T21:35:59.999991953Z")]
#[test_case(0.0, "-4713-11-24T12:00:00Z")]
fn moment_from_julian_day(jd: f64, expected: &str) -> crate::Result<()> {
let moment = Moment::from_julian_day(jd)?;
assert_eq!(moment.format_rfc3339(), expected);
assert_eq!(moment.to_julian_day(), jd);
let dt = DateTime::parse_rfc3339(expected)?;
assert_eq!(dt.as_moment(), moment);
Ok(())
}
#[cfg(feature = "std")]
#[test_case(-1727313985 ; "negative 1727313985")]
#[test_case(0)]
#[test_case(1727313985)]
fn moment_from_system_time(timestamp: i64) {
use std::time::SystemTime;
let moment = Moment::from_unix_seconds(timestamp);
let st = SystemTime::from(moment);
let moment_2 = Moment::from(st);
assert_eq!(moment_2, moment);
}
#[test_case(1727313985, 86_400, "2024-09-27T01:26:25Z")]
#[test_case(1727313985, 18_000, "2024-09-26T06:26:25Z")]
#[test_case(1727313985, 600, "2024-09-26T01:36:25Z")]
fn moment_add_duration(timestamp: i64, seconds: u64, expected: &str) {
let mut moment = Moment::from_unix_seconds(timestamp);
moment = moment + Duration::from_secs(seconds);
assert_eq!(moment.format_rfc3339(), expected);
}
#[test_case(1727313985, 86_400, "2024-09-25T01:26:25Z")]
#[test_case(1727313985, 18_000, "2024-09-25T20:26:25Z")]
#[test_case(1727313985, 600, "2024-09-26T01:16:25Z")]
fn moment_sub_duration(timestamp: i64, seconds: u64, expected: &str) {
let mut moment = Moment::from_unix_seconds(timestamp);
moment = moment - Duration::from_secs(seconds);
assert_eq!(moment.format_rfc3339(), expected);
}
#[test_case(1727313985, 1727480105, (1, 22, 8, 40))]
fn moment_sub_moment(timestamp_start: i64, timestamp_end: i64, expected: (i64, i8, i8, i8)) {
let moment_start = Moment::from_unix_seconds(timestamp_start);
let moment_end = Moment::from_unix_seconds(timestamp_end);
let ti = moment_end - moment_start;
assert_eq!(ti.days(), expected.0);
assert_eq!(ti.hours(), expected.1);
assert_eq!(ti.minutes(), expected.2);
assert_eq!(ti.seconds(), expected.3);
}
}