#[cfg(not(feature = "std"))]
use crate::alloc_prelude::*;
use crate::{
format::parse::{parse, ParseResult, ParsedItems},
offset, Date, DeferredFormat, Duration, PrimitiveDateTime, Time, UtcOffset, Weekday,
};
use core::{
cmp::Ordering,
hash::{Hash, Hasher},
ops::{Add, AddAssign, Sub, SubAssign},
time::Duration as StdDuration,
};
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[cfg_attr(
feature = "serde",
serde(
try_from = "crate::serde::PrimitiveDateTime",
into = "crate::serde::PrimitiveDateTime"
)
)]
#[derive(Debug, Clone, Copy, Eq)]
pub struct OffsetDateTime {
pub(crate) utc_datetime: PrimitiveDateTime,
pub(crate) offset: UtcOffset,
}
impl OffsetDateTime {
#[inline(always)]
#[cfg(feature = "std")]
#[cfg_attr(doc, doc(cfg(feature = "std")))]
pub fn now() -> Self {
PrimitiveDateTime::now().using_offset(offset!(UTC))
}
#[inline(always)]
pub const fn to_offset(self, offset: UtcOffset) -> Self {
Self {
utc_datetime: self.utc_datetime,
offset,
}
}
#[inline(always)]
pub const fn unix_epoch() -> Self {
Self {
utc_datetime: PrimitiveDateTime::unix_epoch(),
offset: offset!(UTC),
}
}
#[inline(always)]
pub fn from_unix_timestamp(timestamp: i64) -> Self {
PrimitiveDateTime::from_unix_timestamp(timestamp).using_offset(offset!(UTC))
}
#[inline(always)]
pub const fn offset(self) -> UtcOffset {
self.offset
}
#[inline(always)]
pub fn timestamp(self) -> i64 {
self.utc_datetime.timestamp()
}
#[inline(always)]
pub fn date(self) -> Date {
(self.utc_datetime + self.offset.as_duration()).date()
}
#[inline(always)]
pub fn time(self) -> Time {
(self.utc_datetime + self.offset.as_duration()).time()
}
#[inline(always)]
pub fn year(self) -> i32 {
self.date().year()
}
#[inline(always)]
pub fn month(self) -> u8 {
self.date().month()
}
#[inline(always)]
pub fn day(self) -> u8 {
self.date().day()
}
#[inline(always)]
pub fn month_day(self) -> (u8, u8) {
self.date().month_day()
}
#[inline(always)]
pub fn ordinal(self) -> u16 {
self.date().ordinal()
}
#[inline(always)]
pub fn iso_year_week(self) -> (i32, u8) {
self.date().iso_year_week()
}
#[inline(always)]
pub fn week(self) -> u8 {
self.date().week()
}
#[inline(always)]
pub fn weekday(self) -> Weekday {
self.date().weekday()
}
#[inline(always)]
pub fn hour(self) -> u8 {
self.time().hour()
}
#[inline(always)]
pub fn minute(self) -> u8 {
self.time().minute()
}
#[inline(always)]
pub fn second(self) -> u8 {
self.time().second()
}
#[inline(always)]
pub fn millisecond(self) -> u16 {
self.time().millisecond()
}
#[inline(always)]
pub fn microsecond(self) -> u32 {
self.time().microsecond()
}
#[inline(always)]
pub fn nanosecond(self) -> u32 {
self.time().nanosecond()
}
}
impl OffsetDateTime {
#[inline(always)]
pub fn format(self, format: &str) -> String {
DeferredFormat {
date: Some(self.date()),
time: Some(self.time()),
offset: Some(self.offset()),
format: crate::format::parse_fmt_string(format),
}
.to_string()
}
#[inline(always)]
pub fn parse(s: &str, format: &str) -> ParseResult<Self> {
Self::try_from_parsed_items(parse(s, format)?)
}
#[inline(always)]
pub(crate) fn try_from_parsed_items(items: ParsedItems) -> ParseResult<Self> {
let offset = UtcOffset::try_from_parsed_items(items)?;
Ok(Self {
utc_datetime: PrimitiveDateTime::try_from_parsed_items(items)? - offset.as_duration(),
offset,
})
}
}
impl PartialEq for OffsetDateTime {
#[inline(always)]
fn eq(&self, rhs: &Self) -> bool {
self.utc_datetime.eq(&rhs.utc_datetime)
}
}
impl PartialOrd for OffsetDateTime {
#[inline(always)]
fn partial_cmp(&self, rhs: &Self) -> Option<Ordering> {
Some(self.cmp(rhs))
}
}
impl Ord for OffsetDateTime {
#[inline(always)]
fn cmp(&self, rhs: &Self) -> Ordering {
self.utc_datetime.cmp(&(rhs.utc_datetime))
}
}
impl Hash for OffsetDateTime {
#[inline(always)]
fn hash<H: Hasher>(&self, hasher: &mut H) {
hasher.write(b"OffsetDateTime");
self.utc_datetime.hash(hasher);
}
}
impl Add<Duration> for OffsetDateTime {
type Output = Self;
#[inline(always)]
fn add(self, duration: Duration) -> Self::Output {
(self.utc_datetime + duration).using_offset(self.offset)
}
}
impl Add<StdDuration> for OffsetDateTime {
type Output = Self;
#[inline(always)]
fn add(self, duration: StdDuration) -> Self::Output {
(self.utc_datetime + duration).using_offset(self.offset)
}
}
impl AddAssign<Duration> for OffsetDateTime {
#[inline(always)]
fn add_assign(&mut self, duration: Duration) {
*self = *self + duration;
}
}
impl AddAssign<StdDuration> for OffsetDateTime {
#[inline(always)]
fn add_assign(&mut self, duration: StdDuration) {
*self = *self + duration;
}
}
impl Sub<Duration> for OffsetDateTime {
type Output = Self;
#[inline(always)]
fn sub(self, duration: Duration) -> Self::Output {
(self.utc_datetime - duration).using_offset(self.offset)
}
}
impl Sub<StdDuration> for OffsetDateTime {
type Output = Self;
#[inline(always)]
fn sub(self, duration: StdDuration) -> Self::Output {
(self.utc_datetime - duration).using_offset(self.offset)
}
}
impl SubAssign<Duration> for OffsetDateTime {
#[inline(always)]
fn sub_assign(&mut self, duration: Duration) {
*self = *self - duration;
}
}
impl SubAssign<StdDuration> for OffsetDateTime {
#[inline(always)]
fn sub_assign(&mut self, duration: StdDuration) {
*self = *self - duration;
}
}
impl Sub<OffsetDateTime> for OffsetDateTime {
type Output = Duration;
#[inline(always)]
fn sub(self, rhs: Self) -> Self::Output {
self.utc_datetime - rhs.utc_datetime
}
}
#[cfg(test)]
#[rustfmt::skip::macros(date)]
mod test {
use super::*;
use crate::{date, prelude::*, time};
#[test]
#[cfg(feature = "std")]
fn now() {
assert!(OffsetDateTime::now().year() >= 2019);
assert_eq!(OffsetDateTime::now().offset(), offset!(UTC));
}
#[test]
fn to_offset() {
assert_eq!(
date!(2000-01-01)
.midnight()
.using_offset(offset!(UTC))
.to_offset(offset!(-1))
.year(),
1999,
);
}
#[test]
fn unix_epoch() {
assert_eq!(
OffsetDateTime::unix_epoch(),
date!(1970-1-1).midnight().using_offset(offset!(UTC)),
);
}
#[test]
fn from_unix_timestamp() {
assert_eq!(
OffsetDateTime::from_unix_timestamp(0),
OffsetDateTime::unix_epoch(),
);
assert_eq!(
OffsetDateTime::from_unix_timestamp(1_546_300_800),
date!(2019-01-01).midnight().using_offset(offset!(UTC)),
);
}
#[test]
fn offset() {
assert_eq!(
date!(2019-01-01)
.midnight()
.using_offset(offset!(UTC))
.offset(),
offset!(UTC),
);
assert_eq!(
date!(2019-01-01)
.midnight()
.using_offset(offset!(+1))
.offset(),
offset!(+1),
);
}
#[test]
fn timestamp() {
assert_eq!(OffsetDateTime::unix_epoch().timestamp(), 0);
assert_eq!(
PrimitiveDateTime::unix_epoch()
.using_offset(offset!(-1))
.timestamp(),
3600,
);
}
#[test]
fn date() {
assert_eq!(
date!(2019-01-01)
.midnight()
.using_offset(offset!(UTC))
.date(),
date!(2019-01-01),
);
assert_eq!(
date!(2019-01-01)
.with_time(time!(23:00:00))
.using_offset(offset!(-1))
.date(),
date!(2019-01-01),
);
}
#[test]
fn time() {
assert_eq!(
date!(2019-01-01)
.midnight()
.using_offset(offset!(UTC))
.time(),
time!(0:00),
);
assert_eq!(
date!(2019-01-01)
.midnight()
.using_offset(offset!(UTC))
.to_offset(offset!(-1))
.time(),
time!(23:00),
);
}
#[test]
fn year() {
assert_eq!(
date!(2019-01-01)
.midnight()
.using_offset(offset!(UTC))
.year(),
2019,
);
assert_eq!(
date!(2019-12-31)
.with_time(time!(23:00))
.using_offset(offset!(UTC))
.to_offset(offset!(+1))
.year(),
2020,
);
assert_eq!(
date!(2020-01-01)
.midnight()
.using_offset(offset!(UTC))
.year(),
2020,
);
}
#[test]
fn month() {
assert_eq!(
date!(2019-01-01)
.midnight()
.using_offset(offset!(UTC))
.month(),
1,
);
assert_eq!(
date!(2019-12-31)
.with_time(time!(23:00))
.using_offset(offset!(+1))
.month(),
12,
);
}
#[test]
fn day() {
assert_eq!(
date!(2019-01-01)
.midnight()
.using_offset(offset!(UTC))
.day(),
1,
);
assert_eq!(
date!(2019-12-31)
.with_time(time!(23:00))
.using_offset(offset!(+1))
.day(),
31,
);
}
#[test]
fn month_day() {
assert_eq!(
date!(2019-01-01)
.midnight()
.using_offset(offset!(UTC))
.month_day(),
(1, 1),
);
assert_eq!(
date!(2019-12-31)
.with_time(time!(23:00))
.using_offset(offset!(+1))
.month_day(),
(12, 31),
);
}
#[test]
fn ordinal() {
assert_eq!(
date!(2019-01-01)
.midnight()
.using_offset(offset!(UTC))
.ordinal(),
1,
);
assert_eq!(
date!(2019-12-31)
.with_time(time!(23:00))
.using_offset(offset!(+1))
.ordinal(),
365,
);
}
#[test]
fn week() {
assert_eq!(
date!(2019-01-01)
.midnight()
.using_offset(offset!(UTC))
.week(),
1,
);
assert_eq!(
date!(2020-01-01)
.midnight()
.using_offset(offset!(UTC))
.week(),
1,
);
assert_eq!(
date!(2020-12-31)
.midnight()
.using_offset(offset!(UTC))
.week(),
53,
);
assert_eq!(
date!(2021-01-01)
.midnight()
.using_offset(offset!(UTC))
.week(),
53,
);
}
#[test]
fn weekday() {
use Weekday::*;
assert_eq!(
date!(2019-01-01)
.midnight()
.using_offset(offset!(UTC))
.weekday(),
Tuesday,
);
assert_eq!(
date!(2019-02-01)
.midnight()
.using_offset(offset!(UTC))
.weekday(),
Friday,
);
assert_eq!(
date!(2019-03-01)
.midnight()
.using_offset(offset!(UTC))
.weekday(),
Friday,
);
}
#[test]
fn hour() {
assert_eq!(
date!(2019-01-01)
.midnight()
.using_offset(offset!(UTC))
.hour(),
0,
);
assert_eq!(
date!(2019-01-01)
.with_time(time!(23:59:59))
.using_offset(UtcOffset::hours(-2))
.hour(),
23,
);
}
#[test]
fn minute() {
assert_eq!(
date!(2019-01-01)
.midnight()
.using_offset(offset!(UTC))
.minute(),
0,
);
assert_eq!(
date!(2019-01-01)
.with_time(time!(23:59:59))
.using_offset(offset!(+0:30))
.minute(),
59,
);
}
#[test]
fn second() {
assert_eq!(
date!(2019-01-01)
.midnight()
.using_offset(offset!(UTC))
.second(),
0,
);
assert_eq!(
date!(2019-01-01)
.with_time(time!(23:59:59))
.using_offset(offset!(+0:00:30))
.second(),
59,
);
}
#[test]
fn millisecond() {
assert_eq!(
date!(2019-01-01)
.midnight()
.using_offset(offset!(UTC))
.millisecond(),
0,
);
assert_eq!(
date!(2019-01-01)
.with_time(time!(23:59:59.999))
.using_offset(offset!(UTC))
.millisecond(),
999,
);
}
#[test]
fn microsecond() {
assert_eq!(
date!(2019-01-01)
.midnight()
.using_offset(offset!(UTC))
.microsecond(),
0,
);
assert_eq!(
date!(2019-01-01)
.with_time(time!(23:59:59.999_999))
.using_offset(offset!(UTC))
.microsecond(),
999_999,
);
}
#[test]
fn nanosecond() {
assert_eq!(
date!(2019-01-01)
.midnight()
.using_offset(offset!(UTC))
.nanosecond(),
0,
);
assert_eq!(
date!(2019-01-01)
.with_time(time!(23:59:59.999_999_999))
.using_offset(offset!(UTC))
.nanosecond(),
999_999_999,
);
}
#[test]
fn format() {
assert_eq!(
date!(2019-01-02)
.midnight()
.using_offset(offset!(UTC))
.format("%F %r %z"),
"2019-01-02 12:00:00 am +0000",
);
}
#[test]
fn parse() {
assert_eq!(
OffsetDateTime::parse("2019-01-02 00:00:00 +0000", "%F %T %z"),
Ok(date!(2019-01-02).midnight().using_offset(offset!(UTC))),
);
assert_eq!(
OffsetDateTime::parse("2019-002 23:59:59 +0000", "%Y-%j %T %z"),
Ok(date!(2019-002)
.with_time(time!(23:59:59))
.using_offset(offset!(UTC)))
);
assert_eq!(
OffsetDateTime::parse("2019-W01-3 12:00:00 pm +0000", "%G-W%V-%u %r %z"),
Ok(date!(2019-W01-3)
.with_time(time!(12:00))
.using_offset(offset!(UTC))),
);
}
#[test]
fn partial_eq() {
assert_eq!(
date!(1999-12-31)
.with_time(time!(23:00))
.using_offset(offset!(-1)),
date!(2000-01-01).midnight().using_offset(offset!(UTC)),
);
}
#[test]
fn partial_ord() {
let t1 = date!(2019-01-01).midnight().using_offset(offset!(UTC));
let t2 = date!(2018-12-31)
.with_time(time!(23:00))
.using_offset(offset!(-1));
assert_eq!(t1.partial_cmp(&t2), Some(Ordering::Equal));
}
#[test]
fn ord() {
let t1 = date!(2019-01-01).midnight().using_offset(offset!(UTC));
let t2 = date!(2018-12-31)
.with_time(time!(23:00))
.using_offset(offset!(-1));
assert_eq!(t1, t2);
let t1 = date!(2019-01-01).midnight().using_offset(offset!(UTC));
let t2 = date!(2019-01-01)
.with_time(time!(0:00:00.000_000_001))
.using_offset(offset!(UTC));
assert!(t2 > t1);
}
#[test]
#[cfg(feature = "std")]
fn hash() {
use std::{collections::hash_map::DefaultHasher, hash::Hash};
assert_eq!(
{
let mut hasher = DefaultHasher::new();
date!(2019-01-01)
.midnight()
.using_offset(offset!(UTC))
.hash(&mut hasher);
hasher.finish()
},
{
let mut hasher = DefaultHasher::new();
date!(2018-12-31)
.with_time(time!(23:00))
.using_offset(offset!(-1))
.hash(&mut hasher);
hasher.finish()
}
);
assert_ne!(
{
let mut hasher = DefaultHasher::new();
date!(2019-01-01).midnight().hash(&mut hasher);
hasher.finish()
},
{
let mut hasher = DefaultHasher::new();
date!(2019-01-01)
.midnight()
.using_offset(offset!(UTC))
.hash(&mut hasher);
hasher.finish()
}
);
}
#[test]
fn add_duration() {
assert_eq!(
date!(2019-01-01).midnight().using_offset(offset!(UTC)) + 5.days(),
date!(2019-01-06).midnight().using_offset(offset!(UTC)),
);
assert_eq!(
date!(2019-12-31).midnight().using_offset(offset!(UTC)) + 1.days(),
date!(2020-01-01).midnight().using_offset(offset!(UTC)),
);
assert_eq!(
date!(2019-12-31)
.with_time(time!(23:59:59))
.using_offset(offset!(UTC))
+ 2.seconds(),
date!(2020-01-01)
.with_time(time!(0:00:01))
.using_offset(offset!(UTC)),
);
assert_eq!(
date!(2020-01-01)
.with_time(time!(0:00:01))
.using_offset(offset!(UTC))
+ (-2).seconds(),
date!(2019-12-31)
.with_time(time!(23:59:59))
.using_offset(offset!(UTC)),
);
assert_eq!(
date!(1999-12-31)
.with_time(time!(23:00))
.using_offset(offset!(UTC))
+ 1.hours(),
date!(2000-01-01).midnight().using_offset(offset!(UTC)),
);
}
#[test]
fn add_std_duration() {
assert_eq!(
date!(2019-01-01).midnight().using_offset(offset!(UTC)) + 5.std_days(),
date!(2019-01-06).midnight().using_offset(offset!(UTC)),
);
assert_eq!(
date!(2019-12-31).midnight().using_offset(offset!(UTC)) + 1.std_days(),
date!(2020-01-01).midnight().using_offset(offset!(UTC)),
);
assert_eq!(
date!(2019-12-31)
.with_time(time!(23:59:59))
.using_offset(offset!(UTC))
+ 2.std_seconds(),
date!(2020-01-01)
.with_time(time!(0:00:01))
.using_offset(offset!(UTC)),
);
}
#[test]
fn add_assign_duration() {
let mut ny19 = date!(2019-01-01).midnight().using_offset(offset!(UTC));
ny19 += 5.days();
assert_eq!(
ny19,
date!(2019-01-06).midnight().using_offset(offset!(UTC))
);
let mut nye20 = date!(2019-12-31).midnight().using_offset(offset!(UTC));
nye20 += 1.days();
assert_eq!(
nye20,
date!(2020-01-01).midnight().using_offset(offset!(UTC))
);
let mut nye20t = date!(2019-12-31)
.with_time(time!(23:59:59))
.using_offset(offset!(UTC));
nye20t += 2.seconds();
assert_eq!(
nye20t,
date!(2020-01-01)
.with_time(time!(0:00:01))
.using_offset(offset!(UTC))
);
let mut ny20t = date!(2020-01-01)
.with_time(time!(0:00:01))
.using_offset(offset!(UTC));
ny20t += (-2).seconds();
assert_eq!(
ny20t,
date!(2019-12-31)
.with_time(time!(23:59:59))
.using_offset(offset!(UTC))
);
}
#[test]
fn add_assign_std_duration() {
let mut ny19 = date!(2019-01-01).midnight().using_offset(offset!(UTC));
ny19 += 5.std_days();
assert_eq!(
ny19,
date!(2019-01-06).midnight().using_offset(offset!(UTC))
);
let mut nye20 = date!(2019-12-31).midnight().using_offset(offset!(UTC));
nye20 += 1.std_days();
assert_eq!(
nye20,
date!(2020-01-01).midnight().using_offset(offset!(UTC))
);
let mut nye20t = date!(2019-12-31)
.with_time(time!(23:59:59))
.using_offset(offset!(UTC));
nye20t += 2.std_seconds();
assert_eq!(
nye20t,
date!(2020-01-01)
.with_time(time!(0:00:01))
.using_offset(offset!(UTC))
);
}
#[test]
fn sub_duration() {
assert_eq!(
date!(2019-01-06).midnight().using_offset(offset!(UTC)) - 5.days(),
date!(2019-01-01).midnight().using_offset(offset!(UTC)),
);
assert_eq!(
date!(2020-01-01).midnight().using_offset(offset!(UTC)) - 1.days(),
date!(2019-12-31).midnight().using_offset(offset!(UTC)),
);
assert_eq!(
date!(2020-01-01)
.with_time(time!(0:00:01))
.using_offset(offset!(UTC))
- 2.seconds(),
date!(2019-12-31)
.with_time(time!(23:59:59))
.using_offset(offset!(UTC)),
);
assert_eq!(
date!(2019-12-31)
.with_time(time!(23:59:59))
.using_offset(offset!(UTC))
- (-2).seconds(),
date!(2020-01-01)
.with_time(time!(0:00:01))
.using_offset(offset!(UTC)),
);
assert_eq!(
date!(1999-12-31)
.with_time(time!(23:00))
.using_offset(offset!(UTC))
- (-1).hours(),
date!(2000-01-01).midnight().using_offset(offset!(UTC)),
);
}
#[test]
fn sub_std_duration() {
assert_eq!(
date!(2019-01-06).midnight().using_offset(offset!(UTC)) - 5.std_days(),
date!(2019-01-01).midnight().using_offset(offset!(UTC)),
);
assert_eq!(
date!(2020-01-01).midnight().using_offset(offset!(UTC)) - 1.std_days(),
date!(2019-12-31).midnight().using_offset(offset!(UTC)),
);
assert_eq!(
date!(2020-01-01)
.with_time(time!(0:00:01))
.using_offset(offset!(UTC))
- 2.std_seconds(),
date!(2019-12-31)
.with_time(time!(23:59:59))
.using_offset(offset!(UTC)),
);
}
#[test]
fn sub_assign_duration() {
let mut ny19 = date!(2019-01-06).midnight().using_offset(offset!(UTC));
ny19 -= 5.days();
assert_eq!(
ny19,
date!(2019-01-01).midnight().using_offset(offset!(UTC))
);
let mut ny20 = date!(2020-01-01).midnight().using_offset(offset!(UTC));
ny20 -= 1.days();
assert_eq!(
ny20,
date!(2019-12-31).midnight().using_offset(offset!(UTC))
);
let mut ny20t = date!(2020-01-01)
.with_time(time!(0:00:01))
.using_offset(offset!(UTC));
ny20t -= 2.seconds();
assert_eq!(
ny20t,
date!(2019-12-31)
.with_time(time!(23:59:59))
.using_offset(offset!(UTC))
);
let mut nye20t = date!(2019-12-31)
.with_time(time!(23:59:59))
.using_offset(offset!(UTC));
nye20t -= (-2).seconds();
assert_eq!(
nye20t,
date!(2020-01-01)
.with_time(time!(0:00:01))
.using_offset(offset!(UTC))
);
}
#[test]
fn sub_assign_std_duration() {
let mut ny19 = date!(2019-01-06).midnight().using_offset(offset!(UTC));
ny19 -= 5.std_days();
assert_eq!(
ny19,
date!(2019-01-01).midnight().using_offset(offset!(UTC))
);
let mut ny20 = date!(2020-01-01).midnight().using_offset(offset!(UTC));
ny20 -= 1.std_days();
assert_eq!(
ny20,
date!(2019-12-31).midnight().using_offset(offset!(UTC))
);
let mut ny20t = date!(2020-01-01)
.with_time(time!(0:00:01))
.using_offset(offset!(UTC));
ny20t -= 2.std_seconds();
assert_eq!(
ny20t,
date!(2019-12-31)
.with_time(time!(23:59:59))
.using_offset(offset!(UTC))
);
}
#[test]
fn sub_self() {
assert_eq!(
date!(2019-01-02).midnight().using_offset(offset!(UTC))
- date!(2019-01-01).midnight().using_offset(offset!(UTC)),
1.days(),
);
assert_eq!(
date!(2019-01-01).midnight().using_offset(offset!(UTC))
- date!(2019-01-02).midnight().using_offset(offset!(UTC)),
(-1).days(),
);
assert_eq!(
date!(2020-01-01).midnight().using_offset(offset!(UTC))
- date!(2019-12-31).midnight().using_offset(offset!(UTC)),
1.days(),
);
assert_eq!(
date!(2019-12-31).midnight().using_offset(offset!(UTC))
- date!(2020-01-01).midnight().using_offset(offset!(UTC)),
(-1).days(),
);
}
}