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}