melib/datetime: add timestamp_to_string_utc

Tests were using `timestamp_to_string` which in turn uses `localtime_r`
which assumes the local machine's time zone. Use gmtime_r instead.

Fixes #252
pull/253/head
Manos Pitsidianakis 2023-07-09 18:50:35 +03:00
parent c2ed3e283f
commit d93ee413a7
Signed by: Manos Pitsidianakis
GPG Key ID: 7729C7707F7E09D0
4 changed files with 42 additions and 18 deletions

View File

@ -25,7 +25,7 @@ use std::collections::VecDeque;
use crate::{
search::*,
utils::datetime::{formats::IMAP_DATE, timestamp_to_string},
utils::datetime::{formats::IMAP_DATE, timestamp_to_string_utc},
};
mod private {
@ -165,25 +165,25 @@ impl ToImapSearch for Query {
Q(Before(t)) => {
space_pad!(s);
s.push_str("BEFORE ");
s.push_str(&timestamp_to_string(*t, Some(IMAP_DATE), true));
s.push_str(&timestamp_to_string_utc(*t, Some(IMAP_DATE), true));
}
Q(After(t)) => {
space_pad!(s);
s.push_str("SINCE ");
s.push_str(&timestamp_to_string(*t, Some(IMAP_DATE), true));
s.push_str(&timestamp_to_string_utc(*t, Some(IMAP_DATE), true));
}
Q(Between(t1, t2)) => {
space_pad!(s);
s.push_str("(SINCE ");
s.push_str(&timestamp_to_string(*t1, Some(IMAP_DATE), true));
s.push_str(&timestamp_to_string_utc(*t1, Some(IMAP_DATE), true));
s.push_str(" BEFORE ");
s.push_str(&timestamp_to_string(*t2, Some(IMAP_DATE), true));
s.push_str(&timestamp_to_string_utc(*t2, Some(IMAP_DATE), true));
s.push(')');
}
Q(On(t)) => {
space_pad!(s);
s.push_str("ON ");
s.push_str(&timestamp_to_string(*t, Some(IMAP_DATE), true));
s.push_str(&timestamp_to_string_utc(*t, Some(IMAP_DATE), true));
}
Q(InReplyTo(t)) => {
space_pad!(s);
@ -281,8 +281,8 @@ mod tests {
);
assert_eq!(
&timestamp_to_string(1685739600, Some(IMAP_DATE), true),
"03-Jun-2023"
&timestamp_to_string_utc(1685739600, Some(IMAP_DATE), true),
"02-Jun-2023"
);
let (_, q) = query()
@ -290,7 +290,7 @@ mod tests {
.unwrap();
assert_eq!(
&q.to_imap_search(),
r#"BEFORE 04-Jun-2023 FROM "user@example.org""#
r#"BEFORE 03-Jun-2023 FROM "user@example.org""#
);
let (_, q) = query()
.parse_complete(r#"subject:"wah ah ah" or (from:Manos and from:Sia)"#)

View File

@ -585,7 +585,7 @@ impl From<crate::search::Query> for Filter<EmailFilterCondition, EmailObject> {
fn from(val: crate::search::Query) -> Self {
let mut ret = Self::Condition(EmailFilterCondition::new().into());
fn rec(q: &crate::search::Query, f: &mut Filter<EmailFilterCondition, EmailObject>) {
use datetime::{formats::RFC3339_DATE, timestamp_to_string};
use datetime::{formats::RFC3339_DATE, timestamp_to_string_utc};
use crate::search::Query::*;
@ -614,26 +614,26 @@ impl From<crate::search::Query> for Filter<EmailFilterCondition, EmailObject> {
Before(t) => {
*f = Filter::Condition(
EmailFilterCondition::new()
.before(timestamp_to_string(*t, Some(RFC3339_DATE), true))
.before(timestamp_to_string_utc(*t, Some(RFC3339_DATE), true))
.into(),
);
}
After(t) => {
*f = Filter::Condition(
EmailFilterCondition::new()
.after(timestamp_to_string(*t, Some(RFC3339_DATE), true))
.after(timestamp_to_string_utc(*t, Some(RFC3339_DATE), true))
.into(),
);
}
Between(a, b) => {
*f = Filter::Condition(
EmailFilterCondition::new()
.after(timestamp_to_string(*a, Some(RFC3339_DATE), true))
.after(timestamp_to_string_utc(*a, Some(RFC3339_DATE), true))
.into(),
);
*f &= Filter::Condition(
EmailFilterCondition::new()
.before(timestamp_to_string(*b, Some(RFC3339_DATE), true))
.before(timestamp_to_string_utc(*b, Some(RFC3339_DATE), true))
.into(),
);
}

View File

@ -286,7 +286,7 @@ impl MailBackend for NntpType {
let mut conn = timeout(Some(Duration::from_secs(60 * 16)), connection.lock()).await?;
if let Some(mut latest_article) = latest_article {
let timestamp = latest_article - 10 * 60;
let datetime_str = crate::utils::datetime::timestamp_to_string(
let datetime_str = crate::utils::datetime::timestamp_to_string_utc(
timestamp,
Some("%Y%m%d %H%M%S"),
true,

View File

@ -34,7 +34,7 @@
//! assert_eq!(timestamp, 1578509043);
//!
//! // Convert timestamp back to string
//! let s = timestamp_to_string(timestamp, Some("%Y-%m-%d"), true);
//! let s = timestamp_to_string_utc(timestamp, Some("%Y-%m-%d"), true);
//! assert_eq!(s, "2020-01-08");
//! ```
use std::{
@ -87,6 +87,8 @@ extern "C" {
fn localtime_r(timep: *const libc::time_t, tm: *mut libc::tm) -> *mut libc::tm;
fn gmtime_r(timep: *const libc::time_t, tm: *mut libc::tm) -> *mut libc::tm;
fn gettimeofday(tv: *mut libc::timeval, tz: *mut libc::timezone) -> i32;
}
@ -199,11 +201,21 @@ impl Locale {
}
}
pub fn timestamp_to_string(timestamp: UnixTimestamp, fmt: Option<&str>, posix: bool) -> String {
#[inline]
fn timestamp_to_string_inner(
timestamp: UnixTimestamp,
fmt: Option<&str>,
posix: bool,
local: bool,
) -> String {
let mut new_tm: libc::tm = unsafe { std::mem::zeroed() };
unsafe {
let i: i64 = timestamp.try_into().unwrap_or(0);
localtime_r(std::ptr::addr_of!(i), std::ptr::addr_of_mut!(new_tm));
if local {
localtime_r(std::ptr::addr_of!(i), std::ptr::addr_of_mut!(new_tm));
} else {
gmtime_r(std::ptr::addr_of!(i), std::ptr::addr_of_mut!(new_tm));
}
}
let format: Cow<'_, CStr> = if let Some(cs) = fmt
.map(str::as_bytes)
@ -251,6 +263,18 @@ pub fn timestamp_to_string(timestamp: UnixTimestamp, fmt: Option<&str>, posix: b
String::from_utf8_lossy(&vec[0..ret]).into_owned()
}
/// Return a UNIX epoch timestamp as string in the local timezone, using `fmt`
/// as the format argument passed to `strptime`.
pub fn timestamp_to_string(timestamp: UnixTimestamp, fmt: Option<&str>, posix: bool) -> String {
timestamp_to_string_inner(timestamp, fmt, posix, true)
}
/// Return a UNIX epoch timestamp as string in the UTC/GMT/+00:00 timezone,
/// using `fmt` as the format argument passed to `strptime`.
pub fn timestamp_to_string_utc(timestamp: UnixTimestamp, fmt: Option<&str>, posix: bool) -> String {
timestamp_to_string_inner(timestamp, fmt, posix, false)
}
fn tm_to_secs(tm: libc::tm) -> std::result::Result<i64, ()> {
let mut is_leap = false;
let mut year = tm.tm_year;