1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
use core::fmt;

use super::{Time, ToA};

impl fmt::Display for Time {
    /// Returns a canonical string representation of _time_.
    ///
    /// `Display` uses the same format as [`Time#to_s`].
    ///
    /// # Examples
    ///
    /// ```
    /// # use spinoso_time::tzrs::{Time, TimeError};
    /// # fn example() -> Result<(), TimeError> {
    /// let now = Time::utc(2022, 05, 26, 13, 16, 22, 0)?;
    /// assert_eq!(now.to_string(), "2022-05-26 13:16:22 UTC");
    /// # Ok(())
    /// # }
    /// # example().unwrap()
    /// ```
    ///
    /// [`Time#to_s`]: https://ruby-doc.org/core-3.1.2/Time.html#method-i-to_s
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        // https://github.com/ruby/ruby/blob/v3_1_2/time.c#L4007-L4017
        const UTC_FORMAT: &str = "%Y-%m-%d %H:%M:%S UTC";
        const FORMAT: &str = "%Y-%m-%d %H:%M:%S %z";

        if self.is_utc() {
            strftime::fmt::strftime(self, UTC_FORMAT, f).map_err(|_| fmt::Error)
        } else {
            strftime::fmt::strftime(self, FORMAT, f).map_err(|_| fmt::Error)
        }
    }
}

// Conversions
impl Time {
    /// Formats _time_ according to the directives in the given format string.
    ///
    /// Can be used to implement [`Time#strftime`]. The resulting byte string
    /// will have the same encoding as the format byte slice.
    ///
    /// # Examples
    ///
    /// ```
    /// # use spinoso_time::tzrs::{TimeError, Time};
    /// # #[derive(Debug)]
    /// # enum Error { Time(TimeError), Strftime(strftime::Error) };
    /// # impl From<TimeError> for Error { fn from(err: TimeError) -> Self { Self::Time(err) } }
    /// # impl From<strftime::Error> for Error { fn from(err: strftime::Error) -> Self { Self::Strftime(err) } }
    /// # fn example() -> Result<(), Error> {
    /// let now = Time::utc(2022, 05, 26, 13, 16, 22, 276)?;
    /// assert_eq!(
    ///     now.strftime("Today is %c 🎉".as_bytes())?,
    ///     "Today is Thu May 26 13:16:22 2022 🎉".as_bytes(),
    /// );
    /// # Ok(())
    /// # }
    /// # example().unwrap()
    /// ```
    ///
    /// # Errors
    ///
    /// Can return [`strftime::Error`] if formatting fails. See
    /// [`strftime::bytes::strftime`] for more details.
    ///
    /// [`Time#strftime`]: https://ruby-doc.org/core-3.1.2/Time.html#method-i-strftime
    #[inline]
    pub fn strftime(&self, format: &[u8]) -> Result<Vec<u8>, strftime::Error> {
        // Requires ASCII-compatible encoding (which rules out things like
        // UTF-16). ASCII, Binary, and UTF-8 are considered ASCII-compatible.
        //
        // ```
        // [3.1.2] * Time.now.strftime("abc %c")
        // => "abc Sat Aug 20 12:18:56 2022"
        // [3.1.2] > Time.now.strftime("abc %c 📦")
        // => "abc Sat Aug 20 12:19:04 2022 📦"
        // [3.1.2] > Time.now.strftime("abc %c 📦 \xFF")
        // => "abc Sat Aug 20 12:19:12 2022 📦 \xFF"
        // [3.1.2] > Time.now.strftime("abc %c 📦 \xFF".encode(Encoding::UTF_16))
        // (irb):5:in `encode': "\xFF" on UTF-8 (Encoding::InvalidByteSequenceError)
        //         from (irb):5:in `<main>'
        //         from /usr/local/var/rbenv/versions/3.1.2/lib/ruby/gems/3.1.0/gems/irb-1.4.1/exe/irb:11:in `<top (required)>'
        //         from /usr/local/var/rbenv/versions/3.1.2/bin/irb:25:in `load'
        //         from /usr/local/var/rbenv/versions/3.1.2/bin/irb:25:in `<main>'
        // [3.1.2] > Time.now.strftime("abc %c 📦 \xFF".encode(Encoding::UTF_8))
        // => "abc Sat Aug 20 12:20:10 2022 📦 \xFF"
        // ```
        strftime::bytes::strftime(self, format)
    }

    /// Serialize a `Time` into its components as a [`ToA`].
    ///
    /// `ToA` stores a `Time` as a ten-element struct of time components: [sec,
    /// min, hour, day, month, year, wday, yday, isdst, zone].
    ///
    /// The ordering of the properties is important for the Ruby [`Time#to_a`]
    /// API.
    ///
    /// Can be used to implement [`Time#to_a`].
    ///
    /// # Examples
    ///
    /// ```
    /// # use spinoso_time::tzrs::{Time, TimeError};
    /// # fn example() -> Result<(), TimeError> {
    /// let now = Time::now()?;
    /// let to_array = now.to_array();
    /// assert_eq!(to_array.sec, now.second());
    /// assert_eq!(to_array.wday, now.day_of_week());
    /// # Ok(())
    /// # }
    /// # example().unwrap()
    /// ```
    ///
    /// [`Time#to_a`]: https://ruby-doc.org/core-3.1.2/Time.html#method-i-to_a
    #[inline]
    #[must_use]
    pub fn to_array(self) -> ToA {
        ToA::from(self)
    }
}