time_format/
lib.rs

1//! # time-format
2//!
3//! This crate does only one thing: format a Unix timestamp.
4//!
5//! ## Splitting a timestamp into its components
6//!
7//! The `components_utc()` function returns the components of a timestamp:
8//!
9//! ```rust
10//! let ts = time_format::now().unwrap();
11//!
12//! let components = time_format::components_utc(ts).unwrap();
13//! ```
14//!
15//! Components are `sec`, `min`, `hour`, `month_day`, `month`, `year`, `week_day` and `year_day`.
16//!
17//! ## Formatting a timestamp
18//!
19//! The `strftime_utc()` function formats a timestamp, using the same format as the `strftime()` function of the standard C library.
20//!
21//! ```rust
22//! let ts = time_format::now().unwrap();
23//!
24//! let s = time_format::strftime_utc("%Y-%m-%d", ts).unwrap();
25//! ```
26//!
27//! ## That's it
28//!
29//! If you need a minimal crate to get timestamps and perform basic operations on them, check out [coarsetime](https://crates.io/crates/coarsetime).
30
31use std::{
32    convert::TryInto,
33    ffi::CString,
34    fmt,
35    mem::MaybeUninit,
36    os::raw::{c_char, c_int, c_long},
37};
38
39#[allow(non_camel_case_types)]
40type time_t = i64;
41
42/// A UNIX timestamp.
43pub type TimeStamp = i64;
44
45#[repr(C)]
46#[derive(Debug, Copy, Clone)]
47struct tm {
48    pub tm_sec: c_int,
49    pub tm_min: c_int,
50    pub tm_hour: c_int,
51    pub tm_mday: c_int,
52    pub tm_mon: c_int,
53    pub tm_year: c_int,
54    pub tm_wday: c_int,
55    pub tm_yday: c_int,
56    pub tm_isdst: c_int,
57    pub tm_gmtoff: c_long,
58    pub tm_zone: *mut c_char,
59}
60
61extern "C" {
62    fn gmtime_r(ts: *const time_t, tm: *mut tm) -> *mut tm;
63    fn strftime(s: *mut c_char, maxsize: usize, format: *const c_char, timeptr: *const tm)
64        -> usize;
65}
66
67#[derive(Debug, Clone, Copy, Eq, PartialEq)]
68pub enum Error {
69    TimeError,
70    FormatError,
71}
72
73impl fmt::Display for Error {
74    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
75        match self {
76            Error::TimeError => write!(f, "Time error"),
77            Error::FormatError => write!(f, "Format error"),
78        }
79    }
80}
81
82impl std::error::Error for Error {}
83
84/// Time components.
85#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)]
86pub struct Components {
87    /// Second.
88    pub sec: u8,
89    /// Minute.
90    pub min: u8,
91    /// Hour.
92    pub hour: u8,
93    /// Day of month.
94    pub month_day: u8,
95    /// Month - January is 1, December is 12.
96    pub month: u8,
97    /// Year.
98    pub year: i16,
99    /// Day of week.
100    pub week_day: u8,
101    /// Day of year.    
102    pub year_day: u16,
103}
104
105/// Split a timestamp into its components.
106pub fn components_utc(ts_seconds: TimeStamp) -> Result<Components, Error> {
107    let mut tm = MaybeUninit::<tm>::uninit();
108    if unsafe { gmtime_r(&ts_seconds, tm.as_mut_ptr() as *mut tm) }.is_null() {
109        return Err(Error::TimeError);
110    }
111    let tm = unsafe { tm.assume_init() };
112    Ok(Components {
113        sec: tm.tm_sec as _,
114        min: tm.tm_min as _,
115        hour: tm.tm_hour as _,
116        month_day: tm.tm_mday as _,
117        month: (1 + tm.tm_mon) as _,
118        year: (1900 + tm.tm_year) as _,
119        week_day: tm.tm_wday as _,
120        year_day: tm.tm_yday as _,
121    })
122}
123
124/// Return the current UNIX timestamp in seconds.
125pub fn now() -> Result<TimeStamp, Error> {
126    std::time::SystemTime::now()
127        .duration_since(std::time::UNIX_EPOCH)
128        .map_err(|_| Error::TimeError)?
129        .as_secs()
130        .try_into()
131        .map_err(|_| Error::TimeError)
132}
133
134/// Return the current time in the specified format, in the UTC time zone.
135/// The time is assumed to be the number of seconds since the Epoch.
136pub fn strftime_utc(format: impl AsRef<str>, ts_seconds: TimeStamp) -> Result<String, Error> {
137    let format = format.as_ref();
138    let mut tm = MaybeUninit::<tm>::uninit();
139    if unsafe { gmtime_r(&ts_seconds, tm.as_mut_ptr() as *mut tm) }.is_null() {
140        return Err(Error::TimeError);
141    }
142    let tm = unsafe { tm.assume_init() };
143
144    let format_len = format.len();
145    let format = CString::new(format).map_err(|_| Error::FormatError)?;
146    let mut buf_size = format_len;
147    let mut buf: Vec<u8> = vec![0; buf_size];
148    loop {
149        let len = unsafe {
150            strftime(
151                buf.as_mut_ptr() as *mut c_char,
152                buf_size,
153                format.as_ptr() as *const c_char,
154                &tm,
155            )
156        };
157        if len == 0 {
158            buf_size *= 2;
159            buf.resize(buf_size, 0);
160        } else {
161            buf.truncate(len);
162            return String::from_utf8(buf).map_err(|_| Error::FormatError);
163        }
164    }
165}