dayjs/
lib.rs

1use chrono::{
2    DateTime, Datelike, FixedOffset, Local, Offset, TimeZone as CTimeZone, Timelike, Utc, Weekday,
3};
4use std::fmt::{Display, Formatter};
5
6/// re-export chrono
7pub use chrono;
8
9/// TimeZone enum for representing different time zone formats.
10///
11/// It can represent:
12/// - Time zone offset as a string (e.g., "+08:00")
13/// - Time zone city as a string (e.g., "Asia/Shanghai")
14/// - Time zone number as an integer (-12 to +12)
15///
16/// # Examples
17///
18/// ```
19/// use dayjs::TimeZone;
20///
21/// let tz_offset = TimeZone::TimeZoneTime("+08:00".to_string());
22///
23/// let tz_city = TimeZone::TimeZoneCity("Asia/Shanghai".to_string());
24///
25/// let tz_number = TimeZone::TimeZoneNumber(8);
26///
27#[derive(Clone, Debug, PartialEq)]
28pub enum TimeZone {
29    // 时区偏移, 如: "+08:00"
30    TimeZoneTime(String),
31    // 时区城市, 如: "Asia/Shanghai"
32    TimeZoneCity(String),
33    // 时区编号, -12 ~ +12
34    TimeZoneNumber(i8),
35}
36
37impl TimeZone {
38    /// Get the current time zone as a string.
39    pub fn current() -> Self {
40        let offset = Local::now().offset().fix();
41        TimeZone::TimeZoneTime(offset.to_string())
42    }
43}
44
45/// Dayjs struct representing a date and time with a time zone.
46///
47/// It contains:
48/// - `tz`: The time zone information as a `TimeZone` enum.
49/// - `time`: The UTC time as a `DateTime<Utc>`.
50///
51/// # Examples
52/// ```
53/// use dayjs::{dayjs, Dayjs, TimeZone};
54/// let now = dayjs();
55/// ```
56#[derive(Clone, Debug, PartialEq)]
57pub struct Dayjs {
58    /// Time zone information
59    pub(crate) tz: TimeZone,
60    /// UTC time
61    pub(crate) time: DateTime<Utc>,
62}
63
64impl Default for Dayjs {
65    fn default() -> Self {
66        Dayjs {
67            tz: TimeZone::current(),
68            time: Utc::now(),
69        }
70    }
71}
72
73/// UTC Timestamp, eg: 143164800
74///
75/// # Examples
76/// ```
77/// use dayjs::timestamp;
78/// let ts = timestamp();
79/// println!("Current UTC timestamp: {}", ts);
80/// ```
81///
82pub fn timestamp() -> i64 {
83    Utc::now().timestamp()
84}
85
86/// Create a new Dayjs instance with the current time.
87///
88/// # Examples
89/// ```
90/// use dayjs::dayjs;
91/// let now = dayjs();
92/// println!("Current time: {}", now);
93/// ```
94pub fn dayjs() -> Dayjs {
95    Dayjs::default()
96}
97
98/// Create a new Dayjs instance with the current time.
99/// This is an alias for `dayjs()`.
100/// # Examples
101/// ```
102/// use dayjs::now;
103/// let current_time = now();
104/// println!("Current time: {}", current_time);
105/// ```
106pub fn now() -> Dayjs {
107    Dayjs::default()
108}
109
110/// Get Dayjs instance from a string representation of date time.
111///
112/// # Parameters
113/// - `s`: A string representing the date time, which can be in various formats such as ISO 8601, RFC 3339, or RFC 2822.
114/// # Returns
115/// - `Ok(Dayjs)`: If the string is successfully parsed into a `DateTime<Utc>`.
116/// - `Err(String)`: If the string cannot be parsed, an error message is returned.
117///
118/// # Examples
119/// ```
120/// use dayjs::from_str;
121/// let date_str = "2023-10-01T12:00:00Z";
122/// let dayjs_instance = from_str(date_str);
123/// match dayjs_instance {
124///     Ok(dayjs) => println!("Parsed Dayjs: {}", dayjs),
125///     Err(e) => println!("Error parsing date: {}", e),
126/// }
127/// ```
128pub fn from_str(s: &str) -> Result<Dayjs, String> {
129    let time: DateTime<Utc> =
130        parse_date_time(s).ok_or_else(|| format!("Failed to parse date time from string: {s}"))?;
131    Ok(Dayjs {
132        time,
133        ..Default::default()
134    })
135}
136
137/// Get Dayjs instance from an integer timestamp.
138/// # Parameters
139/// - `n`: An integer representing the timestamp, which can be in seconds (10 digits) or milliseconds (13 digits).
140/// # Returns
141/// - `Ok(Dayjs)`: If the integer is successfully converted to a `DateTime<Utc>`.
142/// - `Err(String)`: If the integer is not a valid timestamp or does not match the expected length (10 or 13 digits).
143///
144/// # Examples
145/// ```
146/// use dayjs::from_int64;
147/// let timestamp = 1633072800; // Example timestamp in seconds
148/// let dayjs_instance = from_int64(timestamp);
149/// match dayjs_instance {
150///     Ok(dayjs) => println!("Parsed Dayjs: {}", dayjs),
151///     Err(e) => println!("Error parsing timestamp: {}", e),
152/// }
153/// ```
154pub fn from_int64(n: i64) -> Result<Dayjs, String> {
155    let len = format!("{n}").len();
156    match len {
157        10 => {
158            let r = Utc.timestamp_opt(n, 0);
159            let r = r
160                .single()
161                .ok_or_else(|| format!("{n} is not a valid timestamp"))?;
162            Ok(Dayjs {
163                time: r,
164                ..Default::default()
165            })
166        }
167        13 => {
168            let r = Utc.timestamp_millis_opt(n);
169            let r = r
170                .single()
171                .ok_or_else(|| format!("{n} is not a valid timestamp"))?;
172            Ok(Dayjs {
173                time: r,
174                ..Default::default()
175            })
176        }
177        _ => Err(format!("{n} is not a safe time number"))?,
178    }
179}
180
181/// Get the current time zone of the Dayjs instance.
182///
183/// # Parameters
184/// - `tz`: A `TimeZone` enum representing the desired time zone.
185/// # Returns
186/// A `Dayjs` instance with the specified time zone and the current UTC time.
187///
188/// # Examples
189/// ```
190/// use dayjs::{from_timezone, TimeZone};
191/// let tz = TimeZone::TimeZoneTime("+08:00".to_string());
192/// let dayjs_instance = from_timezone(tz);
193/// println!("Current time in specified timezone: {}", dayjs_instance);
194/// ```
195pub fn from_timezone(tz: TimeZone) -> Dayjs {
196    Dayjs {
197        tz,
198        time: Utc::now(),
199    }
200}
201
202/// Parse date time string, supports ISO 8601 format (with timezone offset and 'Z' suffix) and UTC time
203///
204/// # Parameters
205/// - `s`: The date time string to be parsed
206///
207/// # Returns
208/// Returns `DateTime<Utc>` on successful parsing, `None` on failure
209pub fn parse_date_time(s: &str) -> Option<DateTime<Utc>> {
210    if s.ends_with("UTC") || s.ends_with("utc") {
211        let s = s.replace("UTC", "").replace("utc", "");
212        let s = s.trim_end();
213        let s = format!("{} {}", s, "+00:00");
214        if let Ok(dt) = DateTime::parse_from_str(&s, "%Y-%m-%d %H:%M:%S %:z") {
215            return Some(dt.with_timezone(&Utc));
216        }
217    }
218    if let Ok(dt) = DateTime::parse_from_rfc3339(s) {
219        return Some(dt.with_timezone(&Utc));
220    }
221    if let Ok(dt) = DateTime::parse_from_rfc2822(s) {
222        return Some(dt.with_timezone(&Utc));
223    }
224    if let Some(offset) = FixedOffset::east_opt(0) {
225        if let Ok(dt) = DateTime::parse_from_str(s, "%Y-%m-%d %H:%M:%S%.f %z") {
226            return Some(dt.with_timezone(&offset).with_timezone(&Utc));
227        }
228        if let Ok(dt) = DateTime::parse_from_str(s, "%Y-%m-%dT%H:%M:%S%.fZ") {
229            return Some(dt.with_timezone(&offset).with_timezone(&Utc));
230        }
231        if let Ok(dt) = DateTime::parse_from_str(s, "%Y-%m-%d %H:%M:%S %:z") {
232            return Some(dt.with_timezone(&offset).with_timezone(&Utc));
233        }
234        let s = format!("{} {}", s, "+00:00");
235        if let Ok(dt) = DateTime::parse_from_str(&s, "%Y-%m-%d %H:%M:%S %:z") {
236            return Some(dt.with_timezone(&offset).with_timezone(&Utc));
237        }
238    }
239    None
240}
241
242impl Display for Dayjs {
243    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
244        let s = self.time.to_utc();
245        let v = s.to_string();
246        write!(f, "{v}")
247    }
248}
249
250impl Dayjs {
251    /// Format the date time according to the given template.
252    ///
253    /// # Parameters
254    /// - `template`: %Y-%m-%d %H:%M:%S
255    ///
256    /// # Examples
257    /// ```
258    /// let now = dayjs::dayjs();
259    /// let formatted = now.format("%Y-%m-%d %H:%M:%S");
260    /// println!("{}", formatted);
261    /// // 2025-03-25 17:21:47
262    /// ```
263    ///
264    pub fn format(&self, template: &str) -> String {
265        self.time.format(template).to_string()
266    }
267
268    /// Set the time zone for the Dayjs instance.
269    pub fn set_timezone(&mut self, tz: TimeZone) {
270        self.tz = tz;
271    }
272
273    /// Get the current time zone of the Dayjs instance.
274    pub fn get_timezone(&self) -> &TimeZone {
275        &self.tz
276    }
277
278    /// Get the current time in UTC.
279    pub fn get_timestamp(&self) -> i64 {
280        self.time.timestamp()
281    }
282
283    /// Get the current time in second.
284    pub fn timestamp(&self) -> i64 {
285        self.time.timestamp()
286    }
287
288    /// Get the current time in milliseconds since the Unix epoch.
289    pub fn millisecond(&self) -> i64 {
290        self.time.timestamp_millis()
291    }
292
293    /// Get the current time in seconds since the Unix epoch.
294    pub fn second(&self) -> i64 {
295        self.time.timestamp()
296    }
297
298    /// Get the current time in nanoseconds since the Unix epoch.
299    pub fn minute(&self) -> u32 {
300        self.time.minute()
301    }
302
303    /// Get the current time in hours since the Unix epoch.
304    pub fn hour(&self) -> u32 {
305        self.time.hour()
306    }
307
308    /// Get the date of month 1 ~ 31
309    pub fn date(&self) -> u32 {
310        self.time.day()
311    }
312
313    /// Get the week number 1 ~ 7
314    pub fn day(&self) -> Weekday {
315        self.time.weekday()
316    }
317
318    /// Get the month number 1 ~ 366
319    pub fn day_of_year(&self) -> u32 {
320        self.time.ordinal()
321    }
322
323    /// Get the week number 1 ~ 53
324    pub fn week_of_year(&self) -> u32 {
325        self.time.iso_week().week()
326    }
327
328    /// Get the month number 1 ~ 12
329    pub fn month_of_year(&self) -> u32 {
330        self.time.month()
331    }
332}
333
334/// Trait for displaying time in various formats.
335pub trait DisplayTime {
336    /// Formats to array string. [ 2019, 0, 25, 0, 0, 0, 0 ]
337    fn to_array(&self) -> String;
338
339    /// Formats to iso string. "2019-01-25T02:00:00.000Z"
340    fn to_iso(&self) -> String;
341
342    /// Formats to utc string. "2019-01-25 00:00:00 +00:00"
343    fn to_utc(&self) -> String;
344
345    /// Formats to gmt string. "Fri, 25 Jan 2019 00:00:00 GMT"
346    fn to_gmt(&self) -> String;
347
348    /// Converts the time to a timestamp in seconds.
349    fn to_timestamp(&self) -> i64;
350}
351
352impl DisplayTime for Dayjs {
353    /// Formats the date time to an array string.
354    fn to_array(&self) -> String {
355        let dt = self.time;
356        format!(
357            "[ {}, {}, {}, {}, {}, {}, {} ]",
358            dt.year(),
359            dt.month0(),
360            dt.day0(),
361            dt.hour(),
362            dt.minute(),
363            dt.second(),
364            dt.nanosecond() / 1_000_000
365        )
366    }
367
368    /// Formats the date time to an ISO 8601 string.
369    fn to_iso(&self) -> String {
370        let dt = self.time;
371        format!(
372            "{:04}-{:02}-{:02}T{:02}:{:02}:{:02}.{:03}Z",
373            dt.year(),
374            dt.month(),
375            dt.day(),
376            dt.hour(),
377            dt.minute(),
378            dt.second(),
379            dt.nanosecond() / 1_000_000
380        )
381    }
382
383    /// Formats the date time to a UTC string.
384    fn to_utc(&self) -> String {
385        let dt = self.time;
386        format!(
387            "{:04}-{:02}-{:02} {:02}:{:02}:{:02} +00:00",
388            dt.year(),
389            dt.month(),
390            dt.day(),
391            dt.hour(),
392            dt.minute(),
393            dt.second()
394        )
395    }
396
397    /// Formats the date time to a GMT string.
398    fn to_gmt(&self) -> String {
399        let dt = self.time;
400        format!(
401            "{}, {:02} {} {:04} {:02}:{:02}:{:02} GMT",
402            dt.weekday(),
403            dt.day(),
404            dt.format("%b"),
405            dt.year(),
406            dt.hour(),
407            dt.minute(),
408            dt.second()
409        )
410    }
411
412    /// Converts the time to a timestamp in seconds.
413    fn to_timestamp(&self) -> i64 {
414        let dt = self.time;
415        dt.timestamp()
416    }
417}
418
419/// Trait for operations on time, such as adding or subtracting durations.
420pub trait OperationTime {
421    /// Add a duration to the current time.
422    fn add(&mut self, timestamp: i32);
423
424    /// Add a duration to the current time in year.
425    fn add_years(&mut self, years: i32);
426
427    /// Add a duration to the current time in month.
428    fn add_months(&mut self, months: i32);
429
430    /// Add a duration to the current time in week.
431    fn add_weeks(&mut self, weeks: i32);
432
433    /// Add a duration to the current time in day.
434    fn add_days(&mut self, days: i32);
435
436    /// Add a duration to the current time in hours.
437    fn add_hours(&mut self, hours: i32);
438
439    /// Add a duration to the current time in minutes.
440    fn add_minutes(&mut self, minutes: i32);
441
442    /// Add a duration to the current time in seconds.
443    fn add_seconds(&mut self, seconds: i32);
444
445    /// Add a duration to the current time in milliseconds.
446    fn add_milliseconds(&mut self, milliseconds: i32);
447
448    /// Subtract a duration from the current time.
449    fn subtract(&mut self, timestamp: i32);
450
451    /// Subtract a duration from the current time in year.
452    fn subtract_years(&mut self, years: i32);
453
454    /// Subtract a duration from the current time in month.
455    fn subtract_months(&mut self, months: i32);
456
457    /// Subtract a duration from the current time in week.
458    fn subtract_weeks(&mut self, weeks: i32);
459
460    /// Subtract a duration from the current time in day.
461    fn subtract_days(&mut self, days: i32);
462
463    /// Subtract a duration from the current time in hours.
464    fn subtract_hours(&mut self, hours: i32);
465
466    /// Subtract a duration from the current time in minutes.
467    fn subtract_minutes(&mut self, minutes: i32);
468
469    /// Subtract a duration from the current time in seconds.
470    fn subtract_seconds(&mut self, seconds: i32);
471
472    /// Subtract a duration from the current time in milliseconds.
473    fn subtract_milliseconds(&mut self, milliseconds: i32);
474}
475
476impl OperationTime for Dayjs {
477    /// Add a duration to the current time.
478    fn add(&mut self, timestamp: i32) {
479        let dt = self.time + chrono::Duration::seconds(timestamp as i64);
480        self.time = dt;
481    }
482
483    /// Add a duration to the current time in year.
484    fn add_years(&mut self, years: i32) {
485        let dt = self.time + chrono::Duration::days((years * 365) as i64);
486        self.time = dt;
487    }
488
489    /// Add a duration to the current time in month.
490    fn add_months(&mut self, months: i32) {
491        let mut dt = self.time;
492        for _ in 0..months {
493            dt = dt.with_month(dt.month() + 1).unwrap_or(dt);
494        }
495        self.time = dt;
496    }
497
498    /// Add a duration to the current time in week.
499    fn add_weeks(&mut self, weeks: i32) {
500        let dt = self.time + chrono::Duration::weeks(weeks as i64);
501        self.time = dt;
502    }
503
504    /// Add a duration to the current time in day.
505    fn add_days(&mut self, days: i32) {
506        let dt = self.time + chrono::Duration::days(days as i64);
507        self.time = dt;
508    }
509
510    /// Add a duration to the current time in hours.
511    fn add_hours(&mut self, hours: i32) {
512        let dt = self.time + chrono::Duration::hours(hours as i64);
513        self.time = dt;
514    }
515
516    /// Add a duration to the current time in minutes.
517    fn add_minutes(&mut self, minutes: i32) {
518        let dt = self.time + chrono::Duration::minutes(minutes as i64);
519        self.time = dt;
520    }
521
522    /// Add a duration to the current time in seconds.
523    fn add_seconds(&mut self, seconds: i32) {
524        let dt = self.time + chrono::Duration::seconds(seconds as i64);
525        self.time = dt;
526    }
527
528    /// Add a duration to the current time in milliseconds.
529    fn add_milliseconds(&mut self, milliseconds: i32) {
530        let dt = self.time + chrono::Duration::milliseconds(milliseconds as i64);
531        self.time = dt;
532    }
533
534    /// Subtract a duration from the current time.
535    fn subtract(&mut self, timestamp: i32) {
536        let dt = self.time - chrono::Duration::seconds(timestamp as i64);
537        self.time = dt;
538    }
539
540    /// Subtract a duration from the current time in year.
541    fn subtract_years(&mut self, years: i32) {
542        let dt = self.time - chrono::Duration::days(years as i64);
543        self.time = dt;
544    }
545
546    /// Subtract a duration from the current time in month.
547    fn subtract_months(&mut self, months: i32) {
548        let mut dt = self.time;
549        for _ in 0..months {
550            dt = dt.with_month(dt.month() - 1).unwrap_or(dt);
551        }
552        self.time = dt;
553    }
554
555    /// Subtract a duration from the current time in week.
556    fn subtract_weeks(&mut self, weeks: i32) {
557        let dt = self.time - chrono::Duration::weeks(weeks as i64);
558        self.time = dt;
559    }
560
561    /// Subtract a duration from the current time in day.
562    fn subtract_days(&mut self, days: i32) {
563        let dt = self.time - chrono::Duration::days(days as i64);
564        self.time = dt;
565    }
566
567    /// Subtract a duration from the current time in hours.
568    fn subtract_hours(&mut self, hours: i32) {
569        let dt = self.time - chrono::Duration::hours(hours as i64);
570        self.time = dt;
571    }
572
573    /// Subtract a duration from the current time in minutes.
574    fn subtract_minutes(&mut self, minutes: i32) {
575        let dt = self.time - chrono::Duration::minutes(minutes as i64);
576        self.time = dt;
577    }
578
579    /// Subtract a duration from the current time in seconds.
580    fn subtract_seconds(&mut self, seconds: i32) {
581        let dt = self.time - chrono::Duration::seconds(seconds as i64);
582        self.time = dt;
583    }
584
585    /// Subtract a duration from the current time in milliseconds.
586    fn subtract_milliseconds(&mut self, milliseconds: i32) {
587        let dt = self.time - chrono::Duration::milliseconds(milliseconds as i64);
588        self.time = dt;
589    }
590}