dev_kit/command/time/
mod.rs

1use anyhow::anyhow;
2use chrono::{FixedOffset, Utc};
3use derive_more::{Deref, Display, From, FromStr};
4use std::io::Read;
5use std::panic;
6use std::str::FromStr;
7use serde::Serialize;
8
9#[derive(clap::Subcommand)]
10pub enum TimeCommand {
11    #[clap(about = "get current time")]
12    Now {
13        #[arg(long, short, help = "output timezone, alias tz, default to LOCAL", alias = "tz")]
14        timezone: Option<FixedOffset>,
15        #[arg(long, short, help = "output time format: rfc3339(default), timestamp(ts) or custom format")]
16        format: Option<TimeFormat>,
17        #[arg(long, help = "output unix-timestamp unit, s or ms, alias iu, default to ms", alias = "iu")]
18        output_unit: Option<TimestampUnit>,
19    },
20    #[clap(about = "time paser")]
21    Parse {
22        #[arg(help = "input time, support unix-timestamp or string time, eg. 2023-01-01 12:00:00", default_value = "-")]
23        time: Time,
24        #[arg(long, help = "input unix-timestamp unit, s or ms, alias iu, default to ms", alias = "iu")]
25        input_unit: Option<TimestampUnit>,
26        #[arg(long, short, help = "input timezone, alias tz, default to local", alias = "tz")]
27        timezone: Option<FixedOffset>,
28        #[arg(long, short, help = "output time format: rfc3339(default), timestamp(ts) or custom format")]
29        format: Option<TimeFormat>,
30        #[arg(long, help = "output unix-timestamp unit, s or ms, alias ou, default to ms", alias = "ou")]
31        output_unit: Option<TimestampUnit>,
32    },
33}
34
35
36#[derive(Debug, Clone, Display, Serialize)]
37pub enum Time {
38    StringTime(Timestring),
39    Timestamp(Timestamp),
40}
41#[derive(Debug, Clone, Display, Deref, From, FromStr, Serialize)]
42#[display("{_0}")]
43pub struct Timestring(String);
44mod timestring_guess;
45#[derive(Debug, Copy, Clone, Display, Deref, From, Serialize)]
46#[display("{_0}")]
47pub struct Timestamp(i64);
48
49#[derive(Debug, Copy, Clone, Display, Default)]
50pub enum TimestampUnit {
51    #[display("s")]
52    Seconds,
53    #[default]
54    #[display("ms")]
55    Milliseconds,
56}
57
58#[derive(Debug, Clone, Default)]
59pub enum TimeFormat {
60    #[default]
61    RFC3339,
62    Timestamp,
63    Format(String),
64}
65
66impl super::Command for TimeCommand {
67    fn run(&self) -> crate::Result<()> {
68        let TimeFormatterVal { output: result, .. } = self.run_actual()?;
69        println!("{}", result);
70        Ok(())
71    }
72}
73
74impl TimeCommand {
75    pub fn run_actual(&self) -> crate::Result<TimeFormatterVal> {
76        match self {
77            TimeCommand::Now { timezone, format, output_unit, } => {
78                let time = Utc::now().with_timezone(
79                    &timezone.unwrap_or(*chrono::Local::now().offset())
80                );
81                let result = Self::time_formatter(&time, &format, &output_unit.unwrap_or_default())?;
82                Ok(TimeFormatterVal {
83                    intput: Time::StringTime(Timestring(result.clone())),
84                    timestamp: Timestamp(time.timestamp_millis()),
85                    output: result,
86                })
87            }
88            TimeCommand::Parse { time: input_time, input_unit, timezone, format, output_unit, } => {
89                let time = match (input_time, input_unit.unwrap_or_default()) {
90                    (Time::Timestamp(time), TimestampUnit::Seconds) => {
91                        chrono::DateTime::from_timestamp(**time, 0).ok_or(anyhow!("Invalid timestamp {}seconds", time))?
92                    }
93                    (Time::Timestamp(time), TimestampUnit::Milliseconds) => {
94                        chrono::DateTime::from_timestamp_millis(**time).ok_or(anyhow!("Invalid timestamp {}milliseconds", time))?
95                    }
96                    (Time::StringTime(time), _) => {
97                        chrono::DateTime::<Utc>::try_from(time).map_err(|err| {
98                            log::debug!("Failed to parse time string: {}, error: {}", time, err);
99                            anyhow!("Invalid string time {time}")
100                        })?
101                    }
102                }.with_timezone(
103                    &timezone.unwrap_or(*chrono::Local::now().offset())
104                );
105                let result = Self::time_formatter(&time, &format, &output_unit.unwrap_or_default())?;
106                Ok(TimeFormatterVal {
107                    intput: input_time.clone(),
108                    timestamp: Timestamp(time.timestamp_millis()),
109                    output: result,
110                })
111            }
112        }
113    }
114}
115
116impl TimeCommand {
117    fn time_formatter(time: &chrono::DateTime<FixedOffset>, format: &Option<TimeFormat>, unit: &TimestampUnit) -> crate::Result<String> {
118        let format = format.clone().unwrap_or_default();
119        let result = match format {
120            TimeFormat::RFC3339 => time.to_rfc3339(),
121            TimeFormat::Timestamp => match unit {
122                TimestampUnit::Seconds => time.timestamp().to_string(),
123                TimestampUnit::Milliseconds => time.timestamp_millis().to_string(),
124            },
125            TimeFormat::Format(format) => panic::catch_unwind(|| {
126                time.format(&format).to_string()
127            }).map_err(|_| anyhow!("Invalid time format"))?,
128        };
129        Ok(result)
130    }
131}
132
133#[derive(Debug, Clone, Serialize)]
134pub struct TimeFormatterVal {
135    intput: Time,
136    timestamp: Timestamp,
137    output: String,
138}
139
140impl FromStr for Time {
141    type Err = anyhow::Error;
142
143    fn from_str(value: &str) -> Result<Self, Self::Err> {
144        if value.eq("-") {
145            let mut string = String::new();
146            let _ = std::io::stdin().lock().read_to_string(&mut string)
147                .map_err(|err| anyhow!("read from stdin failed, {}", err))?;
148            match string.trim() {
149                "-" => Err(anyhow!("Not a valid input")),
150                _ => Ok(Self::from_str(string.trim())?)
151            }
152        } else {
153            if let Ok(val) = value.parse::<i64>() {
154                Ok(Self::Timestamp(val.into()))
155            } else {
156                Ok(Self::StringTime(value.to_string().into()))
157            }
158        }
159    }
160}
161
162impl FromStr for TimestampUnit {
163    type Err = anyhow::Error;
164
165    fn from_str(s: &str) -> Result<Self, Self::Err> {
166        let s = s.to_lowercase();
167        match s.as_str() {
168            "s" | "seconds" => Ok(TimestampUnit::Seconds),
169            "ms" | "milliseconds" => Ok(TimestampUnit::Milliseconds),
170            _ => Err(anyhow!("Invalid time unit: {}", s)),
171        }
172    }
173}
174
175impl FromStr for TimeFormat {
176    type Err = anyhow::Error;
177
178    fn from_str(val: &str) -> Result<Self, Self::Err> {
179        let s = val.to_lowercase();
180        match s.as_str() {
181            "rfc3339" => Ok(TimeFormat::RFC3339),
182            "timestamp" | "ts" => Ok(TimeFormat::Timestamp),
183            _ => Ok(TimeFormat::Format(val.to_string())),
184        }
185    }
186}