#![allow(clippy::trivially_copy_pass_by_ref)]
use chrono::{Datelike, NaiveDate, NaiveDateTime, NaiveTime, Timelike};
use serde::{Deserialize, Deserializer, Serialize, Serializer};
use std::{
convert::{TryFrom, TryInto}, fmt::{self, Display}
};
const JULIAN_DAY_OF_EPOCH: i64 = 2_440_588;
const SECONDS_PER_DAY: i64 = 86_400;
const MILLIS_PER_SECOND: i64 = 1_000;
const MICROS_PER_MILLI: i64 = 1_000;
const NANOS_PER_MICRO: i64 = 1_000;
const GREGORIAN_DAY_OF_EPOCH: i64 = 719_163;
#[derive(Copy, Clone, Hash, PartialEq, Eq, PartialOrd, Ord, Debug)]
pub struct Date(i64);
impl Date {
pub fn from_days(days: i64) -> Option<Self> {
if JULIAN_DAY_OF_EPOCH + i64::from(i32::min_value()) <= days
&& days <= i64::from(i32::max_value())
{
Some(Self(days))
} else {
None
}
}
pub fn as_days(&self) -> i64 {
self.0
}
}
impl From<NaiveDate> for Date {
fn from(date: NaiveDate) -> Self {
Self::from_days(i64::from(date.num_days_from_ce()) - GREGORIAN_DAY_OF_EPOCH).unwrap()
}
}
impl TryFrom<Date> for NaiveDate {
type Error = ();
fn try_from(date: Date) -> Result<Self, ()> {
Self::from_num_days_from_ce_opt(
(date.0 + GREGORIAN_DAY_OF_EPOCH)
.try_into()
.map_err(|_| ())?,
)
.ok_or(())
}
}
impl Serialize for Date {
fn serialize<S>(&self, _serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
unimplemented!()
}
}
impl<'de> Deserialize<'de> for Date {
fn deserialize<D>(_deserializer: D) -> Result<Self, D::Error>
where
D: Deserializer<'de>,
{
unimplemented!()
}
}
impl Display for Date {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
NaiveDate::try_from(*self)
.expect("not implemented yet")
.fmt(f)
}
}
#[derive(Copy, Clone, Hash, PartialEq, Eq, PartialOrd, Ord, Debug)]
pub struct Time(u64);
impl Time {
pub fn from_millis(millis: u32) -> Option<Self> {
if millis < u32::try_from(SECONDS_PER_DAY * MILLIS_PER_SECOND).unwrap() {
Some(Self(
u64::from(millis)
* u64::try_from(MICROS_PER_MILLI).unwrap()
* u64::try_from(NANOS_PER_MICRO).unwrap(),
))
} else {
None
}
}
pub fn from_micros(micros: u64) -> Option<Self> {
if micros < u64::try_from(SECONDS_PER_DAY * MILLIS_PER_SECOND * MICROS_PER_MILLI).unwrap() {
Some(Self(micros * u64::try_from(NANOS_PER_MICRO).unwrap()))
} else {
None
}
}
pub fn from_nanos(nanos: u64) -> Option<Self> {
if nanos
< u64::try_from(
SECONDS_PER_DAY * MILLIS_PER_SECOND * MICROS_PER_MILLI * NANOS_PER_MICRO,
)
.unwrap()
{
Some(Self(nanos))
} else {
None
}
}
pub fn as_millis(&self) -> u32 {
(self.0
/ u64::try_from(NANOS_PER_MICRO).unwrap()
/ u64::try_from(MICROS_PER_MILLI).unwrap())
.try_into()
.unwrap()
}
pub fn as_micros(&self) -> u64 {
self.0 / u64::try_from(NANOS_PER_MICRO).unwrap()
}
pub fn as_nanos(&self) -> u64 {
self.0
}
}
impl From<NaiveTime> for Time {
fn from(time: NaiveTime) -> Self {
Self::from_nanos(
u64::try_from(time.num_seconds_from_midnight()).unwrap()
* u64::try_from(MILLIS_PER_SECOND * MICROS_PER_MILLI * NANOS_PER_MICRO).unwrap()
+ u64::from(time.nanosecond()),
)
.unwrap()
}
}
impl TryFrom<Time> for NaiveTime {
type Error = ();
fn try_from(time: Time) -> Result<Self, Self::Error> {
Ok(Self::from_num_seconds_from_midnight(
(time.as_nanos()
/ u64::try_from(NANOS_PER_MICRO * MICROS_PER_MILLI * MILLIS_PER_SECOND).unwrap())
.try_into()
.unwrap(),
(time.as_nanos()
% u64::try_from(NANOS_PER_MICRO * MICROS_PER_MILLI * MILLIS_PER_SECOND).unwrap())
.try_into()
.unwrap(),
))
}
}
impl Serialize for Time {
fn serialize<S>(&self, _serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
unimplemented!()
}
}
impl<'de> Deserialize<'de> for Time {
fn deserialize<D>(_deserializer: D) -> Result<Self, D::Error>
where
D: Deserializer<'de>,
{
unimplemented!()
}
}
impl Display for Time {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
chrono::NaiveTime::try_from(*self)
.expect("not implemented yet")
.fmt(f)
}
}
#[derive(Copy, Clone, Hash, PartialEq, Eq, PartialOrd, Ord, Debug)]
pub struct Timestamp {
date: Date,
time: Time,
}
impl Timestamp {
pub fn from_date_time(date: Date, time: Time) -> Option<Self> {
Some(Self { date, time })
}
pub fn from_millis(millis: i64) -> Self {
let mut days = millis / (SECONDS_PER_DAY * MILLIS_PER_SECOND);
let mut millis = millis % (SECONDS_PER_DAY * MILLIS_PER_SECOND);
if millis < 0 {
days -= 1;
millis += SECONDS_PER_DAY * MILLIS_PER_SECOND;
}
Self::from_date_time(
Date::from_days(days).unwrap(),
Time::from_millis(millis.try_into().unwrap()).unwrap(),
)
.unwrap()
}
pub fn from_micros(micros: i64) -> Self {
let mut days = micros / (SECONDS_PER_DAY * MILLIS_PER_SECOND * MICROS_PER_MILLI);
let mut micros = micros % (SECONDS_PER_DAY * MILLIS_PER_SECOND * MICROS_PER_MILLI);
if micros < 0 {
days -= 1;
micros += SECONDS_PER_DAY * MILLIS_PER_SECOND * MICROS_PER_MILLI;
}
Self::from_date_time(
Date::from_days(days).unwrap(),
Time::from_micros(micros.try_into().unwrap()).unwrap(),
)
.unwrap()
}
pub fn from_nanos(nanos: i64) -> Self {
let mut days =
nanos / (SECONDS_PER_DAY * MILLIS_PER_SECOND * MICROS_PER_MILLI * NANOS_PER_MICRO);
let mut nanos =
nanos % (SECONDS_PER_DAY * MILLIS_PER_SECOND * MICROS_PER_MILLI * NANOS_PER_MICRO);
if nanos < 0 {
days -= 1;
nanos += SECONDS_PER_DAY * MILLIS_PER_SECOND * MICROS_PER_MILLI * NANOS_PER_MICRO;
}
Self::from_date_time(
Date::from_days(days).unwrap(),
Time::from_nanos(nanos.try_into().unwrap()).unwrap(),
)
.unwrap()
}
pub fn as_date_time(&self) -> (Date, Time) {
(self.date, self.time)
}
pub fn as_millis(&self) -> Option<i64> {
Some(
self.date
.as_days()
.checked_mul(SECONDS_PER_DAY * MILLIS_PER_SECOND)?
.checked_add(i64::try_from(self.time.as_millis()).unwrap())?,
)
}
pub fn as_micros(&self) -> Option<i64> {
Some(
self.date
.as_days()
.checked_mul(SECONDS_PER_DAY * MILLIS_PER_SECOND * MICROS_PER_MILLI)?
.checked_add(i64::try_from(self.time.as_micros()).unwrap())?,
)
}
pub fn as_nanos(&self) -> Option<i64> {
Some(
self.date
.as_days()
.checked_mul(
SECONDS_PER_DAY * MILLIS_PER_SECOND * MICROS_PER_MILLI * NANOS_PER_MICRO,
)?
.checked_add(i64::try_from(self.time.as_nanos()).unwrap())?,
)
}
}
impl From<NaiveDateTime> for Timestamp {
fn from(timestamp: NaiveDateTime) -> Self {
Self::from_nanos(timestamp.timestamp_nanos())
}
}
impl TryFrom<Timestamp> for NaiveDateTime {
type Error = ();
fn try_from(timestamp: Timestamp) -> Result<Self, Self::Error> {
Ok(NaiveDate::try_from(timestamp.date)?.and_time(timestamp.time.try_into()?))
}
}
impl Serialize for Timestamp {
fn serialize<S>(&self, _serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
unimplemented!()
}
}
impl<'de> Deserialize<'de> for Timestamp {
fn deserialize<D>(_deserializer: D) -> Result<Self, D::Error>
where
D: Deserializer<'de>,
{
unimplemented!()
}
}
impl Display for Timestamp {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
NaiveDateTime::try_from(*self)
.expect("not implemented yet")
.fmt(f)
}
}
#[cfg(test)]
mod tests {
use super::*;
use chrono::NaiveDate;
#[test]
fn test_convert_date_to_string() {
fn check_date_conversion(y: i32, m: u32, d: u32) {
let chrono_date = NaiveDate::from_ymd(y, m, d);
let chrono_datetime = chrono_date.and_hms(0, 0, 0);
assert_eq!(chrono_datetime.timestamp() % SECONDS_PER_DAY, 0);
let date = Date::from_days(chrono_datetime.timestamp() / SECONDS_PER_DAY).unwrap();
assert_eq!(date.to_string(), chrono_date.to_string());
let date2 = Date::from(NaiveDate::try_from(date).unwrap());
assert_eq!(date, date2);
}
check_date_conversion(-262_144, 1, 1);
check_date_conversion(1969, 12, 31);
check_date_conversion(1970, 1, 1);
check_date_conversion(2010, 1, 2);
check_date_conversion(2014, 5, 1);
check_date_conversion(2016, 2, 29);
check_date_conversion(2017, 9, 12);
check_date_conversion(2018, 3, 31);
check_date_conversion(262_143, 12, 31);
}
#[test]
fn test_convert_time_to_string() {
fn check_time_conversion(h: u32, mi: u32, s: u32) {
let chrono_time = NaiveTime::from_hms(h, mi, s);
let time = Time::from(chrono_time);
assert_eq!(time.to_string(), chrono_time.to_string());
}
check_time_conversion(13, 12, 54);
check_time_conversion(8, 23, 1);
check_time_conversion(11, 6, 32);
check_time_conversion(16, 38, 0);
check_time_conversion(21, 15, 12);
}
#[test]
fn test_convert_timestamp_to_string() {
#[allow(clippy::many_single_char_names)]
fn check_datetime_conversion(y: i32, m: u32, d: u32, h: u32, mi: u32, s: u32) {
let dt = NaiveDate::from_ymd(y, m, d).and_hms(h, mi, s);
let res = Timestamp::from_millis(dt.timestamp_millis()).to_string();
let exp = dt.to_string();
assert_eq!(res, exp);
}
check_datetime_conversion(2010, 1, 2, 13, 12, 54);
check_datetime_conversion(2011, 1, 3, 8, 23, 1);
check_datetime_conversion(2012, 4, 5, 11, 6, 32);
check_datetime_conversion(2013, 5, 12, 16, 38, 0);
check_datetime_conversion(2014, 11, 28, 21, 15, 12);
}
}