crabmail

Static HTML email archive viewer in Rust
git clone git://git.alexwennerberg.com/crabmail
Log | Files | Refs | README | LICENSE

time.rs (4197B) - raw


      1 // This file is licensed under the terms of 0BSD:
      2 //
      3 // Permission to use, copy, modify, and/or distribute this software for any purpose with or without
      4 // fee is hereby granted.
      5 //
      6 // THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS
      7 // SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE
      8 // AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
      9 // WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT,
     10 // NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE
     11 // OF THIS SOFTWARE.
     12 
     13 use std::time::{SystemTime, UNIX_EPOCH};
     14 
     15 const DAYS_IN_MONTHS: [i64; 12] = [31, 30, 31, 30, 31, 31, 30, 31, 30, 31, 31, 29];
     16 
     17 /* 2000-03-01 (mod 400 year, immediately after feb29 */
     18 const LEAP_EPOCH: i64 = 946684800 + 86400 * (31 + 29);
     19 
     20 const DAYS_PER_400Y: i64 = 365 * 400 + 97;
     21 const DAYS_PER_100Y: i64 = 365 * 100 + 24;
     22 const DAYS_PER_4Y: i64 = 365 * 4 + 1;
     23 
     24 #[derive(Debug, Clone)]
     25 pub struct Date {
     26     year: u32,
     27     month: u8,
     28     #[allow(dead_code)]
     29     day_of_year: u32,
     30     day_of_month: u8,
     31     #[allow(dead_code)]
     32     day_of_week: u8,
     33     hour: u8,
     34     minute: u8,
     35     second: u8,
     36 }
     37 
     38 impl Date {
     39     pub fn ymd(&self) -> String {
     40         format!(
     41             "{:04}-{:02}-{:02}",
     42             self.year, self.month, self.day_of_month
     43         )
     44     }
     45     pub fn rfc3339(&self) -> String {
     46         format!(
     47             "{:04}-{:02}-{:02}T{:02}:{:02}:{:02}Z",
     48             self.year, self.month, self.day_of_month, self.hour, self.minute, self.second
     49         )
     50     }
     51 
     52     pub fn from(t: i64) -> Self {
     53         secs_to_date(t)
     54     }
     55 }
     56 
     57 pub fn current_time_rfc3339() -> String {
     58     let current_time = SystemTime::now()
     59         .duration_since(UNIX_EPOCH)
     60         .unwrap()
     61         .as_secs();
     62     return secs_to_date(current_time as i64).rfc3339();
     63 }
     64 // from http://git.musl-libc.org/cgit/musl/tree/src/time/__secs_to_tm.c
     65 // with a slightly different API
     66 // this is a line-for-line copy, not idiomatic rust
     67 // UTC
     68 // TODO handle 64-bit overflow
     69 fn secs_to_date(unixtime: i64) -> Date {
     70     let secs = unixtime - LEAP_EPOCH;
     71     let mut days = secs / 86400;
     72     let mut remsecs = secs % 86400;
     73     if remsecs < 0 {
     74         remsecs += 86400;
     75         days -= 1;
     76     }
     77     let mut wday = (3 + days) % 7;
     78     if wday < 0 {
     79         wday += 7
     80     };
     81     let mut qc_cycles = days / DAYS_PER_400Y;
     82     let mut remdays = days % DAYS_PER_400Y;
     83     if remdays < 0 {
     84         remdays += DAYS_PER_400Y;
     85         qc_cycles -= 1;
     86     }
     87     let mut c_cycles = remdays / DAYS_PER_100Y;
     88     if c_cycles == 4 {
     89         c_cycles -= 1;
     90     }
     91     remdays -= c_cycles * DAYS_PER_100Y;
     92 
     93     let mut q_cycles = remdays / DAYS_PER_4Y;
     94     if q_cycles == 25 {
     95         q_cycles -= 1
     96     }
     97     remdays -= q_cycles * DAYS_PER_4Y;
     98 
     99     let mut remyears = remdays / 365;
    100     if remyears == 4 {
    101         remyears -= 1
    102     }
    103     remdays -= remyears * 365;
    104 
    105     // C
    106     let leap = match remyears == 0 && (q_cycles != 0 || c_cycles == 0) {
    107         true => 1,
    108         false => 0,
    109     };
    110     let mut yday = remdays + 31 + 28 + leap;
    111     if yday >= 365 + leap {
    112         yday -= 365 + leap
    113     }
    114 
    115     let mut years = remyears + 4 * q_cycles + 100 * c_cycles + 400 * qc_cycles;
    116     let mut months: i64 = 0;
    117     while DAYS_IN_MONTHS[months as usize] <= remdays {
    118         remdays -= DAYS_IN_MONTHS[months as usize];
    119         months += 1;
    120     }
    121     // some sort of weird off by 1 error from my musl translation
    122     months += 1;
    123 
    124     if months > 10 {
    125         months -= 12;
    126         years += 1;
    127     }
    128     Date {
    129         year: (years + 2000) as u32,
    130         month: (months + 2) as u8,
    131         day_of_year: yday as u32,
    132         day_of_month: (remdays + 1) as u8,
    133         day_of_week: (wday) as u8,
    134         hour: (remsecs / 3600) as u8,
    135         minute: ((remsecs / 60) % 60) as u8,
    136         second: (remsecs % 60) as u8,
    137     }
    138 }
    139 #[cfg(test)]
    140 mod tests {
    141     use super::*;
    142 
    143     #[test]
    144     fn test_date_conversion() {
    145         assert_eq!("2021-12-22", secs_to_date(1640211435).ymd());
    146         assert_eq!("2022-01-04", secs_to_date(1641321435).ymd())
    147     }
    148 }