From 34e970d922ecd6e1cc01dc7d41f405be0dedd871 Mon Sep 17 00:00:00 2001 From: Manos Pitsidianakis Date: Mon, 4 Jan 2021 23:11:47 +0200 Subject: [PATCH] melib/datetime: Add Locale struct for error checking --- melib/src/datetime.rs | 76 +++++++++++++++++++++++++++++++++++-------- 1 file changed, 62 insertions(+), 14 deletions(-) diff --git a/melib/src/datetime.rs b/melib/src/datetime.rs index b66403d53..063bb4c5d 100644 --- a/melib/src/datetime.rs +++ b/melib/src/datetime.rs @@ -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 { + 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( - ::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); + 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; @@ -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!(