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}