melib/datetime: Add Locale struct for error checking
parent
f7cbd9a64d
commit
34e970d922
|
@ -37,13 +37,13 @@
|
|||
//! let s = timestamp_to_string(timestamp, Some("%Y-%m-%d"));
|
||||
//! assert_eq!(s, "2020-01-08");
|
||||
//! ```
|
||||
use crate::error::Result;
|
||||
use crate::error::{Result, ResultIntoMeliError};
|
||||
use std::convert::TryInto;
|
||||
use std::ffi::{CStr, CString};
|
||||
|
||||
pub type UnixTimestamp = u64;
|
||||
|
||||
use libc::{timeval, timezone, uselocale};
|
||||
use libc::{locale_t, timeval, timezone};
|
||||
|
||||
extern "C" {
|
||||
fn strptime(
|
||||
|
@ -66,6 +66,43 @@ extern "C" {
|
|||
fn gettimeofday(tv: *mut timeval, tz: *mut timezone) -> i32;
|
||||
}
|
||||
|
||||
struct Locale {
|
||||
new_locale: locale_t,
|
||||
old_locale: locale_t,
|
||||
}
|
||||
|
||||
impl Drop for Locale {
|
||||
fn drop(&mut self) {
|
||||
unsafe {
|
||||
let _ = libc::uselocale(self.old_locale);
|
||||
libc::freelocale(self.new_locale);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// How to unit test this? Test machine is not guaranteed to have non-english locales.
|
||||
impl Locale {
|
||||
fn new(
|
||||
mask: ::std::os::raw::c_int,
|
||||
locale: *const ::std::os::raw::c_char,
|
||||
base: locale_t,
|
||||
) -> Result<Self> {
|
||||
let new_locale = unsafe { libc::newlocale(mask, locale, base) };
|
||||
if new_locale.is_null() {
|
||||
return Err(nix::Error::last().into());
|
||||
}
|
||||
let old_locale = unsafe { libc::uselocale(new_locale) };
|
||||
if old_locale.is_null() {
|
||||
unsafe { libc::freelocale(new_locale) };
|
||||
return Err(nix::Error::last().into());
|
||||
}
|
||||
Ok(Locale {
|
||||
new_locale,
|
||||
old_locale,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
pub fn timestamp_to_string(timestamp: UnixTimestamp, fmt: Option<&str>) -> String {
|
||||
let mut new_tm: ::libc::tm = unsafe { std::mem::zeroed() };
|
||||
unsafe {
|
||||
|
@ -215,17 +252,16 @@ where
|
|||
unsafe {
|
||||
let fmt = CStr::from_bytes_with_nul_unchecked(fmt);
|
||||
|
||||
let locale = ::libc::newlocale(
|
||||
let ret = {
|
||||
let _with_locale = Locale::new(
|
||||
::libc::LC_TIME,
|
||||
b"C\0".as_ptr() as *const i8,
|
||||
std::ptr::null_mut(),
|
||||
);
|
||||
|
||||
let old_locale = uselocale(locale);
|
||||
let ret = strptime(s.as_ptr(), fmt.as_ptr(), &mut new_tm as *mut _);
|
||||
uselocale(old_locale);
|
||||
|
||||
::libc::freelocale(locale);
|
||||
)
|
||||
.chain_err_summary(|| "Could not set locale for datetime conversion")
|
||||
.chain_err_kind(crate::error::ErrorKind::External)?;
|
||||
strptime(s.as_ptr(), fmt.as_ptr(), &mut new_tm as *mut _)
|
||||
};
|
||||
|
||||
if ret.is_null() {
|
||||
continue;
|
||||
|
@ -278,7 +314,16 @@ where
|
|||
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 _);
|
||||
let ret = {
|
||||
let _with_locale = Locale::new(
|
||||
::libc::LC_TIME,
|
||||
b"C\0".as_ptr() as *const i8,
|
||||
std::ptr::null_mut(),
|
||||
)
|
||||
.chain_err_summary(|| "Could not set locale for datetime conversion")
|
||||
.chain_err_kind(crate::error::ErrorKind::External)?;
|
||||
strptime(s.as_ptr(), fmt.as_ptr(), &mut new_tm as *mut _)
|
||||
};
|
||||
if ret.is_null() {
|
||||
continue;
|
||||
}
|
||||
|
@ -362,6 +407,9 @@ fn test_timestamp() {
|
|||
|
||||
#[test]
|
||||
fn test_rfcs() {
|
||||
if unsafe { libc::setlocale(libc::LC_ALL, b"\0".as_ptr() as _) }.is_null() {
|
||||
println!("Unable to set locale.");
|
||||
}
|
||||
/* Some tests were lazily stolen from https://rachelbythebay.com/w/2013/06/11/time/ */
|
||||
|
||||
assert_eq!(
|
||||
|
|
Loading…
Reference in New Issue