dev_kit/command/time/
mod.rs

1use crate::command::read_stdin;
2use anyhow::anyhow;
3use chrono::{FixedOffset, Utc};
4use derive_more::{Deref, Display, From, FromStr};
5use serde::Serialize;
6use std::panic;
7use std::str::FromStr;
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 let Some(string) = read_stdin() {
145            if !string.is_empty() {
146                return Ok(Self::from_str(&string)?);
147            }
148        }
149        if value.is_empty() {
150            Err(anyhow!("Invalid input"))
151        } else if let Ok(val) = value.parse::<i64>() {
152            Ok(Self::Timestamp(val.into()))
153        } else {
154            Ok(Self::StringTime(value.to_string().into()))
155        }
156    }
157}
158
159impl FromStr for TimestampUnit {
160    type Err = anyhow::Error;
161
162    fn from_str(s: &str) -> Result<Self, Self::Err> {
163        let s = s.to_lowercase();
164        match s.as_str() {
165            "s" | "seconds" => Ok(TimestampUnit::Seconds),
166            "ms" | "milliseconds" => Ok(TimestampUnit::Milliseconds),
167            _ => Err(anyhow!("Invalid time unit: {}", s)),
168        }
169    }
170}
171
172impl FromStr for TimeFormat {
173    type Err = anyhow::Error;
174
175    fn from_str(val: &str) -> Result<Self, Self::Err> {
176        let s = val.to_lowercase();
177        match s.as_str() {
178            "rfc3339" => Ok(TimeFormat::RFC3339),
179            "timestamp" | "ts" => Ok(TimeFormat::Timestamp),
180            _ => Ok(TimeFormat::Format(val.to_string())),
181        }
182    }
183}