tackler_api/
txn_ts.rs

1/*
2 * Tackler-NG 2023-2025
3 * SPDX-License-Identifier: Apache-2.0
4 */
5
6//! Timestamp utilities
7//!
8//! `txn_ts` is collection of utilities to generate
9//! different representations of Txn timestamps.
10//!
11use crate::tackler;
12use jiff::Zoned;
13use jiff::fmt::strtime;
14use jiff::tz::{Offset, TimeZone};
15
16/// UTC Timezone
17pub static TZ_UTC: Offset = jiff::tz::Offset::UTC;
18
19/// Time stamp style
20#[derive(Debug, Copy, Clone, Default)]
21pub enum TimestampStyle {
22    /// 2024-11-09T14:15:16.123456789 -> 2024-11-09
23    Date,
24    /// 2024-11-09T14:15:16.123456789 -> 2024-11-09 14:15:16
25    Secodns,
26    /// This is the default
27    ///
28    /// 2024-11-09T14:15:16.123456789 -> 2024-11-09 14:15:16.123456789
29    #[default]
30    Full,
31}
32
33impl TimestampStyle {
34    /// UI/CFG string for date 'timestamp style' -selector
35    pub const DATE: &'static str = "date";
36
37    /// UI/CFG string for seconds 'timestamp style' -selector
38    pub const SECONDS: &'static str = "seconds";
39
40    /// UI/CFG string for full 'timestamp style' -selector
41    pub const FULL: &'static str = "full";
42
43    /// Get Timestamp style by name
44    ///
45    /// # Errors
46    ///
47    /// Returns `Err` if the timestamp style is unknown
48    pub fn from(name: &str) -> Result<Self, tackler::Error> {
49        match name {
50            TimestampStyle::DATE => Ok(TimestampStyle::Date),
51            TimestampStyle::SECONDS => Ok(TimestampStyle::Secodns),
52            TimestampStyle::FULL => Ok(TimestampStyle::Full),
53            _ => Err(format!(
54                "Unknown timestamp style: '{name}'. Valid options are: {}, {}, {}",
55                Self::DATE,
56                Self::SECONDS,
57                Self::FULL
58            )
59            .into()),
60        }
61    }
62}
63
64/// Time granularity selector for `GroupBy` operations
65#[derive(Debug, Clone, Copy, Default)]
66pub enum GroupBy {
67    /// Group by year
68    Year,
69    /// Group by year-month
70    /// This is the default
71    #[default]
72    Month,
73    /// Group by year-month-day
74    Date,
75    /// Group by ISO week (year-week)
76    IsoWeek,
77    /// Group by ISO week date (year-week-day)
78    IsoWeekDate,
79}
80
81impl GroupBy {
82    /// UI/CFG string for Year (2024) 'group by' -selector
83    pub const YEAR: &'static str = "year";
84
85    /// UI/CFG string for Month (2024-12) 'group by' -selector
86    pub const MONTH: &'static str = "month";
87
88    /// UI/CFG string for Date (2024-12-31) 'group by' -selector
89    pub const DATE: &'static str = "date";
90
91    /// UI/CFG string for ISO-Week (2024-W51) 'group by' -selector
92    pub const ISO_WEEK: &'static str = "iso-week";
93
94    /// UI/CFG string for ISO-Week-Date (2024-W51-5) 'group by' -selector
95    pub const ISO_WEEK_DATE: &'static str = "iso-week-date";
96
97    /// Get 'group by' -selector based on UI/CFG name
98    ///
99    /// # Errors
100    ///
101    /// Returns `Err` if group-by selector is unknown.
102    pub fn from(group_by: &str) -> Result<GroupBy, tackler::Error> {
103        match group_by {
104            GroupBy::ISO_WEEK_DATE => Ok(GroupBy::IsoWeekDate),
105            GroupBy::ISO_WEEK => Ok(GroupBy::IsoWeek),
106            GroupBy::DATE => Ok(GroupBy::Date),
107            GroupBy::MONTH => Ok(GroupBy::Month),
108            GroupBy::YEAR => Ok(GroupBy::Year),
109            _ => {
110                let msg = format!(
111                    "Unknown group-by selector: '{group_by}'. Valid options are: {}, {}, {}, {}, {}",
112                    GroupBy::ISO_WEEK_DATE,
113                    GroupBy::ISO_WEEK,
114                    GroupBy::DATE,
115                    GroupBy::MONTH,
116                    GroupBy::YEAR
117                );
118                Err(msg.into())
119            }
120        }
121    }
122}
123/// Get zoned ts from RFC 3339 string
124///
125/// # Errors
126///
127/// Returns `Err` the string is not in valid RFC 3339 format
128pub fn rfc3339_to_zoned(rfc3339_str: &str) -> Result<Zoned, tackler::Error> {
129    strtime::parse("%Y-%m-%dT%H:%M:%S%.f%:z", rfc3339_str)?
130        .to_zoned()
131        .map_err(|e| {
132            let msg = format!("Can't parse ts as rfc3339: '{rfc3339_str}', error: {e}");
133            msg.into()
134        })
135}
136/// RFC-3339 timestamp as string
137///
138/// # Examples
139/// ```
140/// # use std::error::Error;
141/// use jiff::Zoned;
142/// use tackler_api::txn_ts;
143///
144/// let ts: Zoned = "2022-12-24T14:15:16+02:00[Europe/Helsinki]".parse()?;
145/// assert_eq!(txn_ts::rfc_3339(&ts), "2022-12-24T14:15:16+02:00");
146///
147/// let ns: Zoned = "2022-06-24T14:15:16.123456789+03:00[Europe/Helsinki]".parse()?;
148/// assert_eq!(txn_ts::rfc_3339(&ns), "2022-06-24T14:15:16.123456789+03:00");
149///  # Ok::<(), Box<dyn Error>>(())
150/// ```
151#[must_use]
152pub fn rfc_3339(ts: &Zoned) -> String {
153    strtime::format("%Y-%m-%dT%H:%M:%S%.f%:z", ts)
154        .unwrap_or_else(|err| format!("IE: rfc_3339, frmt error: {err}"))
155}
156
157/// Human-readable timestamp with seconds precision and with zone
158///
159/// This is ISO-8601 style timestamp with space separator between components.
160///
161/// # Examples
162/// ```
163/// # use std::error::Error;
164/// use jiff::Zoned;
165/// use tackler_api::txn_ts;
166///
167/// let ts: Zoned = "2022-12-24T14:15:16+02:00[Europe/Helsinki]".parse()?;
168/// assert_eq!(txn_ts::seconds_tz(&ts), "2022-12-24 14:15:16 +02:00");
169///
170/// let ns: Zoned = "2022-06-24T14:15:16.123456789+03:00[Europe/Helsinki]".parse()?;
171/// assert_eq!(txn_ts::seconds_tz(&ns), "2022-06-24 14:15:16 +03:00");
172/// # Ok::<(), Box<dyn Error>>(())
173/// ```
174#[must_use]
175pub fn seconds_tz(ts: &Zoned) -> String {
176    strtime::format("%Y-%m-%d %H:%M:%S %:z", ts)
177        .unwrap_or_else(|err| format!("IE: seconds_tz, frmt error: {err}"))
178}
179
180/// Human-readable timestamp with full precision and with zone
181///
182/// This is ISO-8601 style timestamp with space separator between components.
183///
184/// # Examples
185/// ```
186/// # use std::error::Error;
187/// use jiff::Zoned;
188/// use tackler_api::txn_ts;
189///
190/// let ts: Zoned = "2022-12-24T14:15:16+02:00[Europe/Helsinki]".parse()?;
191/// assert_eq!(txn_ts::full_tz(&ts), "2022-12-24 14:15:16 +02:00");
192///
193/// let ns: Zoned = "2022-06-24T14:15:16.123456789+03:00[Europe/Helsinki]".parse()?;
194/// assert_eq!(txn_ts::full_tz(&ns), "2022-06-24 14:15:16.123456789 +03:00");
195/// # Ok::<(), Box<dyn Error>>(())
196/// ```
197#[must_use]
198pub fn full_tz(ts: &Zoned) -> String {
199    strtime::format("%Y-%m-%d %H:%M:%S%.f %:z", ts)
200        .unwrap_or_else(|err| format!("IE: full_tz, frmt error: {err}"))
201}
202
203fn fmt_seconds(ts: &Zoned) -> String {
204    strtime::format("%Y-%m-%d %H:%M:%S", ts)
205        .unwrap_or_else(|err| format!("IE: fmt_seconds, frmt error: {err}"))
206}
207
208fn fmt_full(ts: &Zoned) -> String {
209    strtime::format("%Y-%m-%d %H:%M:%S%.f", ts)
210        .unwrap_or_else(|err| format!("IE: fmt_full, frmt error: {err}"))
211}
212
213fn fmt_date(ts: &Zoned) -> String {
214    strtime::format("%Y-%m-%d", ts).unwrap_or_else(|err| format!("IE: fmt_date, frmt error: {err}"))
215}
216
217fn fmt_month(ts: &Zoned) -> String {
218    strtime::format("%Y-%m", ts).unwrap_or_else(|err| format!("IE: fmt_month, frmt error: {err}"))
219}
220
221fn fmt_year(ts: &Zoned) -> String {
222    strtime::format("%Y", ts).unwrap_or_else(|err| format!("IE: fmt_year, frmt error: {err}"))
223}
224
225fn fmt_week(ts: &Zoned) -> String {
226    let iso_date = ts.date().iso_week_date();
227    let y = iso_date.year();
228    let w = iso_date.week();
229
230    format!("{y}-W{w:02}")
231}
232
233fn fmt_week_date(ts: &Zoned) -> String {
234    let iso_date = ts.date().iso_week_date();
235    let y = iso_date.year();
236    let w = iso_date.week();
237    let wd = iso_date.weekday().to_monday_one_offset();
238
239    format!("{y}-W{w:02}-{wd}")
240}
241
242/// Human-readable timestamp with seconds precision in UTC zone
243///
244/// This is ISO-8601 style timestamp with space separator between components.
245/// Timestamp is converted into UTC zone.
246///
247/// # Examples
248/// ```
249/// # use std::error::Error;
250/// use tackler_api::txn_ts;
251///
252/// let ts = "2022-12-24T14:15:16+02:00[Europe/Helsinki]".parse()?;
253/// assert_eq!(txn_ts::as_utc_seconds(&ts), "2022-12-24 12:15:16");
254///
255/// let ns = "2022-06-24T14:15:16.123456789+03:00[Europe/Helsinki]".parse()?;
256/// assert_eq!(txn_ts::as_utc_seconds(&ns), "2022-06-24 11:15:16");
257/// # Ok::<(), Box<dyn Error>>(())
258/// ```
259#[must_use]
260pub fn as_utc_seconds(ts: &Zoned) -> String {
261    fmt_seconds(&ts.with_time_zone(TZ_UTC.to_time_zone().clone()))
262}
263
264/// Human-readable timestamp with full precision in UTC zone
265///
266/// This is ISO-8601 style timestamp with space separator between components.
267/// Timestamp is converted into UTC zone.
268///
269/// # Examples
270/// ```
271/// # use std::error::Error;
272/// use jiff::Zoned;
273/// use tackler_api::txn_ts;
274///
275/// let ts: Zoned = "2022-12-24T14:15:16+02:00[Europe/Helsinki]".parse()?;
276/// assert_eq!(txn_ts::as_utc_full(&ts), "2022-12-24 12:15:16");
277///
278/// let ns: Zoned  = "2022-06-24T14:15:16.123456789+03:00[Europe/Helsinki]".parse()?;
279/// assert_eq!(txn_ts::as_utc_full(&ns), "2022-06-24 11:15:16.123456789");
280///  # Ok::<(), Box<dyn Error>>(())
281/// ```
282#[must_use]
283pub fn as_utc_full(ts: &Zoned) -> String {
284    fmt_full(&ts.with_time_zone(TZ_UTC.to_time_zone().clone()))
285}
286
287/// Human readable timestamp with date (day) precision in UTC zone
288///
289/// This is ISO-8601 style timestamp with space separator between components.
290/// Timestamp is converted into UTC zone.
291///
292/// # Examples
293/// ```
294/// # use std::error::Error;
295/// use jiff::Zoned;
296/// use tackler_api::txn_ts;
297///
298/// let ts: Zoned = "2022-12-31T19:00:00-05:00[America/Montreal]".parse()?;
299/// assert_eq!(txn_ts::as_utc_date(&ts), "2023-01-01");
300/// # Ok::<(), Box<dyn Error>>(())
301/// ```
302#[must_use]
303pub fn as_utc_date(ts: &Zoned) -> String {
304    fmt_date(&ts.with_time_zone(TZ_UTC.to_time_zone().clone()))
305}
306
307/// Human-readable timestamp with month precision in UTC zone
308///
309/// This is ISO-8601 style timestamp with space separator between components.
310/// Timestamp is converted into UTC zone.
311///
312/// # Examples
313/// ```
314///  # use std::error::Error;
315/// use jiff::Zoned;
316/// use tackler_api::txn_ts;
317///
318/// let ts: Zoned = "2022-12-31T19:00:00-05:00[America/Toronto]".parse()?;
319/// assert_eq!(txn_ts::as_utc_month(&ts), "2023-01");
320/// # Ok::<(), Box<dyn Error>>(())
321/// ```
322#[must_use]
323pub fn as_utc_month(ts: &Zoned) -> String {
324    fmt_month(&ts.with_time_zone(TZ_UTC.to_time_zone().clone()))
325}
326
327/// Human-readable timestamp with year precision in UTC zone
328///
329/// This is ISO-8601 style timestamp with space separator between components.
330/// Timestamp is converted into UTC zone.
331///
332/// # Examples
333/// ```
334/// # use std::error::Error;
335/// use jiff::Zoned;
336/// use tackler_api::txn_ts;
337///
338/// let ts: Zoned = "2022-12-31T19:00:00-05:00[America/Toronto]".parse()?;
339/// assert_eq!(txn_ts::as_utc_year(&ts), "2023");
340///  # Ok::<(), Box<dyn Error>>(())
341/// ```
342#[must_use]
343pub fn as_utc_year(ts: &Zoned) -> String {
344    fmt_year(&ts.with_time_zone(TZ_UTC.to_time_zone().clone()))
345}
346
347/// Timestamp with ISO-8601 week precision in UTC timezone
348///
349/// Timestamp is converted into UTC timezone.
350///
351/// # Examples
352/// ```
353///  # use std::error::Error;
354/// use jiff::Zoned;
355/// use tackler_api::txn_ts;
356///
357/// let ts: Zoned = "2010-01-03T00:00:00+00:00[UTC]".parse()?;
358/// assert_eq!(txn_ts::as_utc_iso_week(&ts), "2009-W53");
359/// let ts: Zoned = "2010-01-04T00:00:00+00:00[UTC]".parse()?;
360/// assert_eq!(txn_ts::as_utc_iso_week(&ts), "2010-W01");
361///
362/// let ny: Zoned = "2010-01-03T19:00:00-05:00[America/Toronto]".parse()?;
363/// assert_eq!(txn_ts::as_utc_iso_week(&ny), "2010-W01");
364/// # Ok::<(), Box<dyn Error>>(())
365/// ```
366#[must_use]
367pub fn as_utc_iso_week(ts: &Zoned) -> String {
368    fmt_week(&ts.with_time_zone(TZ_UTC.to_time_zone().clone()))
369}
370
371/// Timestamp with ISO-8601 week-date precision in UTC timezone
372///
373/// Timestamp is converted into UTC timezone.
374///
375/// # Examples
376/// ```
377/// # use std::error::Error;
378/// use jiff::Zoned;
379/// use tackler_api::txn_ts;
380///
381/// let ts: Zoned = "2010-01-03T00:00:00+00:00[UTC]".parse()?;
382/// assert_eq!(txn_ts::as_utc_iso_week_date(&ts), "2009-W53-7");
383/// let ts: Zoned = "2010-01-04T00:00:00+00:00[UTC]".parse()?;
384/// assert_eq!(txn_ts::as_utc_iso_week_date(&ts), "2010-W01-1");
385///
386/// let ny: Zoned = "2010-01-03T19:00:00-05:00[America/Toronto]".parse()?;
387/// assert_eq!(txn_ts::as_utc_iso_week_date(&ny), "2010-W01-1");
388///# Ok::<(), Box<dyn Error>>(())
389/// ```
390#[must_use]
391pub fn as_utc_iso_week_date(ts: &Zoned) -> String {
392    fmt_week_date(&ts.with_time_zone(TZ_UTC.to_time_zone().clone()))
393}
394
395/// Human readable timestamp with seconds precision in provided timezone
396///
397/// This is ISO-8601 style timestamp with space separator between components.
398/// Timestamp is converted into provided timezone.
399///
400/// # Examples
401/// ```
402/// # use std::error::Error;
403/// use jiff::{tz, Zoned};
404/// use tackler_api::txn_ts;
405///
406/// let new_york_tz = tz::TimeZone::get("America/New_York")?;
407/// let helsinki_tz = tz::TimeZone::get("Europe/Helsinki")?;
408///
409/// let ts: Zoned = "2022-12-24T12:15:16+00:00[UTC]".parse()?;
410/// assert_eq!(txn_ts::as_tz_seconds(&ts, new_york_tz.clone()), "2022-12-24 07:15:16");
411/// assert_eq!(txn_ts::as_tz_seconds(&ts, helsinki_tz.clone()), "2022-12-24 14:15:16");
412///
413/// let ns: Zoned = "2022-06-24T12:15:16.123456789+00:00[UTC]".parse()?;
414/// assert_eq!(txn_ts::as_tz_seconds(&ns, helsinki_tz.clone()), "2022-06-24 15:15:16");
415/// # Ok::<(), Box<dyn Error>>(())
416/// ```
417#[must_use]
418pub fn as_tz_seconds(ts: &Zoned, tz: TimeZone) -> String {
419    fmt_seconds(&ts.with_time_zone(tz))
420}
421
422/// Human readable timestamp with full precision in provided timezone
423///
424/// This is ISO-8601 style timestamp with space separator between components.
425/// Timestamp is converted into provided timezone.
426///
427/// # Examples
428/// ```
429/// # use std::error::Error;
430/// use jiff::{tz, Zoned};
431/// use tackler_api::txn_ts;
432///
433/// let new_york_tz = tz::TimeZone::get("America/New_York")?;
434/// let helsinki_tz = tz::TimeZone::get("Europe/Helsinki")?;
435///
436/// let ts: Zoned = "2022-12-24T12:15:16+00:00[UTC]".parse()?;
437/// assert_eq!(txn_ts::as_tz_full(&ts, new_york_tz.clone()), "2022-12-24 07:15:16");
438/// assert_eq!(txn_ts::as_tz_full(&ts, helsinki_tz.clone()), "2022-12-24 14:15:16");
439///
440/// let ns: Zoned = "2022-06-24T12:15:16.123456789+00:00[UTC]".parse()?;
441/// assert_eq!(txn_ts::as_tz_full(&ns, helsinki_tz.clone()), "2022-06-24 15:15:16.123456789");
442/// # Ok::<(), Box<dyn Error>>(())
443/// ```
444#[must_use]
445pub fn as_tz_full(ts: &Zoned, tz: TimeZone) -> String {
446    fmt_full(&ts.with_time_zone(tz))
447}
448
449/// Human-readable timestamp with date precision in provided timezone
450///
451/// This is ISO-8601 style timestamp.
452/// Timestamp is converted into provided timezone.
453///
454/// # Examples
455/// ```
456/// # use std::error::Error;
457/// use jiff::{tz, Zoned};
458/// use tackler_api::txn_ts;
459///
460/// let new_york_tz = tz::TimeZone::get("America/New_York")?;
461/// let helsinki_tz = tz::TimeZone::get("Europe/Helsinki")?;
462///
463/// let ts: Zoned = "2022-12-23T22:00:00+00:00[UTC]".parse()?;
464/// assert_eq!(txn_ts::as_tz_date(&ts, new_york_tz.clone()), "2022-12-23");
465/// assert_eq!(txn_ts::as_tz_date(&ts, helsinki_tz.clone()), "2022-12-24");
466///
467/// let ns: Zoned = "2022-06-23T21:00:00+00:00[UTC]".parse()?;
468/// assert_eq!(txn_ts::as_tz_date(&ns, helsinki_tz.clone()), "2022-06-24");
469/// # Ok::<(), Box<dyn Error>>(())
470/// ```
471#[must_use]
472pub fn as_tz_date(ts: &Zoned, tz: TimeZone) -> String {
473    fmt_date(&ts.with_time_zone(tz))
474}
475
476/// Human-readable timestamp with month precision in provided timezone
477///
478/// This is ISO-8601 style timestamp.
479/// Timestamp is converted into provided timezone.
480///
481/// # Examples
482/// ```
483/// # use std::error::Error;
484/// use jiff::{tz, Zoned};
485/// use tackler_api::txn_ts;
486///
487/// let new_york_tz = tz::TimeZone::get("America/New_York")?;
488/// let helsinki_tz = tz::TimeZone::get("Europe/Helsinki")?;
489///
490/// let ts: Zoned = "2022-12-31T22:00:00+00:00[UTC]".parse()?;
491/// assert_eq!(txn_ts::as_tz_month(&ts, new_york_tz.clone()), "2022-12");
492/// assert_eq!(txn_ts::as_tz_month(&ts, helsinki_tz.clone()), "2023-01");
493///
494/// let ns: Zoned  = "2022-06-30T21:00:00+00:00[UTC]".parse()?;
495/// assert_eq!(txn_ts::as_tz_month(&ns, helsinki_tz.clone()), "2022-07");
496/// # Ok::<(), Box<dyn Error>>(())
497/// ```
498#[must_use]
499pub fn as_tz_month(ts: &Zoned, tz: TimeZone) -> String {
500    fmt_month(&ts.with_time_zone(tz))
501}
502
503/// Human-readable timestamp with year precision in provided timezone
504///
505/// This is ISO-8601 style timestamp.
506/// Timestamp is converted into provided timezone.
507///
508/// # Examples
509/// ```
510/// # use std::error::Error;
511/// use jiff::{tz, Zoned};
512/// use tackler_api::txn_ts;
513///
514/// let new_york_tz = tz::TimeZone::get("America/New_York")?;
515/// let helsinki_tz = tz::TimeZone::get("Europe/Helsinki")?;
516///
517/// let ts: Zoned = "2022-12-31T22:00:00+00:00[UTC]".parse()?;
518/// assert_eq!(txn_ts::as_tz_year(&ts, new_york_tz.clone()), "2022");
519/// assert_eq!(txn_ts::as_tz_year(&ts, helsinki_tz.clone()), "2023");
520///
521/// # Ok::<(), Box<dyn Error>>(())
522/// ```
523#[must_use]
524pub fn as_tz_year(ts: &Zoned, tz: TimeZone) -> String {
525    fmt_year(&ts.with_time_zone(tz))
526}
527
528/// Timestamp with ISO-8601 week precision in provided timezone
529///
530/// Timestamp is converted into provided timezone.
531///
532/// # Examples
533/// ```
534/// # use std::error::Error;
535/// use jiff::{tz, Zoned};
536/// use tackler_api::txn_ts;
537///
538/// let new_york_tz = tz::TimeZone::get("America/New_York")?;
539/// let helsinki_tz = tz::TimeZone::get("Europe/Helsinki")?;
540///
541/// let ts: Zoned = "2010-01-04T00:00:00+00:00[UTC]".parse()?;
542/// assert_eq!(txn_ts::as_tz_iso_week(&ts, new_york_tz.clone()), "2009-W53");
543/// assert_eq!(txn_ts::as_tz_iso_week(&ts, helsinki_tz.clone()), "2010-W01");
544///
545/// # Ok::<(), Box<dyn Error>>(())
546/// ```
547#[must_use]
548pub fn as_tz_iso_week(ts: &Zoned, tz: TimeZone) -> String {
549    fmt_week(&ts.with_time_zone(tz))
550}
551
552/// Timestamp with ISO-8601 week date precision in provided timezone
553///
554/// Timestamp is converted into provided timezone.
555///
556/// # Examples
557/// ```
558/// # use std::error::Error;
559/// use jiff::{tz, Zoned};
560/// use tackler_api::txn_ts;
561///
562/// let new_york_tz = tz::TimeZone::get("America/New_York")?;
563/// let helsinki_tz = tz::TimeZone::get("Europe/Helsinki")?;
564///
565/// let ts: Zoned = "2010-01-04T00:00:00+00:00[UTC]".parse()?;
566/// assert_eq!(txn_ts::as_tz_iso_week_date(&ts, new_york_tz.clone()), "2009-W53-7");
567/// assert_eq!(txn_ts::as_tz_iso_week_date(&ts, helsinki_tz.clone()), "2010-W01-1");
568///
569/// # Ok::<(), Box<dyn Error>>(())
570/// ```
571#[must_use]
572pub fn as_tz_iso_week_date(ts: &Zoned, tz: TimeZone) -> String {
573    fmt_week_date(&ts.with_time_zone(tz))
574}
575
576#[cfg(test)]
577mod tests {
578    use super::*;
579
580    #[test]
581    // test: f0e2f23c-7cc6-4610-80c0-8f1e3a6555c7
582    fn timestampstyle_full() {
583        let tss = TimestampStyle::from("full");
584        match tss {
585            Ok(TimestampStyle::Full) => (),
586            _ => panic!("/*:test:*/"),
587        }
588    }
589
590    #[test]
591    // test: abec5673-f55e-427d-9db6-cdf865100a21
592    fn timestampstyle_seconds() {
593        let tss = TimestampStyle::from("seconds");
594        match tss {
595            Ok(TimestampStyle::Secodns) => (),
596            _ => panic!("/*:test:*/"),
597        }
598    }
599
600    #[test]
601    // test: 0b8dd80a-e826-4107-9892-7db04e3a9f59
602    fn timestampstyle_date() {
603        let tss = TimestampStyle::from("date");
604        match tss {
605            Ok(TimestampStyle::Date) => (),
606            _ => panic!("/*:test:*/"),
607        }
608    }
609
610    #[test]
611    // test: fc6c569a-1ab4-4fde-bed6-1593116f617d
612    fn timestampstyle_invalid() {
613        let tss = TimestampStyle::from("minutes");
614        assert!(tss.is_err());
615    }
616
617    fn txt2ts(txt_ts: &str) -> Zoned {
618        strtime::parse("%Y-%m-%dT%H:%M:%S%.f%:z", txt_ts)
619            .unwrap(/*:test:*/)
620            .to_zoned()
621            .unwrap(/*:test:*/)
622    }
623
624    #[test]
625    fn doc_test() {
626        let ts = txt2ts("2022-12-24T13:14:15+02:00");
627        assert_eq!(rfc_3339(&ts), "2022-12-24T13:14:15+02:00");
628    }
629
630    #[test]
631    fn test_rfc_3339() {
632        assert_eq!(
633            rfc_3339(&txt2ts("2010-01-02T13:14:15+16:00")),
634            "2010-01-02T13:14:15+16:00"
635        );
636        assert_eq!(
637            rfc_3339(&txt2ts("2010-12-24T01:02:03.456+16:00")),
638            "2010-12-24T01:02:03.456+16:00"
639        );
640        assert_eq!(
641            rfc_3339(&txt2ts("2010-12-24T01:02:03.456789+16:00")),
642            "2010-12-24T01:02:03.456789+16:00"
643        );
644        assert_eq!(
645            rfc_3339(&txt2ts("2010-12-24T01:02:03.700-16:00")),
646            "2010-12-24T01:02:03.7-16:00"
647        );
648
649        assert_eq!(
650            rfc_3339(&txt2ts("2010-12-24T00:00:00+00:00")),
651            "2010-12-24T00:00:00+00:00"
652        );
653        assert_eq!(
654            rfc_3339(&txt2ts("2020-12-31T23:58:59+00:00")),
655            "2020-12-31T23:58:59+00:00"
656        );
657        assert_eq!(
658            rfc_3339(&txt2ts("2020-12-31T23:58:59+00:00")),
659            "2020-12-31T23:58:59+00:00"
660        );
661    }
662
663    #[test]
664    fn test_seconds_tz() {
665        assert_eq!(
666            seconds_tz(&txt2ts("2010-01-02T00:00:00+00:00")),
667            "2010-01-02 00:00:00 +00:00"
668        ); // todo: time: Z
669        assert_eq!(
670            seconds_tz(&txt2ts("2010-12-24T13:14:15+16:00")),
671            "2010-12-24 13:14:15 +16:00"
672        );
673        assert_eq!(
674            seconds_tz(&txt2ts("2010-12-24T01:02:03.456+16:00")),
675            "2010-12-24 01:02:03 +16:00"
676        );
677        assert_eq!(
678            seconds_tz(&txt2ts("2010-12-24T01:02:03.456789+16:00")),
679            "2010-12-24 01:02:03 +16:00"
680        );
681        assert_eq!(
682            seconds_tz(&txt2ts("2020-12-24T23:58:59.123456789+00:00")),
683            "2020-12-24 23:58:59 +00:00"
684        ); // todo: time: Z
685        assert_eq!(
686            seconds_tz(&txt2ts("2010-12-24T01:02:03.700-16:00")),
687            "2010-12-24 01:02:03 -16:00"
688        );
689
690        assert_eq!(
691            seconds_tz(&txt2ts("2020-12-31T23:58:59+00:00")),
692            "2020-12-31 23:58:59 +00:00"
693        );
694    }
695
696    #[test]
697    fn test_full_tz() {
698        assert_eq!(
699            full_tz(&txt2ts("2010-01-02T13:14:15+16:00")),
700            "2010-01-02 13:14:15 +16:00"
701        );
702        assert_eq!(
703            full_tz(&txt2ts("2010-12-24T01:02:03.456+16:00")),
704            "2010-12-24 01:02:03.456 +16:00"
705        );
706        assert_eq!(
707            full_tz(&txt2ts("2010-12-24T01:02:03.456789+16:00")),
708            "2010-12-24 01:02:03.456789 +16:00"
709        );
710        assert_eq!(
711            full_tz(&txt2ts("2010-12-24T01:02:03.700-16:00")),
712            "2010-12-24 01:02:03.7 -16:00"
713        );
714
715        assert_eq!(
716            full_tz(&txt2ts("2010-12-24T00:00:00+00:00")),
717            "2010-12-24 00:00:00 +00:00"
718        );
719        assert_eq!(
720            full_tz(&txt2ts("2020-12-31T23:58:59.123456789+00:00")),
721            "2020-12-31 23:58:59.123456789 +00:00"
722        );
723    }
724
725    #[test]
726    fn test_fmt_seconds() {
727        assert_eq!(
728            fmt_seconds(&txt2ts("2010-01-02T00:00:00+00:00")),
729            "2010-01-02 00:00:00"
730        );
731        assert_eq!(
732            fmt_seconds(&txt2ts("2010-12-24T13:14:15+16:00")),
733            "2010-12-24 13:14:15"
734        );
735        assert_eq!(
736            fmt_seconds(&txt2ts("2010-12-24T01:02:03.456+16:00")),
737            "2010-12-24 01:02:03"
738        );
739        assert_eq!(
740            fmt_seconds(&txt2ts("2010-12-24T01:02:03.456789+16:00")),
741            "2010-12-24 01:02:03"
742        );
743        assert_eq!(
744            fmt_seconds(&txt2ts("2020-12-24T23:58:59.123456789+00:00")),
745            "2020-12-24 23:58:59"
746        );
747        assert_eq!(
748            fmt_seconds(&txt2ts("2010-12-24T01:02:03.700-16:00")),
749            "2010-12-24 01:02:03"
750        );
751    }
752
753    #[test]
754    fn test_fmt_full() {
755        assert_eq!(
756            fmt_full(&txt2ts("2010-01-02T00:00:00+00:00")),
757            "2010-01-02 00:00:00"
758        );
759        assert_eq!(
760            fmt_full(&txt2ts("2010-12-24T13:14:15+16:00")),
761            "2010-12-24 13:14:15"
762        );
763        assert_eq!(
764            fmt_full(&txt2ts("2010-12-24T01:02:03.456+16:00")),
765            "2010-12-24 01:02:03.456"
766        );
767        assert_eq!(
768            fmt_full(&txt2ts("2010-12-24T01:02:03.456789+16:00")),
769            "2010-12-24 01:02:03.456789"
770        );
771        assert_eq!(
772            fmt_full(&txt2ts("2020-12-24T23:58:59.123456789+00:00")),
773            "2020-12-24 23:58:59.123456789"
774        );
775        assert_eq!(
776            fmt_full(&txt2ts("2010-12-24T01:02:03.700-16:00")),
777            "2010-12-24 01:02:03.7"
778        );
779    }
780
781    #[test]
782    fn test_fmt_date() {
783        assert_eq!(fmt_date(&txt2ts("2010-01-02T00:00:00+00:00")), "2010-01-02");
784        assert_eq!(fmt_date(&txt2ts("2010-12-24T13:14:15+16:00")), "2010-12-24");
785        assert_eq!(
786            fmt_date(&txt2ts("2010-12-24T01:02:03.456+16:00")),
787            "2010-12-24"
788        );
789        assert_eq!(
790            fmt_date(&txt2ts("2010-12-24T01:02:03.456789+16:00")),
791            "2010-12-24"
792        );
793        assert_eq!(
794            fmt_date(&txt2ts("2020-12-24T23:58:59.123456789+00:00")),
795            "2020-12-24"
796        );
797        assert_eq!(
798            fmt_date(&txt2ts("2010-12-24T01:02:03.700-16:00")),
799            "2010-12-24"
800        );
801    }
802
803    #[test]
804    fn test_fmt_month() {
805        assert_eq!(fmt_month(&txt2ts("2010-01-02T00:00:00+00:00")), "2010-01");
806        assert_eq!(fmt_month(&txt2ts("2010-12-24T13:14:15+16:00")), "2010-12");
807        assert_eq!(
808            fmt_month(&txt2ts("2010-12-24T01:02:03.456+16:00")),
809            "2010-12"
810        );
811        assert_eq!(
812            fmt_month(&txt2ts("2010-12-24T01:02:03.456789+16:00")),
813            "2010-12"
814        );
815        assert_eq!(
816            fmt_month(&txt2ts("2020-12-24T23:58:59.123456789+00:00")),
817            "2020-12"
818        );
819        assert_eq!(
820            fmt_month(&txt2ts("2010-12-24T01:02:03.700-16:00")),
821            "2010-12"
822        );
823    }
824
825    #[test]
826    fn test_fmt_year() {
827        assert_eq!(fmt_year(&txt2ts("2010-01-02T00:00:00+00:00")), "2010");
828        assert_eq!(fmt_year(&txt2ts("2010-12-24T13:14:15+16:00")), "2010");
829        assert_eq!(fmt_year(&txt2ts("2010-12-24T01:02:03.456+16:00")), "2010");
830        assert_eq!(
831            fmt_year(&txt2ts("2010-12-24T01:02:03.456789+16:00")),
832            "2010"
833        );
834        assert_eq!(
835            fmt_year(&txt2ts("2020-12-24T23:58:59.123456789+00:00")),
836            "2020"
837        );
838        assert_eq!(fmt_year(&txt2ts("2010-12-24T01:02:03.700-16:00")), "2010");
839    }
840
841    #[test]
842    fn test_fmt_week() {
843        assert_eq!(fmt_week(&txt2ts("2010-01-03T00:00:00+00:00")), "2009-W53");
844        assert_eq!(fmt_week(&txt2ts("2010-01-04T00:00:00+00:00")), "2010-W01");
845        assert_eq!(fmt_week(&txt2ts("2017-01-01T00:00:00+00:00")), "2016-W52");
846        assert_eq!(fmt_week(&txt2ts("2017-01-02T00:00:00+00:00")), "2017-W01");
847    }
848
849    #[test]
850    fn test_fmt_week_date() {
851        assert_eq!(
852            fmt_week_date(&txt2ts("2010-01-03T00:00:00+00:00")),
853            "2009-W53-7"
854        );
855        assert_eq!(
856            fmt_week_date(&txt2ts("2010-01-04T00:00:00+00:00")),
857            "2010-W01-1"
858        );
859        assert_eq!(
860            fmt_week_date(&txt2ts("2017-01-01T00:00:00+00:00")),
861            "2016-W52-7"
862        );
863
864        assert_eq!(
865            fmt_week_date(&txt2ts("2017-01-02T00:00:00+00:00")),
866            "2017-W01-1"
867        );
868
869        assert_eq!(
870            fmt_week_date(&txt2ts("2020-12-31T00:00:00+00:00")),
871            "2020-W53-4"
872        );
873        assert_eq!(
874            fmt_week_date(&txt2ts("2021-01-01T00:00:00+00:00")),
875            "2020-W53-5"
876        );
877    }
878
879    #[test]
880    fn test_utc_seconds() {
881        assert_eq!(
882            as_utc_seconds(&txt2ts("2010-01-01T00:00:00+16:00")),
883            "2009-12-31 08:00:00"
884        );
885        assert_eq!(
886            as_utc_seconds(&txt2ts("2010-01-02T14:15:16+00:00")),
887            "2010-01-02 14:15:16"
888        );
889        assert_eq!(
890            as_utc_seconds(&txt2ts("2010-01-01T01:02:03.700-16:00")),
891            "2010-01-01 17:02:03"
892        );
893    }
894
895    #[test]
896    fn test_utc_full() {
897        assert_eq!(
898            as_utc_full(&txt2ts("2010-01-01T00:00:00+16:00")),
899            "2009-12-31 08:00:00"
900        );
901        assert_eq!(
902            as_utc_full(&txt2ts("2010-01-02T14:15:16.456+00:00")),
903            "2010-01-02 14:15:16.456"
904        );
905        assert_eq!(
906            as_utc_full(&txt2ts("2010-01-01T01:02:03.700-16:00")),
907            "2010-01-01 17:02:03.7"
908        );
909
910        assert_eq!(
911            as_utc_full(&txt2ts("2020-12-31T23:58:59.123456789+00:00")),
912            "2020-12-31 23:58:59.123456789"
913        );
914
915        assert_eq!(
916            as_utc_full(&txt2ts("2020-12-31T23:58:59.123456789+00:00")),
917            "2020-12-31 23:58:59.123456789"
918        );
919    }
920
921    #[test]
922    fn test_utc_week() {
923        assert_eq!(
924            as_utc_iso_week(&txt2ts("2010-01-03T00:00:00+00:00")),
925            "2009-W53"
926        );
927        assert_eq!(
928            as_utc_iso_week(&txt2ts("2017-01-02T00:00:00+00:00")),
929            "2017-W01"
930        );
931        assert_eq!(
932            as_utc_iso_week(&txt2ts("2017-01-02T00:00:00+02:00")),
933            "2016-W52"
934        );
935        assert_eq!(
936            as_utc_iso_week(&txt2ts("2017-01-02T00:00:00-02:00")),
937            "2017-W01"
938        );
939    }
940
941    #[test]
942    fn test_utc_week_date() {
943        assert_eq!(
944            as_utc_iso_week_date(&txt2ts("2010-01-03T00:00:00+00:00")),
945            "2009-W53-7"
946        );
947
948        assert_eq!(
949            as_utc_iso_week_date(&txt2ts("2017-01-02T00:00:00+00:00")),
950            "2017-W01-1"
951        );
952        assert_eq!(
953            as_utc_iso_week_date(&txt2ts("2017-01-02T00:00:00+02:00")),
954            "2016-W52-7"
955        );
956        assert_eq!(
957            as_utc_iso_week_date(&txt2ts("2017-01-01T22:00:00+00:00")),
958            "2016-W52-7"
959        );
960        assert_eq!(
961            as_utc_iso_week_date(&txt2ts("2017-01-01T22:00:00-02:00")),
962            "2017-W01-1"
963        );
964    }
965
966    #[test]
967    fn test_utc_date() {
968        assert_eq!(
969            as_utc_date(&txt2ts("2010-01-01T15:00:00+16:00")),
970            "2009-12-31"
971        );
972        assert_eq!(
973            as_utc_date(&txt2ts("2010-01-01T08:02:03-16:00")),
974            "2010-01-02"
975        );
976        assert_eq!(
977            as_utc_date(&txt2ts("2020-12-31T23:59:59.999999999+00:00")),
978            "2020-12-31"
979        );
980    }
981
982    #[test]
983    fn test_utc_month() {
984        assert_eq!(
985            as_utc_month(&txt2ts("2010-01-01T15:00:00+16:00")),
986            "2009-12"
987        );
988        assert_eq!(
989            as_utc_month(&txt2ts("2010-01-31T08:02:03-16:00")),
990            "2010-02"
991        );
992        assert_eq!(
993            as_utc_month(&txt2ts("2020-12-31T23:59:59.999999999+00:00")),
994            "2020-12"
995        );
996    }
997
998    #[test]
999    fn test_utc_year() {
1000        assert_eq!(as_utc_year(&txt2ts("2010-01-01T15:00:00+16:00")), "2009");
1001        assert_eq!(as_utc_year(&txt2ts("2010-12-31T08:02:03-16:00")), "2011");
1002        assert_eq!(
1003            as_utc_year(&txt2ts("2020-12-31T23:59:59.999999999+00:00")),
1004            "2020"
1005        );
1006    }
1007
1008    #[test]
1009    fn test_zoned_seconds() {
1010        let utc_tz = TimeZone::get("UTC").unwrap(/*:test:*/);
1011        let helsinki_tz = TimeZone::get("Europe/Helsinki").unwrap(/*:test:*/);
1012
1013        // standard time
1014        assert_eq!(
1015            as_tz_seconds(&txt2ts("2010-01-02T00:00:00+00:00"), utc_tz.clone()),
1016            "2010-01-02 00:00:00"
1017        );
1018        assert_eq!(
1019            as_tz_seconds(&txt2ts("2010-01-02T00:00:00+00:00"), helsinki_tz.clone()),
1020            "2010-01-02 02:00:00"
1021        );
1022
1023        // daylight saving time
1024        assert_eq!(
1025            as_tz_seconds(&txt2ts("2022-06-24T00:00:00+00:00"), utc_tz.clone()),
1026            "2022-06-24 00:00:00"
1027        );
1028        assert_eq!(
1029            as_tz_seconds(&txt2ts("2022-06-24T00:00:00+00:00"), helsinki_tz.clone()),
1030            "2022-06-24 03:00:00"
1031        );
1032
1033        assert_eq!(
1034            as_tz_seconds(&txt2ts("2010-01-02T00:00:00+16:00"), utc_tz),
1035            "2010-01-01 08:00:00"
1036        );
1037        assert_eq!(
1038            as_tz_seconds(&txt2ts("2010-01-02T00:00:00+16:00"), helsinki_tz),
1039            "2010-01-01 10:00:00"
1040        );
1041    }
1042
1043    #[test]
1044    fn test_zoned_full() {
1045        let utc_tz = TimeZone::get("UTC").unwrap(/*:test:*/);
1046        let helsinki_tz = TimeZone::get("Europe/Helsinki").unwrap(/*:test:*/);
1047
1048        // standard time
1049        assert_eq!(
1050            as_tz_full(&txt2ts("2010-01-02T00:00:00+00:00"), utc_tz.clone()),
1051            "2010-01-02 00:00:00"
1052        );
1053        assert_eq!(
1054            as_tz_full(&txt2ts("2010-01-02T00:00:00+00:00"), helsinki_tz.clone()),
1055            "2010-01-02 02:00:00"
1056        );
1057
1058        // daylight saving time
1059        assert_eq!(
1060            as_tz_full(&txt2ts("2022-06-24T00:00:00+00:00"), utc_tz.clone()),
1061            "2022-06-24 00:00:00"
1062        );
1063        assert_eq!(
1064            as_tz_full(&txt2ts("2022-06-24T00:00:00+00:00"), helsinki_tz.clone()),
1065            "2022-06-24 03:00:00"
1066        );
1067
1068        assert_eq!(
1069            as_tz_full(&txt2ts("2010-01-02T00:00:00.123+16:00"), utc_tz),
1070            "2010-01-01 08:00:00.123"
1071        );
1072        assert_eq!(
1073            as_tz_full(&txt2ts("2010-01-02T00:00:00.123+16:00"), helsinki_tz),
1074            "2010-01-01 10:00:00.123"
1075        );
1076    }
1077
1078    #[test]
1079    fn test_zoned_date() {
1080        let utc_tz = TimeZone::get("UTC").unwrap(/*:test:*/);
1081        let helsinki_tz = TimeZone::get("Europe/Helsinki").unwrap(/*:test:*/);
1082
1083        // standard time
1084        assert_eq!(
1085            as_tz_date(&txt2ts("2010-01-02T00:00:00+00:00"), utc_tz.clone()),
1086            "2010-01-02"
1087        );
1088        assert_eq!(
1089            as_tz_date(
1090                &txt2ts("2009-12-31T21:59:59.999999999+00:00"),
1091                helsinki_tz.clone()
1092            ),
1093            "2009-12-31"
1094        );
1095        assert_eq!(
1096            as_tz_date(&txt2ts("2009-12-31T22:00:00+00:00"), helsinki_tz.clone()),
1097            "2010-01-01"
1098        );
1099
1100        // daylight saving time
1101        assert_eq!(
1102            as_tz_date(&txt2ts("2022-06-24T00:00:00+00:00"), utc_tz),
1103            "2022-06-24"
1104        );
1105        assert_eq!(
1106            as_tz_date(&txt2ts("2022-06-23T21:00:00+00:00"), helsinki_tz),
1107            "2022-06-24"
1108        );
1109    }
1110
1111    #[test]
1112    fn test_zoned_month() {
1113        let utc_tz = TimeZone::get("UTC").unwrap(/*:test:*/);
1114        let helsinki_tz = TimeZone::get("Europe/Helsinki").unwrap(/*:test:*/);
1115
1116        // standard time
1117        assert_eq!(
1118            as_tz_month(&txt2ts("2010-01-02T00:00:00+00:00"), utc_tz.clone()),
1119            "2010-01"
1120        );
1121        assert_eq!(
1122            as_tz_month(
1123                &txt2ts("2009-12-31T21:59:59.999999999+00:00"),
1124                helsinki_tz.clone()
1125            ),
1126            "2009-12"
1127        );
1128        assert_eq!(
1129            as_tz_month(&txt2ts("2009-12-31T22:00:00+00:00"), helsinki_tz.clone()),
1130            "2010-01"
1131        );
1132
1133        // daylight saving time
1134        assert_eq!(
1135            as_tz_month(&txt2ts("2022-06-30T00:00:00+00:00"), utc_tz),
1136            "2022-06"
1137        );
1138        assert_eq!(
1139            as_tz_month(&txt2ts("2022-06-30T21:00:00+00:00"), helsinki_tz),
1140            "2022-07"
1141        );
1142    }
1143
1144    #[test]
1145    fn test_zoned_year() {
1146        let utc_tz = TimeZone::get("UTC").unwrap(/*:test:*/);
1147        let helsinki_tz = TimeZone::get("Europe/Helsinki").unwrap(/*:test:*/);
1148
1149        // standard time
1150        assert_eq!(
1151            as_tz_year(&txt2ts("2010-01-02T00:00:00+00:00"), utc_tz),
1152            "2010"
1153        );
1154        assert_eq!(
1155            as_tz_year(
1156                &txt2ts("2009-12-31T21:59:59.999999999+00:00"),
1157                helsinki_tz.clone()
1158            ),
1159            "2009"
1160        );
1161        assert_eq!(
1162            as_tz_year(&txt2ts("2009-12-31T22:00:00+00:00"), helsinki_tz),
1163            "2010"
1164        );
1165    }
1166
1167    #[test]
1168    fn test_zoned_week() {
1169        let utc_tz = TimeZone::get("UTC").unwrap(/*:test:*/);
1170        let helsinki_tz = TimeZone::get("Europe/Helsinki").unwrap(/*:test:*/);
1171
1172        // standard time
1173        assert_eq!(
1174            as_tz_iso_week(&txt2ts("2010-01-03T00:00:00+00:00"), utc_tz.clone()),
1175            "2009-W53"
1176        );
1177        assert_eq!(
1178            as_tz_iso_week(
1179                &txt2ts("2010-01-03T21:59:59.999999999+00:00"),
1180                helsinki_tz.clone()
1181            ),
1182            "2009-W53"
1183        );
1184        assert_eq!(
1185            as_tz_iso_week(&txt2ts("2010-01-04T00:00:00+00:00"), utc_tz.clone()),
1186            "2010-W01"
1187        );
1188        assert_eq!(
1189            as_tz_iso_week(&txt2ts("2010-01-03T22:00:00+00:00"), helsinki_tz.clone()),
1190            "2010-W01"
1191        );
1192
1193        // daylight saving time
1194        assert_eq!(
1195            as_tz_iso_week(
1196                &txt2ts("2022-06-19T23:59:59.999999999+00:00"),
1197                utc_tz.clone()
1198            ),
1199            "2022-W24"
1200        );
1201        assert_eq!(
1202            as_tz_iso_week(
1203                &txt2ts("2022-06-19T20:59:59.999999999+00:00"),
1204                helsinki_tz.clone()
1205            ),
1206            "2022-W24"
1207        );
1208        assert_eq!(
1209            as_tz_iso_week(&txt2ts("2022-06-20T00:00:00+00:00"), utc_tz.clone()),
1210            "2022-W25"
1211        );
1212        assert_eq!(
1213            as_tz_iso_week(&txt2ts("2022-06-19T21:00:00+00:00"), helsinki_tz.clone()),
1214            "2022-W25"
1215        );
1216    }
1217
1218    #[test]
1219    fn test_zoned_week_date() {
1220        let utc_tz = TimeZone::get("UTC").unwrap(/*:test:*/);
1221        let helsinki_tz = TimeZone::get("Europe/Helsinki").unwrap(/*:test:*/);
1222
1223        // standard time
1224        assert_eq!(
1225            as_tz_iso_week_date(&txt2ts("2010-01-03T00:00:00+00:00"), utc_tz.clone()),
1226            "2009-W53-7"
1227        );
1228        assert_eq!(
1229            as_tz_iso_week_date(
1230                &txt2ts("2010-01-03T21:59:59.999999999+00:00"),
1231                helsinki_tz.clone()
1232            ),
1233            "2009-W53-7"
1234        );
1235        assert_eq!(
1236            as_tz_iso_week_date(&txt2ts("2010-01-04T00:00:00+00:00"), utc_tz.clone()),
1237            "2010-W01-1"
1238        );
1239        assert_eq!(
1240            as_tz_iso_week_date(&txt2ts("2010-01-03T22:00:00+00:00"), helsinki_tz.clone()),
1241            "2010-W01-1"
1242        );
1243
1244        // daylight saving time
1245        assert_eq!(
1246            as_tz_iso_week_date(
1247                &txt2ts("2022-06-19T23:59:59.999999999+00:00"),
1248                utc_tz.clone()
1249            ),
1250            "2022-W24-7"
1251        );
1252        assert_eq!(
1253            as_tz_iso_week_date(
1254                &txt2ts("2022-06-19T20:59:59.999999999+00:00"),
1255                helsinki_tz.clone()
1256            ),
1257            "2022-W24-7"
1258        );
1259        assert_eq!(
1260            as_tz_iso_week_date(&txt2ts("2022-06-20T00:00:00+00:00"), utc_tz.clone()),
1261            "2022-W25-1"
1262        );
1263        assert_eq!(
1264            as_tz_iso_week_date(&txt2ts("2022-06-19T21:00:00+00:00"), helsinki_tz.clone()),
1265            "2022-W25-1"
1266        );
1267    }
1268}