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