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}