/*
* meli - melib POSIX libc time interface
*
* Copyright 2020 Manos Pitsidianakis
*
* This file is part of meli.
*
* meli is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* meli is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with meli. If not, see .
*/
use std::convert::TryInto;
use std::ffi::{CStr, CString};
use crate::error::Result;
pub type UnixTimestamp = u64;
use libc::{timeval, timezone};
extern "C" {
fn strptime(
s: *const ::std::os::raw::c_char,
format: *const ::std::os::raw::c_char,
tm: *mut ::libc::tm,
) -> *const ::std::os::raw::c_char;
fn strftime(
s: *mut ::std::os::raw::c_char,
max: ::libc::size_t,
format: *const ::std::os::raw::c_char,
tm: *const ::libc::tm,
) -> ::libc::size_t;
fn mktime(tm: *const ::libc::tm) -> ::libc::time_t;
fn localtime_r(timep: *const ::libc::time_t, tm: *mut ::libc::tm) -> *mut ::libc::tm;
fn gettimeofday(tv: *mut timeval, tz: *mut timezone) -> i32;
}
pub fn timestamp_to_string(timestamp: UnixTimestamp, fmt: Option<&str>) -> String {
let mut new_tm: ::libc::tm = unsafe { std::mem::zeroed() };
unsafe {
let i: i64 = timestamp.try_into().unwrap_or(0);
localtime_r(&i as *const i64, &mut new_tm as *mut ::libc::tm);
}
let fmt = fmt
.map(|slice| CString::new(slice))
.map(|res| res.ok())
.and_then(|opt| opt);
let format: &CStr = if let Some(ref s) = fmt {
&s
} else {
unsafe { CStr::from_bytes_with_nul_unchecked(b"%a, %d %b %Y %T %z\0") }
};
let mut vec: [u8; 256] = [0; 256];
let ret = unsafe {
strftime(
vec.as_mut_ptr() as *mut _,
256,
format.as_ptr(),
&new_tm as *const _,
)
};
String::from_utf8_lossy(&vec[0..ret]).into_owned()
}
fn tm_to_secs(tm: ::libc::tm) -> std::result::Result {
let mut is_leap = false;
let mut year = tm.tm_year;
let mut month = tm.tm_mon;
if month >= 12 || month < 0 {
let mut adj = month / 12;
month %= 12;
if month < 0 {
adj -= 1;
month += 12;
}
year += adj;
}
let mut t = year_to_secs(year.into(), &mut is_leap)?;
t += month_to_secs(month.try_into().unwrap_or(0), is_leap);
t += 86400 * (tm.tm_mday - 1) as i64;
t += 3600 * (tm.tm_hour) as i64;
t += 60 * (tm.tm_min) as i64;
t += tm.tm_sec as i64;
Ok(t)
}
fn year_to_secs(year: i64, is_leap: &mut bool) -> std::result::Result {
if year < -100 {
/* Sorry time travelers. */
return Err(());
}
if year - 2 <= 136 {
let y = year;
let mut leaps = (y - 68) >> 2;
if (y - 68) & 3 == 0 {
leaps -= 1;
*is_leap = true;
} else {
*is_leap = false;
}
return Ok((31536000 * (y - 70) + 86400 * leaps)
.try_into()
.unwrap_or(0));
}
let cycles = (year - 100) / 400;
let centuries;
let mut leaps;
let mut rem;
rem = (year - 100) % 400;
if rem == 0 {
*is_leap = true;
centuries = 0;
leaps = 0;
} else {
if rem >= 200 {
if rem >= 300 {
centuries = 3;
rem -= 300;
} else {
centuries = 2;
rem -= 200;
}
} else {
if rem >= 100 {
centuries = 1;
rem -= 100;
} else {
centuries = 0;
}
}
if rem == 0 {
*is_leap = false;
leaps = 0;
} else {
leaps = rem / 4;
rem %= 4;
*is_leap = rem == 0;
}
}
leaps += 97 * cycles + 24 * centuries - if *is_leap { 1 } else { 0 };
return Ok(match (year - 100).overflowing_mul(31536000) {
(_, true) => return Err(()),
(res, false) => res + leaps * 86400 + 946684800 + 86400,
});
}
fn month_to_secs(month: usize, is_leap: bool) -> i64 {
const SECS_THROUGH_MONTH: [i64; 12] = [
0,
31 * 86400,
59 * 86400,
90 * 86400,
120 * 86400,
151 * 86400,
181 * 86400,
212 * 86400,
243 * 86400,
273 * 86400,
304 * 86400,
334 * 86400,
];
let mut t = SECS_THROUGH_MONTH[month];
if is_leap && month >= 2 {
t += 86400;
}
return t;
}
pub fn rfc822_to_timestamp(s: T) -> Result
where
T: Into>,
{
let s = CString::new(s)?;
let mut new_tm: ::libc::tm = unsafe { std::mem::zeroed() };
for fmt in &[
&b"%a, %e %h %Y %H:%M:%S \0"[..],
&b"%e %h %Y %H:%M:%S \0"[..],
] {
unsafe {
let fmt = CStr::from_bytes_with_nul_unchecked(fmt);
let ret = strptime(s.as_ptr(), fmt.as_ptr(), &mut new_tm as *mut _);
if ret.is_null() {
continue;
}
let rest = CStr::from_ptr(ret);
let tm_gmtoff = if rest.to_bytes().len() > 4
&& rest.to_bytes().is_ascii()
&& rest.to_bytes()[1..5].iter().all(u8::is_ascii_digit)
{
let offset = std::str::from_utf8_unchecked(&rest.to_bytes()[0..5]);
if let (Ok(mut hr_offset), Ok(mut min_offset)) =
(offset[1..3].parse::(), offset[3..5].parse::())
{
if rest.to_bytes()[0] == b'-' {
hr_offset = -hr_offset;
min_offset = -min_offset;
}
hr_offset * 60 * 60 + min_offset * 60
} else {
0
}
} else {
let rest = if rest.to_bytes().starts_with(b"(") && rest.to_bytes().ends_with(b")") {
&rest.to_bytes()[1..rest.to_bytes().len() - 1]
} else {
rest.to_bytes()
};
if let Ok(idx) = TIMEZONE_ABBR.binary_search_by(|probe| probe.0.cmp(rest)) {
let (hr_offset, min_offset) = debug!(TIMEZONE_ABBR[idx]).1;
(hr_offset as i64) * 60 * 60 + (min_offset as i64) * 60
} else {
0
}
};
return Ok(tm_to_secs(new_tm)
.map(|res| (res - tm_gmtoff) as u64)
.unwrap_or(0));
}
}
return Ok(0);
}
pub fn rfc3339_to_timestamp(s: T) -> Result
where
T: Into>,
{
let s = CString::new(s)?;
let mut new_tm: ::libc::tm = unsafe { std::mem::zeroed() };
for fmt in &[&b"%Y-%m-%dT%H:%M:%S\0"[..], &b"%Y-%m-%d\0"[..]] {
unsafe {
let fmt = CStr::from_bytes_with_nul_unchecked(fmt);
let ret = strptime(s.as_ptr(), fmt.as_ptr(), &mut new_tm as *mut _);
if ret.is_null() {
continue;
}
let rest = CStr::from_ptr(ret);
let tm_gmtoff = if rest.to_bytes().len() > 4
&& rest.to_bytes().is_ascii()
&& rest.to_bytes()[1..3].iter().all(u8::is_ascii_digit)
&& rest.to_bytes()[4..6].iter().all(u8::is_ascii_digit)
{
let offset = std::str::from_utf8_unchecked(&rest.to_bytes()[0..6]);
if let (Ok(mut hr_offset), Ok(mut min_offset)) =
(offset[1..3].parse::(), offset[4..6].parse::())
{
if rest.to_bytes()[0] == b'-' {
hr_offset = -hr_offset;
min_offset = -min_offset;
}
hr_offset * 60 * 60 + min_offset * 60
} else {
0
}
} else {
let rest = if rest.to_bytes().starts_with(b"(") && rest.to_bytes().ends_with(b")") {
&rest.to_bytes()[1..rest.to_bytes().len() - 1]
} else {
rest.to_bytes()
};
if let Ok(idx) = TIMEZONE_ABBR.binary_search_by(|probe| probe.0.cmp(rest)) {
let (hr_offset, min_offset) = debug!(TIMEZONE_ABBR[idx]).1;
(hr_offset as i64) * 60 * 60 + (min_offset as i64) * 60
} else {
0
}
};
return Ok(tm_to_secs(new_tm)
.map(|res| (res - tm_gmtoff) as u64)
.unwrap_or(0));
}
}
return Ok(0);
}
// FIXME: Handle non-local timezone?
pub fn timestamp_from_string(s: T, fmt: &str) -> Result