1use thiserror::Error;
2use time::{
3 Duration, OffsetDateTime, Time, UtcOffset,
4 format_description::{self},
5 macros::format_description as fd,
6};
7
8#[derive(Error, Debug)]
9pub enum OffsetDateTimeError {
10 #[error("Invalid offset hours: {0}")]
11 InvalidOffsetHours(i8),
12 #[error("Invalid timestamp: {0}")]
13 InvalidTimestamp(i64),
14 #[error("Invalid milliseconds: {0}")]
15 InvalidMilliseconds(u16),
16 #[error("Failed to parse datetime: {0}")]
17 ParseError(String),
18 #[error("Failed to format datetime: {0}")]
19 FormatError(String),
20 #[error("Invalid seconds value: {0}")]
21 InvalidSeconds(i64),
22 #[error("Invalid alignment unit: {0}")]
23 InvalidAlignmentUnit(u64),
24 #[error("Failed to add time: {0:?}")]
25 AddTimeError(OffsetDateTime),
26}
27
28pub trait ExtOffsetDateTime {
29 fn is_same_minute(&self, b: &OffsetDateTime) -> bool;
31
32 fn reset_minute(&self) -> OffsetDateTime;
34
35 fn milli_timestamp(&self) -> i64;
37
38 fn to_display_string(&self, offset_hours: i8) -> String;
40
41 fn to_chinese_string(&self) -> String;
43
44 fn from_milliseconds(
46 timestamp: u64,
47 offset_hours: i8,
48 ) -> Result<OffsetDateTime, OffsetDateTimeError>;
49
50 fn from_seconds(
52 timestamp: u64,
53 offset_hours: i8,
54 ) -> Result<OffsetDateTime, OffsetDateTimeError>;
55
56 fn from_date_time(
58 date: &str,
59 time: &str,
60 milli: u64,
61 offset_hours: i8,
62 ) -> Result<OffsetDateTime, OffsetDateTimeError>;
63
64 fn from_simple(dt: &str, offset_hours: i8) -> Result<OffsetDateTime, OffsetDateTimeError>;
66
67 fn convert_to_dot_date(input: &str) -> Result<String, OffsetDateTimeError>;
69
70 fn now_with_offset(offset_hours: i8) -> OffsetDateTime {
72 OffsetDateTime::now_utc().to_offset(UtcOffset::from_hms(offset_hours, 0, 0).unwrap())
73 }
74
75 fn replace_time_with_seconds(
84 &self,
85 seconds: i64,
86 ) -> Result<OffsetDateTime, OffsetDateTimeError>;
87
88 fn align_to(&self, interval: i64) -> Result<OffsetDateTime, OffsetDateTimeError>;
97
98 fn next_day(&self) -> OffsetDateTime;
100
101 fn next_hour(&self) -> OffsetDateTime;
103
104 fn next_minute(&self) -> OffsetDateTime;
106
107 fn next_second(&self) -> OffsetDateTime;
109
110 fn to_hour_seconds(&self) -> i64;
117
118 fn to_minute_seconds(&self) -> i64;
125
126 fn duration_to_time(&self, target_hour: u8, target_minute: u8, target_second: u8) -> Duration;
145}
146
147impl ExtOffsetDateTime for OffsetDateTime {
148 fn is_same_minute(&self, b: &OffsetDateTime) -> bool {
149 self.hour() == b.hour() && self.minute() == b.minute()
150 }
151
152 fn reset_minute(&self) -> OffsetDateTime {
153 let time = Time::from_hms(self.hour(), self.minute(), 0).expect("Invalid time components");
154 self.replace_time(time)
155 }
156
157 fn milli_timestamp(&self) -> i64 {
158 (self.unix_timestamp() as i64) * 1000 + self.millisecond() as i64
159 }
160
161 fn to_display_string(&self, offset_hours: i8) -> String {
162 let offset = UtcOffset::from_hms(offset_hours, 0, 0).expect("Invalid offset hours");
163 self.to_offset(offset)
164 .format(
165 &format_description::parse(
166 "[year]-[month]-[day] [hour repr:24]:[minute]:[second][offset_hour sign:mandatory]:[offset_minute]"
167 )
168 .unwrap(),
169 )
170 .expect("Failed to format datetime")
171 }
172
173 fn to_chinese_string(&self) -> String {
174 let offset = UtcOffset::from_hms(8, 0, 0).expect("Invalid offset hours");
175 let format = format_description::parse(
176 "[year]年[month]月[day]日 [hour]时[minute]分[second]秒 [offset_hour sign:mandatory]:[offset_minute]",
177 )
178 .expect("parse");
179 self.to_offset(offset)
180 .format(&format)
181 .expect("Failed to format datetime")
182 }
183
184 fn from_milliseconds(
185 timestamp: u64,
186 offset_hours: i8,
187 ) -> Result<OffsetDateTime, OffsetDateTimeError> {
188 let seconds = timestamp / 1000;
189 let millis = timestamp % 1000;
190 let offset = UtcOffset::from_hms(offset_hours, 0, 0)
191 .map_err(|_| OffsetDateTimeError::InvalidOffsetHours(offset_hours))?;
192
193 let dt = OffsetDateTime::from_unix_timestamp(seconds as i64)
194 .map_err(|_| OffsetDateTimeError::InvalidTimestamp(seconds as i64))?;
195
196 let dt = dt
197 .replace_millisecond(millis as u16)
198 .map_err(|_| OffsetDateTimeError::InvalidMilliseconds(millis as u16))?;
199
200 Ok(dt.to_offset(offset))
201 }
202
203 fn from_seconds(
204 timestamp: u64,
205 offset_hours: i8,
206 ) -> Result<OffsetDateTime, OffsetDateTimeError> {
207 let offset = UtcOffset::from_hms(offset_hours, 0, 0)
208 .map_err(|_| OffsetDateTimeError::InvalidOffsetHours(offset_hours))?;
209
210 let dt = OffsetDateTime::from_unix_timestamp(timestamp as i64)
211 .map_err(|_| OffsetDateTimeError::InvalidTimestamp(timestamp as i64))?;
212
213 Ok(dt.to_offset(offset))
214 }
215
216 fn from_date_time(
217 date_str: &str,
218 time_str: &str,
219 milli: u64,
220 offset_hours: i8,
221 ) -> Result<OffsetDateTime, OffsetDateTimeError> {
222 let format = fd!(
223 "[year][month][day] [hour]:[minute]:[second].[subsecond digits:3] [offset_hour \
224 sign:mandatory]:[offset_minute]:[offset_second]"
225 );
226 let dt = format!(
227 "{} {}.{:03} {:+03}:00:00",
228 date_str, time_str, milli, offset_hours
229 );
230 OffsetDateTime::parse(&dt, &format)
231 .map_err(|e| OffsetDateTimeError::ParseError(e.to_string()))
232 }
233
234 fn from_simple(dt: &str, offset_hours: i8) -> Result<OffsetDateTime, OffsetDateTimeError> {
235 let format = fd!("[year][month][day]_[hour][minute] [offset_hour sign:mandatory]");
236 let dt = format!("{} {:+03}", dt, offset_hours);
237 OffsetDateTime::parse(&dt, &format)
238 .map_err(|e| OffsetDateTimeError::ParseError(e.to_string()))
239 }
240
241 fn convert_to_dot_date(input: &str) -> Result<String, OffsetDateTimeError> {
242 let parse_format = fd!("[year][month][day]");
243 let date = time::Date::parse(input, &parse_format)
244 .map_err(|e| OffsetDateTimeError::ParseError(e.to_string()))?;
245
246 let output_format = fd!("[year].[month].[day]");
247 date.format(&output_format)
248 .map_err(|e| OffsetDateTimeError::FormatError(e.to_string()))
249 }
250
251 fn replace_time_with_seconds(
252 &self,
253 seconds: i64,
254 ) -> Result<OffsetDateTime, OffsetDateTimeError> {
255 if seconds < 0 || seconds >= 24 * 3600 {
256 return Err(OffsetDateTimeError::InvalidSeconds(seconds));
257 }
258
259 let hours = (seconds / 3600) as u8;
260 let minutes = ((seconds % 3600) / 60) as u8;
261 let secs = (seconds % 60) as u8;
262
263 let time = Time::from_hms(hours, minutes, secs)
264 .map_err(|_| OffsetDateTimeError::InvalidSeconds(seconds))?;
265
266 Ok(self.replace_time(time))
267 }
268
269 fn align_to(&self, interval: i64) -> Result<OffsetDateTime, OffsetDateTimeError> {
270 if interval == 0 {
271 return Err(OffsetDateTimeError::InvalidAlignmentUnit(
272 interval.abs() as u64
273 ));
274 }
275
276 let total_seconds =
277 self.hour() as i64 * 3600 + self.minute() as i64 * 60 + self.second() as i64;
278 let aligned_seconds = (total_seconds / interval) * interval;
279
280 let hours = (aligned_seconds / 3600) as u8;
281 let minutes = ((aligned_seconds % 3600) / 60) as u8;
282 let secs = (aligned_seconds % 60) as u8;
283
284 let time = Time::from_hms(hours, minutes, secs)
285 .map_err(|_| OffsetDateTimeError::InvalidSeconds(aligned_seconds))?;
286
287 Ok(self.replace_time(time))
288 }
289
290 fn next_day(&self) -> OffsetDateTime {
291 self.clone() + Duration::days(1)
292 }
293
294 fn next_hour(&self) -> OffsetDateTime {
295 self.clone() + Duration::hours(1)
296 }
297
298 fn next_minute(&self) -> OffsetDateTime {
299 self.clone() + Duration::minutes(1)
300 }
301
302 fn next_second(&self) -> OffsetDateTime {
303 self.clone() + Duration::seconds(1)
304 }
305
306 fn to_hour_seconds(&self) -> i64 {
307 self.hour() as i64 * 3600
308 }
309
310 fn to_minute_seconds(&self) -> i64 {
311 self.hour() as i64 * 3600 + self.minute() as i64 * 60
312 }
313
314 fn duration_to_time(&self, target_hour: u8, target_minute: u8, target_second: u8) -> Duration {
315 let target_time = Time::from_hms(target_hour, target_minute, target_second)
317 .expect("Invalid target time components");
318
319 let target_today = self.replace_time(target_time);
321
322 let duration_to_today = target_today - *self;
324
325 if duration_to_today.is_positive() || duration_to_today.is_zero() {
326 duration_to_today
328 } else {
329 let target_tomorrow = target_today + Duration::days(1);
331 target_tomorrow - *self
332 }
333 }
334}