axlibc/
strftime.rs

1use alloc::string::String;
2use core::{ffi::c_char, fmt};
3
4use axio::Write;
5
6use crate::ctypes;
7
8pub trait WriteByte: fmt::Write {
9    fn write_u8(&mut self, byte: u8) -> fmt::Result;
10}
11
12struct StringWriter(pub *mut u8, pub usize);
13
14impl Write for StringWriter {
15    fn write(&mut self, buf: &[u8]) -> axerrno::AxResult<usize> {
16        if self.1 > 1 {
17            let copy_size = buf.len().min(self.1 - 1);
18            unsafe {
19                core::ptr::copy_nonoverlapping(buf.as_ptr(), self.0, copy_size);
20                self.1 -= copy_size;
21
22                self.0 = self.0.add(copy_size);
23                *self.0 = 0;
24            }
25        }
26        Ok(buf.len())
27    }
28    fn flush(&mut self) -> axerrno::AxResult {
29        Ok(())
30    }
31}
32
33impl fmt::Write for StringWriter {
34    fn write_str(&mut self, s: &str) -> fmt::Result {
35        // can't fail
36        self.write(s.as_bytes()).unwrap();
37        Ok(())
38    }
39}
40
41impl WriteByte for StringWriter {
42    fn write_u8(&mut self, byte: u8) -> fmt::Result {
43        // can't fail
44        self.write(&[byte]).unwrap();
45        Ok(())
46    }
47}
48
49struct CountingWriter<T> {
50    pub inner: T,
51    pub written: usize,
52}
53
54impl<T> CountingWriter<T> {
55    pub fn new(writer: T) -> Self {
56        Self {
57            inner: writer,
58            written: 0,
59        }
60    }
61}
62
63impl<T: fmt::Write> fmt::Write for CountingWriter<T> {
64    fn write_str(&mut self, s: &str) -> fmt::Result {
65        self.written += s.len();
66        self.inner.write_str(s)
67    }
68}
69
70impl<T: WriteByte> WriteByte for CountingWriter<T> {
71    fn write_u8(&mut self, byte: u8) -> fmt::Result {
72        self.written += 1;
73        self.inner.write_u8(byte)
74    }
75}
76
77impl<T: Write> Write for CountingWriter<T> {
78    fn write(&mut self, buf: &[u8]) -> axerrno::AxResult<usize> {
79        let res = self.inner.write(buf);
80        if let Ok(written) = res {
81            self.written += written;
82        }
83        res
84    }
85
86    fn write_all(&mut self, buf: &[u8]) -> axerrno::AxResult {
87        match self.inner.write_all(buf) {
88            Ok(()) => (),
89            Err(err) => return Err(err),
90        }
91        self.written += buf.len();
92        Ok(())
93    }
94
95    fn flush(&mut self) -> axerrno::AxResult {
96        self.inner.flush()
97    }
98}
99
100unsafe fn strftime_inner<W: WriteByte>(
101    w: W,
102    format: *const c_char,
103    t: *const ctypes::tm,
104) -> ctypes::size_t {
105    #[allow(clippy::unnecessary_cast)]
106    pub unsafe fn inner_strftime<W: WriteByte>(
107        w: &mut W,
108        mut format: *const c_char,
109        t: *const ctypes::tm,
110    ) -> bool {
111        macro_rules! w {
112            (byte $b:expr) => {{
113                if w.write_u8($b).is_err() {
114                    return false;
115                }
116            }};
117            (char $chr:expr) => {{
118                if w.write_char($chr).is_err() {
119                    return false;
120                }
121            }};
122            (recurse $fmt:expr) => {{
123                let mut fmt = String::with_capacity($fmt.len() + 1);
124                fmt.push_str($fmt);
125                fmt.push('\0');
126
127                if !inner_strftime(w, fmt.as_ptr() as *mut c_char, t) {
128                    return false;
129                }
130            }};
131            ($str:expr) => {{
132                if w.write_str($str).is_err() {
133                    return false;
134                }
135            }};
136            ($fmt:expr, $($args:expr),+) => {{
137                // Would use write!() if I could get the length written
138                if write!(w, $fmt, $($args),+).is_err() {
139                    return false;
140                }
141            }};
142        }
143        const WDAYS: [&str; 7] = [
144            "Sunday",
145            "Monday",
146            "Tuesday",
147            "Wednesday",
148            "Thursday",
149            "Friday",
150            "Saturday",
151        ];
152        const MONTHS: [&str; 12] = [
153            "January",
154            "Febuary",
155            "March",
156            "April",
157            "May",
158            "June",
159            "July",
160            "August",
161            "September",
162            "October",
163            "November",
164            "December",
165        ];
166
167        while *format != 0 {
168            if *format as u8 != b'%' {
169                w!(byte * format as u8);
170                format = format.offset(1);
171                continue;
172            }
173
174            format = format.offset(1);
175
176            if *format as u8 == b'E' || *format as u8 == b'O' {
177                // Ignore because these do nothing without locale
178                format = format.offset(1);
179            }
180
181            match *format as u8 {
182                b'%' => w!(byte b'%'),
183                b'n' => w!(byte b'\n'),
184                b't' => w!(byte b'\t'),
185                b'a' => w!(&WDAYS[(*t).tm_wday as usize][..3]),
186                b'A' => w!(WDAYS[(*t).tm_wday as usize]),
187                b'b' | b'h' => w!(&MONTHS[(*t).tm_mon as usize][..3]),
188                b'B' => w!(MONTHS[(*t).tm_mon as usize]),
189                b'C' => {
190                    let mut year = (*t).tm_year / 100;
191                    // Round up
192                    if (*t).tm_year % 100 != 0 {
193                        year += 1;
194                    }
195                    w!("{:02}", year + 19);
196                }
197                b'd' => w!("{:02}", (*t).tm_mday),
198                b'D' => w!(recurse "%m/%d/%y"),
199                b'e' => w!("{:2}", (*t).tm_mday),
200                b'F' => w!(recurse "%Y-%m-%d"),
201                b'H' => w!("{:02}", (*t).tm_hour),
202                b'I' => w!("{:02}", ((*t).tm_hour + 12 - 1) % 12 + 1),
203                b'j' => w!("{:03}", (*t).tm_yday),
204                b'k' => w!("{:2}", (*t).tm_hour),
205                b'l' => w!("{:2}", ((*t).tm_hour + 12 - 1) % 12 + 1),
206                b'm' => w!("{:02}", (*t).tm_mon + 1),
207                b'M' => w!("{:02}", (*t).tm_min),
208                b'p' => w!(if (*t).tm_hour < 12 { "AM" } else { "PM" }),
209                b'P' => w!(if (*t).tm_hour < 12 { "am" } else { "pm" }),
210                b'r' => w!(recurse "%I:%M:%S %p"),
211                b'R' => w!(recurse "%H:%M"),
212                // Nothing is modified in mktime, but the C standard of course requires a mutable pointer ._.
213                b's' => w!("{}", super::mktime(t as *mut ctypes::tm)),
214                b'S' => w!("{:02}", (*t).tm_sec),
215                b'T' => w!(recurse "%H:%M:%S"),
216                b'u' => w!("{}", ((*t).tm_wday + 7 - 1) % 7 + 1),
217                b'U' => w!("{}", ((*t).tm_yday + 7 - (*t).tm_wday) / 7),
218                b'w' => w!("{}", (*t).tm_wday),
219                b'W' => w!("{}", ((*t).tm_yday + 7 - ((*t).tm_wday + 6) % 7) / 7),
220                b'y' => w!("{:02}", (*t).tm_year % 100),
221                b'Y' => w!("{}", (*t).tm_year + 1900),
222                b'z' => w!("+0000"), // TODO
223                b'Z' => w!("UTC"),   // TODO
224                b'+' => w!(recurse "%a %b %d %T %Z %Y"),
225                _ => return false,
226            }
227
228            format = format.offset(1);
229        }
230        true
231    }
232
233    let mut w: CountingWriter<W> = CountingWriter::new(w);
234    if !inner_strftime(&mut w, format, t) {
235        return 0;
236    }
237
238    w.written
239}
240
241/// Convert date and time to a string.
242#[unsafe(no_mangle)]
243#[allow(clippy::unnecessary_cast)] // casting c_char to u8, c_char is either i8 or u8
244pub unsafe extern "C" fn strftime(
245    buf: *mut c_char,
246    size: ctypes::size_t,
247    format: *const c_char,
248    timeptr: *const ctypes::tm,
249) -> ctypes::size_t {
250    let ret = strftime_inner(StringWriter(buf as *mut u8, size), format, timeptr);
251    if ret < size { ret } else { 0 }
252}