Skip to main content

keramics_datetime/
filetime.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_u32_le;
17
18use super::epoch::Epoch;
19use super::util::{get_date_values, get_time_values};
20
21const FILETIME_EPOCH: Epoch = Epoch {
22    year: 1601,
23    month: 1,
24    day_of_month: 1,
25};
26
27/// Windows FILETIME timestamp.
28#[derive(Clone, Debug, Default, PartialEq)]
29pub struct Filetime {
30    /// Number of 100th nanoseconds since January 1, 1601 (UTC).
31    pub timestamp: u64,
32}
33
34impl Filetime {
35    /// Creates a new timestamp.
36    pub fn new(timestamp: u64) -> Self {
37        Self {
38            timestamp: timestamp,
39        }
40    }
41
42    /// Reads a timestamp from a byte sequence.
43    ///
44    /// A FILETIME timestamp is stored in a structure that consists of
45    /// 2 x 32-bit integers, both presumed to be unsigned.
46    pub fn from_bytes(data: &[u8]) -> Self {
47        let lower_32bit: u32 = bytes_to_u32_le!(data, 0);
48        let upper_32bit: u32 = bytes_to_u32_le!(data, 4);
49        Self {
50            timestamp: (upper_32bit as u64) << 32 | (lower_32bit as u64),
51        }
52    }
53
54    /// Retrieves an ISO 8601 string representation of the timestamp.
55    pub fn to_iso8601_string(&self) -> String {
56        let fraction: u64 = self.timestamp % 10000000;
57        let number_of_seconds: u64 = self.timestamp / 10000000;
58        let (days, hours, minutes, seconds): (i64, u8, u8, u8) =
59            get_time_values(number_of_seconds as i64);
60        let (year, month, day_of_month): (i16, u8, u8) = get_date_values(days, &FILETIME_EPOCH);
61        format!(
62            "{:04}-{:02}-{:02}T{:02}:{:02}:{:02}.{:07}",
63            year, month, day_of_month, hours, minutes, seconds, fraction
64        )
65    }
66}
67
68impl fmt::Display for Filetime {
69    /// Formats the timestamp for display.
70    fn fmt(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
71        write!(
72            formatter,
73            "{} (0x{:08x}:0x{:08x})",
74            self.to_iso8601_string(),
75            self.timestamp >> 32,
76            self.timestamp & 0xffffffff,
77        )
78    }
79}
80
81#[cfg(test)]
82mod tests {
83    use super::*;
84
85    #[test]
86    fn test_filetime_from_bytes() {
87        let test_data: [u8; 8] = [0xce, 0x17, 0x0a, 0x3d, 0x62, 0x3a, 0xcb, 0x01];
88
89        let test_struct: Filetime = Filetime::from_bytes(&test_data);
90        assert_eq!(test_struct.timestamp, 0x01cb3a623d0a17ce);
91    }
92
93    #[test]
94    fn test_filetime_to_iso8601_string() {
95        let test_struct: Filetime = Filetime::new(0x01cb3a623d0a17ce);
96
97        let string: String = test_struct.to_iso8601_string();
98        assert_eq!(string.as_str(), "2010-08-12T21:06:31.5468750");
99    }
100}