repo_description: A build-time information stored in your rust project.(binary,lib,cdylib,dylib,wasm)
repo_topics: build cargo cargo-build cargo-env cargo-lock cargo-plugin cargo-toml cargolock compile compiled compiler env metadata reproducible-build reproducible-builds rust rust-build rust-crate shadow shadow-rs
code: use crate::{Format, SdResult, ShadowError};
  use std::error::Error;
  use time::format_description::well_known::{Rfc2822, Rfc3339};
  #[cfg(feature = "tzdb")]
  use time::UtcOffset;
  use time::{format_description, OffsetDateTime};
  
  pub enum DateTime {
      Local(OffsetDateTime),
      Utc(OffsetDateTime),
  }
  
  pub(crate) const DEFINE_SOURCE_DATE_EPOCH: &str = "SOURCE_DATE_EPOCH";
  
  pub fn now_date_time() -> DateTime {
      // Enable reproducibility for uses of `now_date_time` by respecting the
      // `SOURCE_DATE_EPOCH` env variable.
      //
      // https://reproducible-builds.org/docs/source-date-epoch/
      match std::env::var_os(DEFINE_SOURCE_DATE_EPOCH) {
          None => DateTime::now(),
          Some(timestamp) => {
              let epoch = timestamp
                  .into_string()
                  .unwrap_or_else(|_| panic!("Input {DEFINE_SOURCE_DATE_EPOCH} could not be parsed"))
                  .parse::<i64>()
                  .unwrap_or_else(|_| {
                      panic!("Input {DEFINE_SOURCE_DATE_EPOCH} could not be cast to a number")
                  });
              DateTime::Utc(OffsetDateTime::from_unix_timestamp(epoch).unwrap())
          }
      }
  }
  
  impl Default for DateTime {
      fn default() -> Self {
          Self::now()
      }
  }
  
  impl DateTime {
      pub fn now() -> Self {
          Self::local_now().unwrap_or_else(|_| DateTime::Utc(OffsetDateTime::now_utc()))
      }
  
      pub fn offset_datetime() -> OffsetDateTime {
          let date_time = Self::now();
          match date_time {
              DateTime::Local(time) | DateTime::Utc(time) => time,
          }
      }
  
      #[cfg(not(feature = "tzdb"))]
      pub fn local_now() -> Result<Self, Box<dyn Error>> {
          // Warning: This attempts to create a new OffsetDateTime with the current date and time in the local offset, which may fail.
          // Currently, it always fails on MacOS.
          // This issue does not exist with the "tzdb" feature (see below), which should be used instead.
          OffsetDateTime::now_local()
              .map(DateTime::Local)
              .map_err(|e| e.into())
      }
  
      #[cfg(feature = "tzdb")]
      pub fn local_now() -> Result<Self, Box<dyn Error>> {
          let local_time = tzdb::now::local()?;
          let time_zone_offset =
              UtcOffset::from_whole_seconds(local_time.local_time_type().ut_offset())?;
          let local_date_time = OffsetDateTime::from_unix_timestamp(local_time.unix_time())?
              .to_offset(time_zone_offset);
          Ok(DateTime::Local(local_date_time))
      }
  
      pub fn timestamp_2_utc(time_stamp: i64) -> SdResult<Self> {
          let time = OffsetDateTime::from_unix_timestamp(time_stamp).map_err(ShadowError::new)?;
          Ok(DateTime::Utc(time))
      }
  
      pub fn from_iso8601_string(iso_string: &str) -> SdResult<Self> {
          let time = OffsetDateTime::parse(iso_string, &Rfc3339).map_err(ShadowError::new)?;
          Ok(DateTime::Local(time))
      }
  
      pub fn to_rfc2822(&self) -> String {
          match self {
              DateTime::Local(dt) | DateTime::Utc(dt) => dt.format(&Rfc2822).unwrap_or_default(),
          }
      }
  
      pub fn to_rfc3339(&self) -> String {
          match self {
              DateTime::Local(dt) | DateTime::Utc(dt) => dt.format(&Rfc3339).unwrap_or_default(),
          }
      }
  
      pub fn timestamp(&self) -> i64 {
          match self {
              DateTime::Local(dt) | DateTime::Utc(dt) => dt.unix_timestamp(),
          }
      }
  }
  
  impl Format for DateTime {
      fn human_format(&self) -> String {
          match self {
              DateTime::Local(dt) | DateTime::Utc(dt) => dt.human_format(),
          }
      }
  }
  
  impl Format for OffsetDateTime {
      fn human_format(&self) -> String {
          let fmt = format_description::parse(
              "[year]-[month]-[day] [hour]:[minute]:[second] [offset_hour \
           sign:mandatory]:[offset_minute]",
          )
          .unwrap();
          self.format(&fmt).unwrap()
      }
  }
  
  #[cfg(test)]
  mod tests {
      use super::*;
      use human_format_validate::parse_human_format;
  
      mod human_format_validate {
          use std::num::{NonZeroU32, NonZeroU8};
          use winnow::ascii::{digit1, space1};
          use winnow::error::{ContextError, ParseError};
          use winnow::token::{literal, take};
          use winnow::{ModalResult, Parser};
  
          fn u8_len2(input: &mut &str) -> ModalResult<u8> {
              take(2_usize).try_map(str::parse).parse_next(input)
          }
  
          fn non_zero_u8_len2<const LIMIT: u8>(input: &mut &str) -> ModalResult<NonZeroU8> {
              take(2_usize)
                  .try_map(str::parse)
                  .verify(|x| *x <= unsafe { NonZeroU8::new_unchecked(LIMIT) })
                  .parse_next(input)
          }
  
          //
          fn non_zero_u32(input: &mut &str) -> ModalResult<NonZeroU32> {
              digit1.try_map(str::parse).parse_next(input)
          }
  
          // 2022-07-14 00:40:05 +08:00
          pub(crate) fn parse_human_format(
              input: &str,
          ) -> Result<(), ParseError<&str, ContextError>> {
              (
                  non_zero_u32,
                  literal('-'),
                  non_zero_u8_len2::<12>,
                  literal('-'),
                  non_zero_u8_len2::<31>,
                  space1,
                  u8_len2,
                  literal(':'),
                  u8_len2,
                  literal(':'),
                  u8_len2,
                  space1,
                  literal('+'),
                  u8_len2,
                  literal(':'),
                  u8_len2,
              )
                  .parse(input)?;
              Ok(())
          }
  
          #[test]
          fn test_parse() {
              assert!(parse_human_format("2022-07-14 00:40:05 +08:00").is_ok());
              assert!(parse_human_format("2022-12-14 00:40:05 +08:00").is_ok());
              assert!(parse_human_format("2022-13-14 00:40:05 +08:00").is_err());
              assert!(parse_human_format("2022-12-31 00:40:05 +08:00").is_ok());
              assert!(parse_human_format("2022-12-32 00:40:05 +08:00").is_err());
              assert!(parse_human_format("2022-07-14 00:40:05 +08:0").is_err());
              assert!(parse_human_format("2022-07-14 00:40:05 -08:0").is_err());
              assert!(parse_human_format("2022-07-00 00:40:05 +08:00").is_err());
              assert!(parse_human_format("2022-00-01 00:40:05 +08:00").is_err());
              assert!(parse_human_format("2022-00-01 00:40:05 08:00").is_err());
              assert!(parse_human_format("2022-00-01 00:40:05+08:00").is_err());
              assert!(parse_human_format("20221-00-01 00:40:05+08:00").is_err());
              assert!(parse_human_format("20221-01-01 00:40:05 +08:00").is_ok());
          }
      }
  
      #[test]
      fn test_source_date_epoch() {
          std::env::set_var(DEFINE_SOURCE_DATE_EPOCH, "1628080443");
          let time = now_date_time();
          assert_eq!(time.human_format(), "2021-08-04 12:34:03 +00:00");
      }
  
      #[test]
      fn test_local_now_human_format() {
          let time = DateTime::local_now().unwrap().human_format();
          #[cfg(unix)]
          assert!(!std::fs::read("/etc/localtime").unwrap().is_empty());
  
          assert!(parse_human_format(&time).is_ok());
  
          println!("local now:{time}"); // 2022-07-14 00:40:05 +08:00
          assert_eq!(time.len(), 26);
      }
  
      #[test]
      fn test_timestamp_2_utc() {
          let time = DateTime::timestamp_2_utc[REDACTED_PHONE]).unwrap();
          assert_eq!(time.to_rfc2822(), "Wed, 04 Aug 2021 12:34:03 +0000");
          assert_eq!(time.to_rfc3339(), "2021-08-04T12:34:03Z");
          assert_eq!(time.human_format(), "2021-08-04 12:34:03 +00:00");
          assert_eq!(time.timestamp(), 1628080443);
      }
  
      #[test]
      fn test_from_iso8601_string() {
          let time = DateTime::from_iso8601_string("2021-08-04T12:34:03+08:00").unwrap();
          assert_eq!(time.to_rfc2822(), "Wed, 04 Aug 2021 12:34:03 +0800");
          assert_eq!(time.to_rfc3339(), "2021-08-04T12:34:03+08:00");
          assert_eq!(time.human_format(), "2021-08-04 12:34:03 +08:00");
      }
  }
  