use std::convert::TryInto;
use std::fmt;
use std::ops;
mod strftime;
use super::Date;
#[derive(
Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, serde::Serialize, serde::Deserialize,
)]
#[serde(transparent)]
#[repr(transparent)]
pub struct DateTime {
#[serde(with = "friendly_date_time")]
inner: DateTimeImpl,
}
type DateTimeImpl = time::OffsetDateTime;
impl DateTime {
pub fn now() -> Self {
Self {
inner: DateTimeImpl::now_utc(),
}
}
pub fn from_ymd(year: i32, month: u8, day: u8) -> Self {
Self {
inner: time::Date::from_calendar_date(
year,
month.try_into().expect("the month is out of range"),
day,
)
.expect("one or more components were invalid")
.with_hms(0, 0, 0)
.expect("one or more components were invalid")
.assume_offset(time::macros::offset!(UTC)),
}
}
#[allow(clippy::should_implement_trait)]
pub fn from_str(other: &str) -> Option<Self> {
parse_date_time(other).map(|d| Self { inner: d })
}
pub fn with_date(self, other: Date) -> Self {
Self {
inner: self.inner.replace_date(other.inner),
}
}
pub fn with_offset(self, offset: time::UtcOffset) -> Self {
Self {
inner: self.inner.to_offset(offset),
}
}
pub fn date(self) -> Date {
Date {
inner: self.inner.date(),
}
}
#[inline]
pub fn format(&self, fmt: &str) -> Result<String, strftime::DateFormatError> {
strftime::strftime(self.inner, fmt)
}
pub fn to_rfc2822(&self) -> String {
self.inner
.format(&time::format_description::well_known::Rfc2822)
.expect("always valid")
}
}
impl DateTime {
#[inline]
pub fn year(&self) -> i32 {
self.inner.year()
}
#[inline]
pub fn month(&self) -> u8 {
self.inner.month() as u8
}
#[inline]
pub fn day(&self) -> u8 {
self.inner.day()
}
#[inline]
pub fn ordinal(&self) -> u16 {
self.inner.ordinal()
}
#[inline]
pub fn iso_week(&self) -> u8 {
self.inner.iso_week()
}
}
impl Default for DateTime {
fn default() -> Self {
Self {
inner: DateTimeImpl::UNIX_EPOCH,
}
}
}
const DATE_TIME_FORMAT: &[time::format_description::FormatItem<'static>] = time::macros::format_description!(
"[year]-[month]-[day] [hour]:[minute]:[second] [offset_hour sign:mandatory][offset_minute]"
);
impl fmt::Display for DateTime {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(
f,
"{}",
self.inner
.format(DATE_TIME_FORMAT)
.map_err(|_e| fmt::Error)?
)
}
}
impl ops::Deref for DateTime {
type Target = DateTimeImpl;
fn deref(&self) -> &Self::Target {
&self.inner
}
}
impl ops::DerefMut for DateTime {
fn deref_mut(&mut self) -> &mut Self::Target {
&mut self.inner
}
}
mod friendly_date_time {
use super::*;
use serde::{self, Deserialize, Deserializer, Serializer};
pub(crate) fn serialize<S>(date: &DateTimeImpl, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
let s = date
.format(DATE_TIME_FORMAT)
.map_err(serde::ser::Error::custom)?;
serializer.serialize_str(&s)
}
pub(crate) fn deserialize<'de, D>(deserializer: D) -> Result<DateTimeImpl, D::Error>
where
D: Deserializer<'de>,
{
let s: std::borrow::Cow<'_, str> = Deserialize::deserialize(deserializer)?;
DateTimeImpl::parse(&s, DATE_TIME_FORMAT).map_err(serde::de::Error::custom)
}
}
fn parse_date_time(s: &str) -> Option<DateTimeImpl> {
use regex::Regex;
use time::macros::format_description;
const USER_FORMATS: &[&[time::format_description::FormatItem<'_>]] = &[
DATE_TIME_FORMAT,
format_description!("[day] [month repr:long] [year] [hour]:[minute]:[second] [offset_hour sign:mandatory][offset_minute]"),
format_description!("[day] [month repr:short] [year] [hour]:[minute]:[second] [offset_hour sign:mandatory][offset_minute]"),
format_description!("[month]/[day]/[year] [hour]:[minute]:[second] [offset_hour sign:mandatory][offset_minute]"),
format_description!("[weekday repr:short] [month repr:short] [day padding:none] [hour]:[minute]:[second] [year] [offset_hour sign:mandatory][offset_minute]"),
];
if let "" = s {
None
} else if let "now" | "today" = s.to_lowercase().trim() {
Some(DateTimeImpl::now_utc())
} else {
let offset_re = Regex::new(r"[+-][01][0-9]{3}$").unwrap();
let offset = if offset_re.is_match(s) { "" } else { " +0000" };
let s = s.to_owned() + offset;
USER_FORMATS
.iter()
.find_map(|f| DateTimeImpl::parse(s.as_str(), f).ok())
}
}
#[cfg(test)]
mod test {
use super::*;
#[test]
fn parse_date_time_empty_is_bad() {
let input = "";
let actual = parse_date_time(input);
assert!(actual.is_none());
}
#[test]
fn parse_date_time_bad() {
let input = "aaaaa";
let actual = parse_date_time(input);
assert!(actual.is_none());
}
#[test]
fn parse_date_time_now() {
let input = "now";
let actual = parse_date_time(input);
assert!(actual.is_some());
}
#[test]
fn parse_date_time_today() {
let input = "today";
let actual = parse_date_time(input);
assert!(actual.is_some());
let input = "Today";
let actual = parse_date_time(input);
assert!(actual.is_some());
}
#[test]
fn parse_date_time_serialized_format() {
let input = "2016-02-16 10:00:00 +0100"; let actual = parse_date_time(input);
assert!(actual.unwrap().unix_timestamp() == 1455613200);
let input = "2016-02-16 10:00:00 +0000"; let actual = parse_date_time(input);
assert!(actual.unwrap().unix_timestamp() == 1455616800);
let input = "2016-02-16 10:00:00"; let actual = parse_date_time(input);
assert!(actual.unwrap().unix_timestamp() == 1455616800);
}
#[test]
fn parse_date_time_day_month_format() {
let input = "16 February 2016 10:00:00 +0100"; let actual = parse_date_time(input);
assert!(actual.unwrap().unix_timestamp() == 1455613200);
let input = "16 February 2016 10:00:00 +0000"; let actual = parse_date_time(input);
assert!(actual.unwrap().unix_timestamp() == 1455616800);
let input = "16 February 2016 10:00:00"; let actual = parse_date_time(input);
assert!(actual.unwrap().unix_timestamp() == 1455616800);
}
#[test]
fn parse_date_time_day_mon_format() {
let input = "16 Feb 2016 10:00:00 +0100"; let actual = parse_date_time(input);
assert!(actual.unwrap().unix_timestamp() == 1455613200);
let input = "16 Feb 2016 10:00:00 +0000"; let actual = parse_date_time(input);
assert!(actual.unwrap().unix_timestamp() == 1455616800);
let input = "16 Feb 2016 10:00:00"; let actual = parse_date_time(input);
assert!(actual.unwrap().unix_timestamp() == 1455616800);
}
#[test]
fn parse_date_time_mdy_format() {
let input = "02/16/2016 10:00:00 +0100"; let actual = parse_date_time(input);
assert!(actual.unwrap().unix_timestamp() == 1455613200);
let input = "02/16/2016 10:00:00 +0000"; let actual = parse_date_time(input);
assert!(actual.unwrap().unix_timestamp() == 1455616800);
let input = "02/16/2016 10:00:00"; let actual = parse_date_time(input);
assert!(actual.unwrap().unix_timestamp() == 1455616800);
}
#[test]
fn parse_date_time_dow_mon_format() {
let input = "Tue Feb 16 10:00:00 2016 +0100"; let actual = parse_date_time(input);
assert!(actual.unwrap().unix_timestamp() == 1455613200);
let input = "Tue Feb 16 10:00:00 2016 +0000"; let actual = parse_date_time(input);
assert!(actual.unwrap().unix_timestamp() == 1455616800);
let input = "Tue Feb 16 10:00:00 2016"; let actual = parse_date_time(input);
assert!(actual.unwrap().unix_timestamp() == 1455616800);
}
#[test]
fn parse_date_time_to_string() {
let date = DateTime::now();
let input = date.to_string();
let actual = parse_date_time(&input);
assert!(actual.is_some());
}
}