dtg_lib/
lib.rs

1#![doc = include_str!("../README.md")]
2
3//--------------------------------------------------------------------------------------------------
4// Crates
5
6use lazy_static::lazy_static;
7use std::collections::HashMap;
8
9pub use jiff::{
10    civil::{Date, Time},
11    tz::TimeZone,
12    Span, Timestamp,
13};
14
15//--------------------------------------------------------------------------------------------------
16// Constants / lazy static
17
18const DEFAULT: &str = "%a %d %b %Y %H:%M:%S %Z";
19const EPOCH: &str = "%s.%f";
20const RFC_3339: &str = "%Y-%m-%dT%H:%M:%SZ";
21
22lazy_static! {
23    #[rustfmt::skip]
24    static ref ITOC: HashMap<i8, char> = {
25        [
26            (0, '0'), (10, 'A'), (20, 'K'), (30, 'U'), (40, 'e'), (50, 'o'),
27            (1, '1'), (11, 'B'), (21, 'L'), (31, 'V'), (41, 'f'), (51, 'p'),
28            (2, '2'), (12, 'C'), (22, 'M'), (32, 'W'), (42, 'g'), (52, 'q'),
29            (3, '3'), (13, 'D'), (23, 'N'), (33, 'X'), (43, 'h'), (53, 'r'),
30            (4, '4'), (14, 'E'), (24, 'O'), (34, 'Y'), (44, 'i'), (54, 's'),
31            (5, '5'), (15, 'F'), (25, 'P'), (35, 'Z'), (45, 'j'), (55, 't'),
32            (6, '6'), (16, 'G'), (26, 'Q'), (36, 'a'), (46, 'k'), (56, 'u'),
33            (7, '7'), (17, 'H'), (27, 'R'), (37, 'b'), (47, 'l'), (57, 'v'),
34            (8, '8'), (18, 'I'), (28, 'S'), (38, 'c'), (48, 'm'), (58, 'w'),
35            (9, '9'), (19, 'J'), (29, 'T'), (39, 'd'), (49, 'n'), (59, 'x'),
36        ]
37        .iter()
38        .cloned()
39        .collect()
40    };
41
42    static ref CTOI: HashMap<char, i8> = {
43        ITOC.iter().map(|(i, c)| (*c, *i)).collect()
44    };
45}
46
47//--------------------------------------------------------------------------------------------------
48// DtgError struct
49
50/**
51Custom error
52
53* 101: Invalid timestamp
54* 102: Invalid timezone
55* 103: Failed to get local timezone
56* 104: Failed to get elapsed time
57*/
58#[derive(Debug)]
59pub struct DtgError {
60    pub code: usize,
61    pub message: String,
62}
63
64impl DtgError {
65    /// Create error
66    pub fn new(message: &str, code: usize) -> DtgError {
67        DtgError {
68            code,
69            message: message.to_string(),
70        }
71    }
72}
73
74impl std::fmt::Display for DtgError {
75    fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
76        write!(f, "{}", self.message)
77    }
78}
79
80impl std::cmp::PartialEq for DtgError {
81    fn eq(&self, other: &DtgError) -> bool {
82        self.code == other.code && self.message == other.message
83    }
84}
85
86//--------------------------------------------------------------------------------------------------
87// Dtg struct
88
89/**
90Date time group
91*/
92#[derive(Debug)]
93pub struct Dtg {
94    dt: Timestamp,
95}
96
97impl Dtg {
98    /**
99    Create a current [Dtg]
100    */
101    pub fn now() -> Dtg {
102        Dtg {
103            dt: Timestamp::now(),
104        }
105    }
106
107    /**
108    Create a [Dtg] from a string timestamp
109
110    ```
111    use dtg_lib::{Dtg, Timestamp};
112
113    assert_eq!(
114        Dtg::from("1658448142").unwrap(),
115        Dtg::from_dt(&Timestamp::new(1658448142, 0).unwrap()),
116    );
117    assert_eq!(
118        Dtg::from("1658448142.936196858").unwrap(),
119        Dtg::from_dt(&Timestamp::new(1658448142, 936196858).unwrap()),
120    );
121    ```
122    */
123    pub fn from(s: &str) -> Result<Dtg, DtgError> {
124        let mut x = s.split('.');
125        if let Some(seconds) = x.next() {
126            if let Ok(seconds) = seconds.parse::<i64>() {
127                if seconds <= 8210298412799 {
128                    if let Some(nanoseconds) = x.next() {
129                        let mut nanoseconds = nanoseconds.to_string();
130                        while nanoseconds.len() < 9 {
131                            nanoseconds.push('0');
132                        }
133                        if let Ok(nanoseconds) = nanoseconds[..9].parse::<i32>() {
134                            if let Ok(dt) = Timestamp::new(seconds, nanoseconds) {
135                                return Ok(Dtg { dt });
136                            }
137                        }
138                    } else if let Ok(dt) = Timestamp::new(seconds, 0) {
139                        return Ok(Dtg { dt });
140                    }
141                }
142            }
143        }
144        Err(DtgError::new(&format!("Invalid timestamp: `{s}`"), 101))
145    }
146
147    /**
148    Create a [Dtg] from separate year, month, day, hour, minute, second values
149
150    ```
151    use dtg_lib::{Dtg, Format};
152
153    let dtg = Dtg::from_ymd_hms(2022, 7, 22, 0, 2, 22).unwrap();
154
155    assert_eq!(dtg.format(&Some(Format::custom("%s")), &None), "1658448142");
156    assert_eq!(dtg.rfc_3339(), "2022-07-22T00:02:22Z");
157    ```
158    */
159    pub fn from_ymd_hms(
160        year: i16,
161        month: i8,
162        day: i8,
163        hour: i8,
164        minute: i8,
165        second: i8,
166    ) -> Result<Dtg, DtgError> {
167        if let Ok(dt) = Date::new(year, month, day)
168            .and_then(|d| Ok(d.to_datetime(Time::new(hour, minute, second, 0)?)))
169            .and_then(|dt| dt.to_zoned(TimeZone::UTC))
170            .map(|zdt| zdt.timestamp())
171        {
172            return Ok(Dtg { dt });
173        }
174
175        Err(DtgError::new(
176            &format!(
177                "Invalid timestamp: `{year}-{month:02}-{day:02}T{hour:02}:{minute:02}:{second:02}Z`",
178            ),
179            101,
180        ))
181    }
182
183    /**
184    Create a [Dtg] from an "x" format timestamp
185
186    ```
187    use dtg_lib::{Dtg, Format};
188
189    let dtg = Dtg::from_x("Xg6L02M").unwrap();
190
191    assert_eq!(dtg.format(&Some(Format::custom("%s")), &None), "1658448142");
192    assert_eq!(dtg.rfc_3339(), "2022-07-22T00:02:22Z");
193    ```
194    */
195    pub fn from_x(s: &str) -> Result<Dtg, DtgError> {
196        let mut chars = s.chars().rev();
197
198        fn next(chars: &mut std::iter::Rev<std::str::Chars>) -> Option<i8> {
199            if let Some(x) = chars.next() {
200                CTOI.get(&x).copied()
201            } else {
202                None
203            }
204        }
205
206        let second = next(&mut chars).expect("second");
207        let minute = next(&mut chars).expect("minute");
208        let hour = next(&mut chars).expect("hour");
209        let day = next(&mut chars).expect("day") + 1;
210        let month = next(&mut chars).expect("month") + 1;
211
212        let mut year = 0;
213        for (exp, c) in chars.enumerate() {
214            year += (*CTOI.get(&c).unwrap() as i16) * 60_i16.pow(exp as u32);
215        }
216
217        Dtg::from_ymd_hms(year, month, day, hour, minute, second)
218            .map_err(|e| DtgError::new(&format!("Invalid timestamp: `{s}`: {e}"), 101))
219    }
220
221    /**
222    Create a [Dtg] from a [`Timestamp`]
223
224    ```
225    use jiff::Timestamp;
226    use dtg_lib::Dtg;
227
228    assert_eq!(
229        Dtg::from("1658448142").unwrap(),
230        Dtg::from_dt(&Timestamp::new(1658448142, 0).unwrap()),
231    );
232    ```
233    */
234    pub fn from_dt(dt: &Timestamp) -> Dtg {
235        Dtg { dt: *dt }
236    }
237
238    /**
239    Format as a string
240
241    ```
242    use dtg_lib::{tz, Dtg};
243
244    let dtg = Dtg::from("1658448142").unwrap();
245    let default_utc = "Fri 22 Jul 2022 00:02:22 UTC";
246    let default_mt = "Thu 21 Jul 2022 18:02:22 MDT";
247
248    assert_eq!(dtg.default(&None), default_utc);
249    assert_eq!(dtg.default(&tz("UTC").ok()), default_utc);
250    assert_eq!(dtg.default(&tz("MST7MDT").ok()), default_mt);
251    ```
252    */
253    pub fn default(&self, tz: &Option<TimeZone>) -> String {
254        self.format(&Some(Format::default()), tz)
255    }
256
257    /**
258    Format as an RFC 3339 string
259
260    ```
261    use dtg_lib::Dtg;
262
263    let dtg = Dtg::from("1658448142").unwrap();
264
265    assert_eq!(dtg.rfc_3339(), "2022-07-22T00:02:22Z");
266    ```
267    */
268    pub fn rfc_3339(&self) -> String {
269        self.format(&None, &None)
270    }
271
272    /**
273    Format as "x" format
274
275    ```
276    use dtg_lib::Dtg;
277
278    let dtg = Dtg::from("1658448142").unwrap();
279
280    assert_eq!(dtg.x_format(), "Xg6L02M");
281    ```
282    */
283    pub fn x_format(&self) -> String {
284        self.format(&Some(Format::X), &None)
285    }
286
287    /**
288    Format as "a" format
289
290    ```
291    use dtg_lib::{tz, Dtg};
292
293    let dtg = Dtg::from("1658448142").unwrap();
294    let a_utc = "\
295    1658448142.000000000
296    2022-07-22T00:02:22Z
297    Fri 22 Jul 2022 00:02:22 UTC
298    Fri 22 Jul 2022 00:02:22 UTC";
299    let a_mt = "\
300    1658448142.000000000
301    2022-07-22T00:02:22Z
302    Fri 22 Jul 2022 00:02:22 UTC
303    Thu 21 Jul 2022 18:02:22 MDT";
304
305    assert_eq!(dtg.a_format(&None), a_utc);
306    // assert_eq!(dtg.a_format(&tz("UTC").ok()), a_utc);
307    // assert_eq!(dtg.a_format(&tz("MST7MDT").ok()), a_mt);
308    ```
309    */
310    pub fn a_format(&self, tz: &Option<TimeZone>) -> String {
311        self.format(&Some(Format::A), tz)
312    }
313
314    /**
315    Format like a binary clock using the Braille Patterns Unicode Block and `|` separators
316
317    ```
318    use dtg_lib::Dtg;
319
320    let dtg = Dtg::from("1658448142").unwrap();
321
322    assert_eq!(dtg.bcd_format(), "⠄⠤|⢰|⠤|⠀|⠠|⠤"); // 2022|07|22|00|02|22
323    ```
324    */
325    pub fn bcd_format(&self) -> String {
326        self.format(&Some(Format::BCD), &None)
327    }
328
329    /**
330    Format as a string with format and timezone
331
332    ```
333    use dtg_lib::{tz, Dtg, Format};
334
335    let dtg = Dtg::from("1658448142").unwrap();
336
337    assert_eq!(
338        dtg.format(&None, &None),
339        "2022-07-22T00:02:22Z",
340    );
341    assert_eq!(
342        dtg.format(&Some(Format::X), &None),
343        "Xg6L02M",
344    );
345
346    let a_fmt = Some(Format::custom("%A"));
347
348    assert_eq!(
349        dtg.format(&a_fmt, &None),
350        "Friday",
351    );
352    assert_eq!(
353        dtg.format(&a_fmt, &tz("MST7MDT").ok()),
354        "Thursday",
355    );
356    ```
357    */
358    pub fn format(&self, fmt: &Option<Format>, tz: &Option<TimeZone>) -> String {
359        let tz = tz.clone().unwrap_or(TimeZone::UTC);
360        match fmt {
361            Some(fmt) => fmt.with(&self.dt, &tz),
362            None => Format::Custom(RFC_3339.to_string()).with(&self.dt, &tz),
363        }
364    }
365
366    pub fn elapsed(&self) -> Result<Duration, DtgError> {
367        match self.dt.until(Timestamp::now()) {
368            Ok(d) => Ok(Duration::new(d)),
369            Err(_) => Err(DtgError::new("Failed to get elapsed time", 104)),
370        }
371    }
372}
373
374impl std::cmp::PartialEq for Dtg {
375    fn eq(&self, other: &Dtg) -> bool {
376        self.dt == other.dt
377    }
378}
379
380//--------------------------------------------------------------------------------------------------
381// Duration
382
383pub struct Duration {
384    d: Span,
385}
386
387impl Duration {
388    fn new(d: Span) -> Duration {
389        Duration { d }
390    }
391}
392
393impl std::fmt::Display for Duration {
394    fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
395        fn inner(n: i64, abbr: &str) -> Option<String> {
396            if n != 0 {
397                Some(format!("{n}{abbr}"))
398            } else if abbr == "s" {
399                Some(String::from("0s"))
400            } else {
401                None
402            }
403        }
404
405        write!(
406            f,
407            "{}",
408            [
409                (self.d.get_days() as i64, "d"),
410                (self.d.get_hours() as i64, "h"),
411                (self.d.get_minutes(), "m"),
412                (self.d.get_seconds(), "s"),
413            ]
414            .iter()
415            .filter_map(|x| inner(x.0, x.1))
416            .collect::<Vec<String>>()
417            .join(""),
418        )
419    }
420}
421
422//--------------------------------------------------------------------------------------------------
423// Format enum
424
425/**
426Format
427
428# "a" format
429
430Four newline-separated timestamps with the epoch time in fractional seconds, RFC 3339 format / UTC,
431default format / UTC, and default format / local
432
433```text
434%s.%f
435%Y-%m-%dT%H:%M:%SZ
436%a %d %b %Y %H:%M:%S %Z # UTC
437%a %d %b %Y %H:%M:%S %Z # Specified or local timezone
438```
439
440# "x" format
441
442Novel UTC / base 60 encoding
443
444```text
4450* 0 1 2 3 4 5 6 7 8 9
4461* A B C D E F G H I J
4472* K L M N O P Q R S T
4483* U V W X Y Z a b c d
4494* e f g h i j k l m n
4505* o p q r s t u v w x
451```
452
453Field  | Values           | Result
454-------|------------------|----------
455Year   | 2020 => 33*60+40 | Xe
456Month  | Jan-Dec => 0-11  | 0-B
457Day    | 0-27/28/29/30    | 0-R/S/T/U
458Hour   | 0-23             | 0-N
459Minute | 0-59             | 0-x
460Second | 0-59             | 0-x
461
462See also [Dtg::from_x]
463
464# Custom format
465
466See also [Dtg::format]
467
468## Date specifiers
469
470Spec. | Example       | Description
471------|---------------|----------------------------------------------------------------------------
472`%Y`  | `2001`        | The full proleptic Gregorian year, zero-padded to 4 digits.
473`%C`  | `20`          | The proleptic Gregorian year divided by 100, zero-padded to 2 digits.
474`%y`  | `01`          | The proleptic Gregorian year modulo 100, zero-padded to 2 digits.
475`%m`  | `07`          | Month number (01--12), zero-padded to 2 digits.
476`%b`  | `Jul`         | Abbreviated month name. Always 3 letters.
477`%B`  | `July`        | Full month name. Also accepts corresponding abbreviation in parsing.
478`%h`  | `Jul`         | Same as `%b`.
479`%d`  | `08`          | Day number (01--31), zero-padded to 2 digits.
480`%e`  | ` 8`          | Same as `%d` but space-padded. Same as `%_d`.
481`%a`  | `Sun`         | Abbreviated weekday name. Always 3 letters.
482`%A`  | `Sunday`      | Full weekday name. Also accepts corresponding abbreviation in parsing.
483`%w`  | `0`           | Sunday = 0, Monday = 1, ..., Saturday = 6.
484`%u`  | `7`           | Monday = 1, Tuesday = 2, ..., Sunday = 7. (ISO 8601)
485`%U`  | `28`          | Week number starting with Sunday (00--53), zero-padded to 2 digits.
486`%W`  | `27`          | Same as `%U`, but week 1 starts with the first Monday in that year instead.
487`%G`  | `2001`        | Same as `%Y` but uses the year number in ISO 8601 week date.
488`%g`  | `01`          | Same as `%y` but uses the year number in ISO 8601 week date.
489`%V`  | `27`          | Same as `%U` but uses the week number in ISO 8601 week date (01--53).
490`%j`  | `189`         | Day of the year (001--366), zero-padded to 3 digits.
491`%D`  | `07/08/01`    | Month-day-year format. Same as `%m/%d/%y`.
492`%x`  | `07/08/01`    | Locale's date representation (e.g., 12/31/99).
493`%F`  | `2001-07-08`  | Year-month-day format (ISO 8601). Same as `%Y-%m-%d`.
494`%v`  | ` 8-Jul-2001` | Day-month-year format. Same as `%e-%b-%Y`.
495
496## Time specifiers
497
498Spec.  | Example       | Description
499-------|---------------|----------------------------------------------------------------------
500`%H`   | `00`          | Hour number (00--23), zero-padded to 2 digits.
501`%k`   | ` 0`          | Same as `%H` but space-padded. Same as `%_H`.
502`%I`   | `12`          | Hour number in 12-hour clocks (01--12), zero-padded to 2 digits.
503`%l`   | `12`          | Same as `%I` but space-padded. Same as `%_I`.
504`%P`   | `am`          | `am` or `pm` in 12-hour clocks.
505`%p`   | `AM`          | `AM` or `PM` in 12-hour clocks.
506`%M`   | `34`          | Minute number (00--59), zero-padded to 2 digits.
507`%S`   | `60`          | Second number (00--60), zero-padded to 2 digits.
508`%f`   | `026490000`   | The fractional seconds (in nanoseconds) since last whole second.
509`%.f`  | `.026490`     | Similar to `.%f` but left-aligned. These all consume the leading dot.
510`%.3f` | `.026`        | Similar to `.%f` but left-aligned but fixed to a length of 3.
511`%.6f` | `.026490`     | Similar to `.%f` but left-aligned but fixed to a length of 6.
512`%.9f` | `.026490000`  | Similar to `.%f` but left-aligned but fixed to a length of 9.
513`%3f`  | `026`         | Similar to `%.3f` but without the leading dot.
514`%6f`  | `026490`      | Similar to `%.6f` but without the leading dot.
515`%9f`  | `026490000`   | Similar to `%.9f` but without the leading dot.
516`%R`   | `00:34`       | Hour-minute format. Same as `%H:%M`.
517`%T`   | `00:34:60`    | Hour-minute-second format. Same as `%H:%M:%S`.
518`%X`   | `00:34:60`    | Locale's time representation (e.g., 23:13:48).
519`%r`   | `12:34:60 AM` | Hour-minute-second format in 12-hour clocks. Same as `%I:%M:%S %p`.
520
521## Time zone specifiers
522
523Spec. | Example  | Description
524------|----------|--------------------------------------------------------------------------
525`%Z`  | `ACST`   | Local time zone name. Skips all non-whitespace characters during parsing.
526`%z`  | `+0930`  | Offset from the local time to UTC (with UTC being `+0000`).
527`%:z` | `+09:30` | Same as `%z` but with a colon.
528`%#z` | `+09`    | *Parsing only:* Same as `%z` but allows minutes to be missing or present.
529
530## Date & time specifiers
531
532Spec. | Example                            | Description
533------|------------------------------------|------------------------------------------------------------------
534`%c`  | `Sun Jul  8 00:34:60 2001`         | Locale's date and time (e.g., Thu Mar  3 23:05:25 2005).
535`%+`  | `2001-07-08T00:34:60.026490+09:30` | ISO 8601 / RFC 3339 date & time format.
536`%s`  | `994518299`                        | UNIX timestamp, the number of seconds since 1970-01-01 00:00 UTC.
537
538## Special specifiers
539
540Spec. | Description
541------|------------------------
542`%t`  | Literal tab (`\t`).
543`%n`  | Literal newline (`\n`).
544`%%`  | Literal percent sign.
545*/
546#[derive(Clone)]
547pub enum Format {
548    A,
549    BCD,
550    X,
551    Custom(String),
552}
553
554impl Format {
555    /**
556    Create a default [Format]
557    */
558    pub fn new() -> Format {
559        Format::Custom(DEFAULT.to_string())
560    }
561
562    /**
563    Create an RFC 3339 [Format]
564    */
565    pub fn rfc_3339() -> Format {
566        Format::Custom(RFC_3339.to_string())
567    }
568
569    /**
570    Create a custom [Format]
571    */
572    pub fn custom(s: &str) -> Format {
573        Format::Custom(s.to_string())
574    }
575
576    /**
577    Format a [Timestamp] with a timezone
578    */
579    fn with(&self, dt: &Timestamp, tz: &TimeZone) -> String {
580        match self {
581            Format::Custom(f) => {
582                if f == "%s" {
583                    format!("{}", dt.as_second())
584                } else if f == EPOCH {
585                    format!("{}.{}", dt.as_second(), dt.subsec_nanosecond())
586                } else if f == RFC_3339 {
587                    dt.strftime(f).to_string()
588                } else {
589                    let mut f = f.clone();
590                    if f.contains("%n") {
591                        f = f.replace("%n", "\n");
592                    }
593                    if f.contains("%f") {
594                        f = f.replace("%f", &format!("{:09}", dt.subsec_nanosecond()));
595                    }
596                    dt.to_zoned(tz.clone()).strftime(&f).to_string()
597                }
598            }
599            Format::A => {
600                let epoch_s = dt.as_second() as i128;
601                let epoch_ns = dt.as_nanosecond() - epoch_s * 1_000_000_000;
602                let epoch = format!("{epoch_s}.{epoch_ns:09}");
603                let rfc = dt.strftime(RFC_3339).to_string();
604                let default = dt.to_zoned(TimeZone::UTC).strftime(DEFAULT).to_string();
605                let zoned_default = dt.to_zoned(tz.clone()).strftime(DEFAULT).to_string();
606                [epoch, rfc, default, zoned_default].join("\n")
607            }
608            Format::X => self.x(dt),
609            Format::BCD => self.bcd(dt, tz),
610        }
611    }
612
613    /**
614    Format a [Timestamp] with "x" format
615    */
616    fn x(&self, dt: &Timestamp) -> String {
617        let dt = dt.to_zoned(TimeZone::UTC);
618        let mut year = dt.year();
619        let mut y = vec![];
620        if year == 0 {
621            y.push(0);
622        }
623        while year > 0 {
624            y.push((year % 60) as i8);
625            year /= 60;
626        }
627        let year = y
628            .iter()
629            .rev()
630            .map(|x| ITOC.get(x).unwrap())
631            .collect::<String>();
632        let mon = ITOC.get(&(dt.month() - 1)).unwrap();
633        let day = ITOC.get(&(dt.day() - 1)).unwrap();
634        let h = ITOC.get(&(dt.hour())).unwrap();
635        let m = ITOC.get(&(dt.minute())).unwrap();
636        let s = ITOC.get(&(dt.second())).unwrap();
637        format!("{year}{mon}{day}{h}{m}{s}")
638    }
639
640    /**
641    Format a [Timestamp] like a binary clock using the Braille Patterns Unicode Block and `|`
642    separators
643    */
644    fn bcd(&self, dt: &Timestamp, tz: &TimeZone) -> String {
645        let dt = dt.to_zoned(tz.clone());
646        let yyyy = dt.year();
647        let (mut r, yyyy) = if yyyy < 0 {
648            (String::from("-"), (-yyyy) as u32)
649        } else {
650            (String::new(), yyyy as u32)
651        };
652        let cc = (yyyy / 100) as u8;
653        let yy = (yyyy - yyyy / 100 * 100) as u8;
654        for (i, n) in [
655            cc,
656            yy,
657            dt.month() as u8,
658            dt.day() as u8,
659            dt.hour() as u8,
660            dt.minute() as u8,
661            dt.second() as u8,
662        ]
663        .iter()
664        .enumerate()
665        {
666            if i >= 2 {
667                r.push('|');
668            }
669            r.push(bbd_lib::encode_bcd(*n));
670        }
671        r
672    }
673}
674
675impl Default for Format {
676    fn default() -> Format {
677        Format::new()
678    }
679}
680
681//--------------------------------------------------------------------------------------------------
682// Functions
683
684/**
685Get a timezone by name
686
687```
688use dtg_lib::{tz, DtgError, TimeZone};
689
690assert_eq!(tz("UTC"), Ok(TimeZone::UTC));
691
692assert_eq!(tz("nonexistent"), Err(DtgError::new("Invalid timezone: `nonexistent`", 102)));
693```
694
695Timezones:
696
697```text
698Africa/Abidjan
699Africa/Accra
700Africa/Addis_Ababa
701Africa/Algiers
702Africa/Asmara
703Africa/Asmera
704Africa/Bamako
705Africa/Bangui
706Africa/Banjul
707Africa/Bissau
708Africa/Blantyre
709Africa/Brazzaville
710Africa/Bujumbura
711Africa/Cairo
712Africa/Casablanca
713Africa/Ceuta
714Africa/Conakry
715Africa/Dakar
716Africa/Dar_es_Salaam
717Africa/Djibouti
718Africa/Douala
719Africa/El_Aaiun
720Africa/Freetown
721Africa/Gaborone
722Africa/Harare
723Africa/Johannesburg
724Africa/Juba
725Africa/Kampala
726Africa/Khartoum
727Africa/Kigali
728Africa/Kinshasa
729Africa/Lagos
730Africa/Libreville
731Africa/Lome
732Africa/Luanda
733Africa/Lubumbashi
734Africa/Lusaka
735Africa/Malabo
736Africa/Maputo
737Africa/Maseru
738Africa/Mbabane
739Africa/Mogadishu
740Africa/Monrovia
741Africa/Nairobi
742Africa/Ndjamena
743Africa/Niamey
744Africa/Nouakchott
745Africa/Ouagadougou
746Africa/Porto-Novo
747Africa/Sao_Tome
748Africa/Timbuktu
749Africa/Tripoli
750Africa/Tunis
751Africa/Windhoek
752America/Adak
753America/Anchorage
754America/Anguilla
755America/Antigua
756America/Araguaina
757America/Argentina/Buenos_Aires
758America/Argentina/Catamarca
759America/Argentina/ComodRivadavia
760America/Argentina/Cordoba
761America/Argentina/Jujuy
762America/Argentina/La_Rioja
763America/Argentina/Mendoza
764America/Argentina/Rio_Gallegos
765America/Argentina/Salta
766America/Argentina/San_Juan
767America/Argentina/San_Luis
768America/Argentina/Tucuman
769America/Argentina/Ushuaia
770America/Aruba
771America/Asuncion
772America/Atikokan
773America/Atka
774America/Bahia
775America/Bahia_Banderas
776America/Barbados
777America/Belem
778America/Belize
779America/Blanc-Sablon
780America/Boa_Vista
781America/Bogota
782America/Boise
783America/Buenos_Aires
784America/Cambridge_Bay
785America/Campo_Grande
786America/Cancun
787America/Caracas
788America/Catamarca
789America/Cayenne
790America/Cayman
791America/Chicago
792America/Chihuahua
793America/Ciudad_Juarez
794America/Coral_Harbour
795America/Cordoba
796America/Costa_Rica
797America/Creston
798America/Cuiaba
799America/Curacao
800America/Danmarkshavn
801America/Dawson
802America/Dawson_Creek
803America/Denver
804America/Detroit
805America/Dominica
806America/Edmonton
807America/Eirunepe
808America/El_Salvador
809America/Ensenada
810America/Fort_Nelson
811America/Fort_Wayne
812America/Fortaleza
813America/Glace_Bay
814America/Godthab
815America/Goose_Bay
816America/Grand_Turk
817America/Grenada
818America/Guadeloupe
819America/Guatemala
820America/Guayaquil
821America/Guyana
822America/Halifax
823America/Havana
824America/Hermosillo
825America/Indiana/Indianapolis
826America/Indiana/Knox
827America/Indiana/Marengo
828America/Indiana/Petersburg
829America/Indiana/Tell_City
830America/Indiana/Vevay
831America/Indiana/Vincennes
832America/Indiana/Winamac
833America/Indianapolis
834America/Inuvik
835America/Iqaluit
836America/Jamaica
837America/Jujuy
838America/Juneau
839America/Kentucky/Louisville
840America/Kentucky/Monticello
841America/Knox_IN
842America/Kralendijk
843America/La_Paz
844America/Lima
845America/Los_Angeles
846America/Louisville
847America/Lower_Princes
848America/Maceio
849America/Managua
850America/Manaus
851America/Marigot
852America/Martinique
853America/Matamoros
854America/Mazatlan
855America/Mendoza
856America/Menominee
857America/Merida
858America/Metlakatla
859America/Mexico_City
860America/Miquelon
861America/Moncton
862America/Monterrey
863America/Montevideo
864America/Montreal
865America/Montserrat
866America/Nassau
867America/New_York
868America/Nipigon
869America/Nome
870America/Noronha
871America/North_Dakota/Beulah
872America/North_Dakota/Center
873America/North_Dakota/New_Salem
874America/Nuuk
875America/Ojinaga
876America/Panama
877America/Pangnirtung
878America/Paramaribo
879America/Phoenix
880America/Port-au-Prince
881America/Port_of_Spain
882America/Porto_Acre
883America/Porto_Velho
884America/Puerto_Rico
885America/Punta_Arenas
886America/Rainy_River
887America/Rankin_Inlet
888America/Recife
889America/Regina
890America/Resolute
891America/Rio_Branco
892America/Rosario
893America/Santa_Isabel
894America/Santarem
895America/Santiago
896America/Santo_Domingo
897America/Sao_Paulo
898America/Scoresbysund
899America/Shiprock
900America/Sitka
901America/St_Barthelemy
902America/St_Johns
903America/St_Kitts
904America/St_Lucia
905America/St_Thomas
906America/St_Vincent
907America/Swift_Current
908America/Tegucigalpa
909America/Thule
910America/Thunder_Bay
911America/Tijuana
912America/Toronto
913America/Tortola
914America/Vancouver
915America/Virgin
916America/Whitehorse
917America/Winnipeg
918America/Yakutat
919America/Yellowknife
920Antarctica/Casey
921Antarctica/Davis
922Antarctica/DumontDUrville
923Antarctica/Macquarie
924Antarctica/Mawson
925Antarctica/McMurdo
926Antarctica/Palmer
927Antarctica/Rothera
928Antarctica/South_Pole
929Antarctica/Syowa
930Antarctica/Troll
931Antarctica/Vostok
932Arctic/Longyearbyen
933Asia/Aden
934Asia/Almaty
935Asia/Amman
936Asia/Anadyr
937Asia/Aqtau
938Asia/Aqtobe
939Asia/Ashgabat
940Asia/Ashkhabad
941Asia/Atyrau
942Asia/Baghdad
943Asia/Bahrain
944Asia/Baku
945Asia/Bangkok
946Asia/Barnaul
947Asia/Beirut
948Asia/Bishkek
949Asia/Brunei
950Asia/Calcutta
951Asia/Chita
952Asia/Choibalsan
953Asia/Chongqing
954Asia/Chungking
955Asia/Colombo
956Asia/Dacca
957Asia/Damascus
958Asia/Dhaka
959Asia/Dili
960Asia/Dubai
961Asia/Dushanbe
962Asia/Famagusta
963Asia/Gaza
964Asia/Harbin
965Asia/Hebron
966Asia/Ho_Chi_Minh
967Asia/Hong_Kong
968Asia/Hovd
969Asia/Irkutsk
970Asia/Istanbul
971Asia/Jakarta
972Asia/Jayapura
973Asia/Jerusalem
974Asia/Kabul
975Asia/Kamchatka
976Asia/Karachi
977Asia/Kashgar
978Asia/Kathmandu
979Asia/Katmandu
980Asia/Khandyga
981Asia/Kolkata
982Asia/Krasnoyarsk
983Asia/Kuala_Lumpur
984Asia/Kuching
985Asia/Kuwait
986Asia/Macao
987Asia/Macau
988Asia/Magadan
989Asia/Makassar
990Asia/Manila
991Asia/Muscat
992Asia/Nicosia
993Asia/Novokuznetsk
994Asia/Novosibirsk
995Asia/Omsk
996Asia/Oral
997Asia/Phnom_Penh
998Asia/Pontianak
999Asia/Pyongyang
1000Asia/Qatar
1001Asia/Qostanay
1002Asia/Qyzylorda
1003Asia/Rangoon
1004Asia/Riyadh
1005Asia/Saigon
1006Asia/Sakhalin
1007Asia/Samarkand
1008Asia/Seoul
1009Asia/Shanghai
1010Asia/Singapore
1011Asia/Srednekolymsk
1012Asia/Taipei
1013Asia/Tashkent
1014Asia/Tbilisi
1015Asia/Tehran
1016Asia/Tel_Aviv
1017Asia/Thimbu
1018Asia/Thimphu
1019Asia/Tokyo
1020Asia/Tomsk
1021Asia/Ujung_Pandang
1022Asia/Ulaanbaatar
1023Asia/Ulan_Bator
1024Asia/Urumqi
1025Asia/Ust-Nera
1026Asia/Vientiane
1027Asia/Vladivostok
1028Asia/Yakutsk
1029Asia/Yangon
1030Asia/Yekaterinburg
1031Asia/Yerevan
1032Atlantic/Azores
1033Atlantic/Bermuda
1034Atlantic/Canary
1035Atlantic/Cape_Verde
1036Atlantic/Faeroe
1037Atlantic/Faroe
1038Atlantic/Jan_Mayen
1039Atlantic/Madeira
1040Atlantic/Reykjavik
1041Atlantic/South_Georgia
1042Atlantic/St_Helena
1043Atlantic/Stanley
1044Australia/ACT
1045Australia/Adelaide
1046Australia/Brisbane
1047Australia/Broken_Hill
1048Australia/Canberra
1049Australia/Currie
1050Australia/Darwin
1051Australia/Eucla
1052Australia/Hobart
1053Australia/LHI
1054Australia/Lindeman
1055Australia/Lord_Howe
1056Australia/Melbourne
1057Australia/NSW
1058Australia/North
1059Australia/Perth
1060Australia/Queensland
1061Australia/South
1062Australia/Sydney
1063Australia/Tasmania
1064Australia/Victoria
1065Australia/West
1066Australia/Yancowinna
1067Brazil/Acre
1068Brazil/DeNoronha
1069Brazil/East
1070Brazil/West
1071CET
1072CST6CDT
1073Canada/Atlantic
1074Canada/Central
1075Canada/Eastern
1076Canada/Mountain
1077Canada/Newfoundland
1078Canada/Pacific
1079Canada/Saskatchewan
1080Canada/Yukon
1081Chile/Continental
1082Chile/EasterIsland
1083Cuba
1084EET
1085EST
1086EST5EDT
1087Egypt
1088Eire
1089Etc/GMT
1090Etc/GMT+0
1091Etc/GMT+1
1092Etc/GMT+10
1093Etc/GMT+11
1094Etc/GMT+12
1095Etc/GMT+2
1096Etc/GMT+3
1097Etc/GMT+4
1098Etc/GMT+5
1099Etc/GMT+6
1100Etc/GMT+7
1101Etc/GMT+8
1102Etc/GMT+9
1103Etc/GMT-0
1104Etc/GMT-1
1105Etc/GMT-10
1106Etc/GMT-11
1107Etc/GMT-12
1108Etc/GMT-13
1109Etc/GMT-14
1110Etc/GMT-2
1111Etc/GMT-3
1112Etc/GMT-4
1113Etc/GMT-5
1114Etc/GMT-6
1115Etc/GMT-7
1116Etc/GMT-8
1117Etc/GMT-9
1118Etc/GMT0
1119Etc/Greenwich
1120Etc/UCT
1121Etc/UTC
1122Etc/Universal
1123Etc/Zulu
1124Europe/Amsterdam
1125Europe/Andorra
1126Europe/Astrakhan
1127Europe/Athens
1128Europe/Belfast
1129Europe/Belgrade
1130Europe/Berlin
1131Europe/Bratislava
1132Europe/Brussels
1133Europe/Bucharest
1134Europe/Budapest
1135Europe/Busingen
1136Europe/Chisinau
1137Europe/Copenhagen
1138Europe/Dublin
1139Europe/Gibraltar
1140Europe/Guernsey
1141Europe/Helsinki
1142Europe/Isle_of_Man
1143Europe/Istanbul
1144Europe/Jersey
1145Europe/Kaliningrad
1146Europe/Kiev
1147Europe/Kirov
1148Europe/Kyiv
1149Europe/Lisbon
1150Europe/Ljubljana
1151Europe/London
1152Europe/Luxembourg
1153Europe/Madrid
1154Europe/Malta
1155Europe/Mariehamn
1156Europe/Minsk
1157Europe/Monaco
1158Europe/Moscow
1159Europe/Nicosia
1160Europe/Oslo
1161Europe/Paris
1162Europe/Podgorica
1163Europe/Prague
1164Europe/Riga
1165Europe/Rome
1166Europe/Samara
1167Europe/San_Marino
1168Europe/Sarajevo
1169Europe/Saratov
1170Europe/Simferopol
1171Europe/Skopje
1172Europe/Sofia
1173Europe/Stockholm
1174Europe/Tallinn
1175Europe/Tirane
1176Europe/Tiraspol
1177Europe/Ulyanovsk
1178Europe/Uzhgorod
1179Europe/Vaduz
1180Europe/Vatican
1181Europe/Vienna
1182Europe/Vilnius
1183Europe/Volgograd
1184Europe/Warsaw
1185Europe/Zagreb
1186Europe/Zaporozhye
1187Europe/Zurich
1188GB
1189GB-Eire
1190GMT
1191GMT+0
1192GMT-0
1193GMT0
1194Greenwich
1195HST
1196Hongkong
1197Iceland
1198Indian/Antananarivo
1199Indian/Chagos
1200Indian/Christmas
1201Indian/Cocos
1202Indian/Comoro
1203Indian/Kerguelen
1204Indian/Mahe
1205Indian/Maldives
1206Indian/Mauritius
1207Indian/Mayotte
1208Indian/Reunion
1209Iran
1210Israel
1211Jamaica
1212Japan
1213Kwajalein
1214Libya
1215MET
1216MST
1217MST7MDT
1218Mexico/BajaNorte
1219Mexico/BajaSur
1220Mexico/General
1221NZ
1222NZ-CHAT
1223Navajo
1224PRC
1225PST8PDT
1226Pacific/Apia
1227Pacific/Auckland
1228Pacific/Bougainville
1229Pacific/Chatham
1230Pacific/Chuuk
1231Pacific/Easter
1232Pacific/Efate
1233Pacific/Enderbury
1234Pacific/Fakaofo
1235Pacific/Fiji
1236Pacific/Funafuti
1237Pacific/Galapagos
1238Pacific/Gambier
1239Pacific/Guadalcanal
1240Pacific/Guam
1241Pacific/Honolulu
1242Pacific/Johnston
1243Pacific/Kanton
1244Pacific/Kiritimati
1245Pacific/Kosrae
1246Pacific/Kwajalein
1247Pacific/Majuro
1248Pacific/Marquesas
1249Pacific/Midway
1250Pacific/Nauru
1251Pacific/Niue
1252Pacific/Norfolk
1253Pacific/Noumea
1254Pacific/Pago_Pago
1255Pacific/Palau
1256Pacific/Pitcairn
1257Pacific/Pohnpei
1258Pacific/Ponape
1259Pacific/Port_Moresby
1260Pacific/Rarotonga
1261Pacific/Saipan
1262Pacific/Samoa
1263Pacific/Tahiti
1264Pacific/Tarawa
1265Pacific/Tongatapu
1266Pacific/Truk
1267Pacific/Wake
1268Pacific/Wallis
1269Pacific/Yap
1270Poland
1271Portugal
1272ROC
1273ROK
1274Singapore
1275Turkey
1276UCT
1277US/Alaska
1278US/Aleutian
1279US/Arizona
1280US/Central
1281US/East-Indiana
1282US/Eastern
1283US/Hawaii
1284US/Indiana-Starke
1285US/Michigan
1286US/Mountain
1287US/Pacific
1288US/Samoa
1289UTC
1290Universal
1291W-SU
1292WET
1293Zulu
1294localtime
1295```
1296*/
1297pub fn tz(s: &str) -> Result<TimeZone, DtgError> {
1298    match s {
1299        "local" => match iana_time_zone::get_timezone() {
1300            Ok(local) => tz(&local),
1301            Err(_) => Err(DtgError::new("Failed to get local timezone", 103)),
1302        },
1303        _ => match jiff::tz::db().get(s) {
1304            Ok(z) => Ok(z),
1305            Err(_) => Err(DtgError::new(&format!("Invalid timezone: `{s}`"), 102)),
1306        },
1307    }
1308}