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 Ok(Self::from_str(string.trim())?)
146 } else {
147 if let Ok(val) = value.parse::<i64>() {
148 Ok(Self::Timestamp(val.into()))
149 } else {
150 Ok(Self::StringTime(value.to_string().into()))
151 }
152 }
153 }
154}
155
156impl FromStr for TimestampUnit {
157 type Err = anyhow::Error;
158
159 fn from_str(s: &str) -> Result<Self, Self::Err> {
160 let s = s.to_lowercase();
161 match s.as_str() {
162 "s" | "seconds" => Ok(TimestampUnit::Seconds),
163 "ms" | "milliseconds" => Ok(TimestampUnit::Milliseconds),
164 _ => Err(anyhow!("Invalid time unit: {}", s)),
165 }
166 }
167}
168
169impl FromStr for TimeFormat {
170 type Err = anyhow::Error;
171
172 fn from_str(val: &str) -> Result<Self, Self::Err> {
173 let s = val.to_lowercase();
174 match s.as_str() {
175 "rfc3339" => Ok(TimeFormat::RFC3339),
176 "timestamp" | "ts" => Ok(TimeFormat::Timestamp),
177 _ => Ok(TimeFormat::Format(val.to_string())),
178 }
179 }
180}