Skip to main content

keramics_datetime/
posix.rs

1/* Copyright 2024-2025 Joachim Metz <joachim.metz@gmail.com>
2 *
3 * Licensed under the Apache License, Version 2.0 (the "License");
4 * you may not use this file except in compliance with the License. You may
5 * obtain a copy of the License at https://www.apache.org/licenses/LICENSE-2.0
6 *
7 * Unless required by applicable law or agreed to in writing, software
8 * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
9 * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
10 * License for the specific language governing permissions and limitations
11 * under the License.
12 */
13
14use std::fmt;
15
16use keramics_types::{bytes_to_i32_be, bytes_to_i32_le};
17
18use super::epoch::Epoch;
19use super::util::{get_date_values, get_time_values};
20
21const POSIX_EPOCH: Epoch = Epoch {
22    year: 1970,
23    month: 1,
24    day_of_month: 1,
25};
26
27/// 32-bit POSIX timestamp (time_t).
28#[derive(Clone, Debug, Default, PartialEq)]
29pub struct PosixTime32 {
30    /// Number of seconds since January 1, 1970 (UTC) (POSIX epoch).
31    /// Negative values represent date and times predating the epoch.
32    pub timestamp: i32,
33}
34
35impl PosixTime32 {
36    /// Creates a new timestamp.
37    pub fn new(timestamp: i32) -> Self {
38        Self {
39            timestamp: timestamp,
40        }
41    }
42
43    /// Reads a big-endian timestamp from a byte sequence.
44    pub fn from_be_bytes(data: &[u8]) -> Self {
45        let timestamp: i32 = bytes_to_i32_be!(data, 0);
46        Self {
47            timestamp: timestamp,
48        }
49    }
50
51    /// Reads a little-endian timestamp from a byte sequence.
52    pub fn from_le_bytes(data: &[u8]) -> Self {
53        let timestamp: i32 = bytes_to_i32_le!(data, 0);
54        Self {
55            timestamp: timestamp,
56        }
57    }
58
59    /// Retrieves an ISO 8601 string representation of the timestamp.
60    pub fn to_iso8601_string(&self) -> String {
61        let (days, hours, minutes, seconds): (i64, u8, u8, u8) =
62            get_time_values(self.timestamp as i64);
63        let (year, month, day_of_month): (i16, u8, u8) = get_date_values(days, &POSIX_EPOCH);
64        format!(
65            "{:04}-{:02}-{:02}T{:02}:{:02}:{:02}",
66            year, month, day_of_month, hours, minutes, seconds
67        )
68    }
69}
70
71impl fmt::Display for PosixTime32 {
72    /// Formats the timestamp for display.
73    fn fmt(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
74        write!(
75            formatter,
76            "{} ({})",
77            self.to_iso8601_string(),
78            self.timestamp
79        )
80    }
81}
82
83/// 64-bit POSIX timestamp in nanoseconds.
84#[derive(Clone, Debug, Default, PartialEq)]
85pub struct PosixTime64Ns {
86    /// Number of seconds since January 1, 1970 (UTC) (POSIX epoch).
87    /// Negative values represent date and times predating the epoch.
88    pub timestamp: i64,
89
90    /// Fraction of second in nanoseconds.
91    pub fraction: u32,
92}
93
94impl PosixTime64Ns {
95    /// Creates a new timestamp.
96    pub fn new(timestamp: i64, fraction: u32) -> Self {
97        Self {
98            timestamp: timestamp,
99            fraction: fraction,
100        }
101    }
102
103    /// Retrieves an ISO 8601 string representation of the timestamp.
104    pub fn to_iso8601_string(&self) -> String {
105        let (days, hours, minutes, seconds): (i64, u8, u8, u8) = get_time_values(self.timestamp);
106        let (year, month, day_of_month): (i16, u8, u8) = get_date_values(days, &POSIX_EPOCH);
107        format!(
108            "{:04}-{:02}-{:02}T{:02}:{:02}:{:02}.{:09}",
109            year, month, day_of_month, hours, minutes, seconds, self.fraction
110        )
111    }
112}
113
114impl fmt::Display for PosixTime64Ns {
115    /// Formats the timestamp for display.
116    fn fmt(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
117        write!(
118            formatter,
119            "{} ({}.{})",
120            self.to_iso8601_string(),
121            self.timestamp,
122            self.fraction
123        )
124    }
125}
126
127#[cfg(test)]
128mod tests {
129    use super::*;
130
131    #[test]
132    fn test_posix_time32_from_be_bytes() {
133        let test_data: [u8; 4] = [0x67, 0x54, 0x0d, 0xd9];
134
135        let test_struct: PosixTime32 = PosixTime32::from_be_bytes(&test_data);
136        assert_eq!(test_struct.timestamp, 1733561817);
137    }
138
139    #[test]
140    fn test_posix_time32_from_le_bytes() {
141        let test_data: [u8; 4] = [0xd9, 0x0d, 0x54, 0x67];
142
143        let test_struct: PosixTime32 = PosixTime32::from_le_bytes(&test_data);
144        assert_eq!(test_struct.timestamp, 1733561817);
145    }
146
147    #[test]
148    fn test_posix_time32_to_iso8601_string() {
149        let test_struct: PosixTime32 = PosixTime32::new(1281643591);
150
151        let string: String = test_struct.to_iso8601_string();
152        assert_eq!(string.as_str(), "2010-08-12T20:06:31");
153
154        let test_struct: PosixTime32 = PosixTime32::new(-1281643591);
155
156        let string: String = test_struct.to_iso8601_string();
157        assert_eq!(string.as_str(), "1929-05-22T03:53:29");
158    }
159
160    #[test]
161    fn test_posix_time64ns_to_iso8601_string() {
162        let test_struct: PosixTime64Ns = PosixTime64Ns::new(1281643591, 987654321);
163
164        let string: String = test_struct.to_iso8601_string();
165        assert_eq!(string.as_str(), "2010-08-12T20:06:31.987654321");
166
167        let test_struct: PosixTime64Ns = PosixTime64Ns::new(-1281643592, 12345679);
168
169        let string: String = test_struct.to_iso8601_string();
170        assert_eq!(string.as_str(), "1929-05-22T03:53:28.012345679");
171    }
172}