use chrono::{DateTime, NaiveDate, NaiveDateTime, TimeZone, Utc};
use chrono_tz::Asia::Kolkata;
use serde::{Deserialize, Deserializer, Serialize, Serializer};
use std::fmt;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct Time {
inner: Option<DateTime<Utc>>,
}
impl Time {
const LAYOUTS: &'static [&'static str] = &["%Y-%m-%d", "%Y-%m-%d %H:%M:%S"];
const ZONED_LAYOUTS: &'static [&'static str] = &[
"%Y-%m-%dT%H:%M:%S%z",
"%Y-%m-%dT%H:%M:%S%.f%:z", ];
pub fn new(dt: DateTime<Utc>) -> Self {
Time { inner: Some(dt) }
}
pub fn null() -> Self {
Time { inner: None }
}
pub fn from_timestamp(timestamp: i64) -> Self {
if timestamp == 0 {
Self::null()
} else {
match DateTime::from_timestamp(timestamp, 0) {
Some(dt) => Time { inner: Some(dt) },
None => Time { inner: None },
}
}
}
pub fn is_null(&self) -> bool {
self.inner.is_none()
}
pub fn as_datetime(&self) -> Option<DateTime<Utc>> {
self.inner
}
fn parse_time(s: &str) -> Result<Option<DateTime<Utc>>, String> {
let s = s.trim();
if s.is_empty() || s == "null" {
return Ok(None);
}
for layout in Self::LAYOUTS {
if let Ok(naive_dt) = NaiveDateTime::parse_from_str(s, layout) {
if let Some(ist_dt) = Kolkata.from_local_datetime(&naive_dt).single() {
return Ok(Some(ist_dt.with_timezone(&Utc)));
}
}
if let Ok(naive_date) = NaiveDate::parse_from_str(s, layout) {
let naive_dt = naive_date.and_hms_opt(0, 0, 0).unwrap();
if let Some(ist_dt) = Kolkata.from_local_datetime(&naive_dt).single() {
return Ok(Some(ist_dt.with_timezone(&Utc)));
}
}
}
for layout in Self::ZONED_LAYOUTS {
if let Ok(dt) = DateTime::parse_from_str(s, layout) {
return Ok(Some(dt.with_timezone(&Utc)));
}
}
if let Ok(dt) = DateTime::parse_from_rfc3339(s) {
return Ok(Some(dt.with_timezone(&Utc)));
}
Err("unknown time format".to_string())
}
}
impl Serialize for Time {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
match self.inner {
Some(dt) => {
serializer.serialize_str(&dt.to_rfc3339())
}
None => serializer.serialize_none(),
}
}
}
impl<'de> Deserialize<'de> for Time {
fn deserialize<D>(deserializer: D) -> Result<Time, D::Error>
where
D: Deserializer<'de>,
{
let s: Option<String> = Option::deserialize(deserializer)?;
match s {
Some(s) => {
let s = s.trim().trim_matches('"');
Self::parse_time(s)
.map(|opt_dt| Time { inner: opt_dt })
.map_err(serde::de::Error::custom)
}
None => Ok(Time { inner: None }),
}
}
}
impl fmt::Display for Time {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self.inner {
Some(dt) => write!(f, "{}", dt.to_rfc3339()),
None => write!(f, "null"),
}
}
}
impl Default for Time {
fn default() -> Self {
Time { inner: None }
}
}
impl From<DateTime<Utc>> for Time {
fn from(dt: DateTime<Utc>) -> Self {
Time { inner: Some(dt) }
}
}
impl From<Option<DateTime<Utc>>> for Time {
fn from(opt_dt: Option<DateTime<Utc>>) -> Self {
Time { inner: opt_dt }
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_parse_date_only() {
let result = Time::parse_time("2024-01-15").unwrap();
assert!(result.is_some());
}
#[test]
fn test_parse_datetime() {
let result = Time::parse_time("2024-01-15 14:30:00").unwrap();
assert!(result.is_some());
}
#[test]
fn test_parse_rfc3339() {
let result = Time::parse_time("2024-01-15T14:30:00+05:30").unwrap();
assert!(result.is_some());
}
#[test]
fn test_parse_null() {
let result = Time::parse_time("null").unwrap();
assert!(result.is_none());
}
#[test]
fn test_parse_empty() {
let result = Time::parse_time("").unwrap();
assert!(result.is_none());
}
}