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