rfc3339: move timezone offset compatibility code to old time parser

The compatibility code was added to the new rfc3339 code path temporarily so
that the old code path would not be changed before the PMG 8 release.

Now move it to the old time format code to make sure the rfc3339 code path
works as expected. Since we have all the information we need (year, month,
day, hours, minutes, seconds, timezone), there's no need for a workaround in
this code path.

As a side effect of parsing the time format `YYYY-MM-DD HH:MM:SS` in
localtime, it's now possible to parse an rfc3339 compliant format
(except for fractional seconds).

The change needs to be accompanied by one in pmg-api MailTracker.pmg to
keep the time displayed in the GUI the same for the old time format, and
correct for the new rfc3339 format.

Signed-off-by: Mira Limbeck <m.limbeck@proxmox.com>
This commit is contained in:
Mira Limbeck 2024-02-20 11:06:44 +01:00 committed by Stoiko Ivanov
parent e34f84b91f
commit 8a5b28ff16
2 changed files with 59 additions and 29 deletions

View File

@ -9,7 +9,7 @@ use std::io::BufReader;
use std::io::BufWriter; use std::io::BufWriter;
use std::io::Write; use std::io::Write;
use anyhow::{bail, Error}; use anyhow::{bail, Context, Error};
use flate2::read; use flate2::read;
use libc::time_t; use libc::time_t;
@ -90,12 +90,12 @@ fn main() -> Result<(), Error> {
println!( println!(
"# Start: {} ({})", "# Start: {} ({})",
time::strftime(c_str!("%F %T"), &parser.start_tm)?, proxmox_time::strftime_local("%F %T", parser.options.start)?,
parser.options.start parser.options.start
); );
println!( println!(
"# End: {} ({})", "# End: {} ({})",
time::strftime(c_str!("%F %T"), &parser.end_tm)?, proxmox_time::strftime_local("%F %T", parser.options.end)?,
parser.options.end parser.options.end
); );
@ -1681,8 +1681,6 @@ struct Parser {
string_match: bool, string_match: bool,
lines: u64, lines: u64,
timezone_offset: time_t,
} }
impl Parser { impl Parser {
@ -1708,7 +1706,6 @@ impl Parser {
ctime: 0, ctime: 0,
string_match: false, string_match: false,
lines: 0, lines: 0,
timezone_offset: ltime.tm_gmtoff,
}) })
} }
@ -1787,7 +1784,11 @@ impl Parser {
line, line,
self.current_year, self.current_year,
self.current_month, self.current_month,
self.timezone_offset, // use start time for timezone offset in parse_time_no_year rather than the
// timezone offset of the current time
// this is required for cases where current time is in standard time, while start
// time is in summer time or the other way around
self.start_tm.tm_gmtoff,
) { ) {
Some(t) => t, Some(t) => t,
None => continue, None => continue,
@ -1876,7 +1877,7 @@ impl Parser {
&buffer[0..size], &buffer[0..size],
self.current_year, self.current_year,
self.current_month, self.current_month,
self.timezone_offset, self.start_tm.tm_gmtoff,
) { ) {
// found the earliest file in the time frame // found the earliest file in the time frame
if time < self.options.start { if time < self.options.start {
@ -1896,7 +1897,7 @@ impl Parser {
&buffer[0..size], &buffer[0..size],
self.current_year, self.current_year,
self.current_month, self.current_month,
self.timezone_offset, self.start_tm.tm_gmtoff,
) { ) {
if time < self.options.start { if time < self.options.start {
break; break;
@ -1920,12 +1921,14 @@ impl Parser {
} }
if let Some(start) = args.opt_value_from_str::<_, String>(["-s", "--starttime"])? { if let Some(start) = args.opt_value_from_str::<_, String>(["-s", "--starttime"])? {
if let Ok(res) = time::strptime(&start, c_str!("%F %T")) { if let Ok(epoch) = proxmox_time::parse_rfc3339(&start).or_else(|_| {
self.options.start = res.as_utc_to_epoch(); time::date_to_rfc3339(&start).and_then(|s| proxmox_time::parse_rfc3339(&s))
self.start_tm = res; }) {
} else if let Ok(res) = time::strptime(&start, c_str!("%s")) { self.options.start = epoch;
self.options.start = res.as_utc_to_epoch(); self.start_tm = time::Tm::at_local(epoch).context("failed to parse start time")?;
self.start_tm = res; } else if let Ok(epoch) = start.parse::<time_t>() {
self.options.start = epoch;
self.start_tm = time::Tm::at_local(epoch).context("failed to parse start time")?;
} else { } else {
bail!("failed to parse start time"); bail!("failed to parse start time");
} }
@ -1939,12 +1942,14 @@ impl Parser {
} }
if let Some(end) = args.opt_value_from_str::<_, String>(["-e", "--endtime"])? { if let Some(end) = args.opt_value_from_str::<_, String>(["-e", "--endtime"])? {
if let Ok(res) = time::strptime(&end, c_str!("%F %T")) { if let Ok(epoch) = proxmox_time::parse_rfc3339(&end).or_else(|_| {
self.options.end = res.as_utc_to_epoch(); time::date_to_rfc3339(&end).and_then(|s| proxmox_time::parse_rfc3339(&s))
self.end_tm = res; }) {
} else if let Ok(res) = time::strptime(&end, c_str!("%s")) { self.options.end = epoch;
self.options.end = res.as_utc_to_epoch(); self.end_tm = time::Tm::at_local(epoch).context("failed to parse end time")?;
self.end_tm = res; } else if let Ok(epoch) = end.parse::<time_t>() {
self.options.end = epoch;
self.end_tm = time::Tm::at_local(epoch).context("failed to parse end time")?;
} else { } else {
bail!("failed to parse end time"); bail!("failed to parse end time");
} }
@ -2196,11 +2201,11 @@ fn parse_time(
cur_month: i64, cur_month: i64,
timezone_offset: time_t, timezone_offset: time_t,
) -> Option<(time_t, &'_ [u8])> { ) -> Option<(time_t, &'_ [u8])> {
parse_time_with_year(data, timezone_offset) parse_time_with_year(data)
.or_else(|| parse_time_no_year(data, cur_year, cur_month)) .or_else(|| parse_time_no_year(data, cur_year, cur_month, timezone_offset))
} }
fn parse_time_with_year(data: &'_ [u8], timezone_offset: time_t) -> Option<(time_t, &'_ [u8])> { fn parse_time_with_year(data: &'_ [u8]) -> Option<(time_t, &'_ [u8])> {
let mut timestamp_buffer = [0u8; 25]; let mut timestamp_buffer = [0u8; 25];
let count = data.iter().take_while(|b| **b != b' ').count(); let count = data.iter().take_while(|b| **b != b' ').count();
@ -2227,13 +2232,17 @@ fn parse_time_with_year(data: &'_ [u8], timezone_offset: time_t) -> Option<(time
match proxmox_time::parse_rfc3339(unsafe { match proxmox_time::parse_rfc3339(unsafe {
std::str::from_utf8_unchecked(&timestamp_buffer[0..timestamp_len]) std::str::from_utf8_unchecked(&timestamp_buffer[0..timestamp_len])
}) { }) {
// TODO handle timezone offset in old code path instead Ok(ltime) => Some((ltime, data)),
Ok(ltime) => Some((ltime + timezone_offset, data)),
Err(_err) => None, Err(_err) => None,
} }
} }
fn parse_time_no_year(data: &'_ [u8], cur_year: i64, cur_month: i64) -> Option<(time_t, &'_ [u8])> { fn parse_time_no_year(
data: &'_ [u8],
cur_year: i64,
cur_month: i64,
timezone_offset: time_t,
) -> Option<(time_t, &'_ [u8])> {
if data.len() < 15 { if data.len() < 15 {
return None; return None;
} }
@ -2338,7 +2347,7 @@ fn parse_time_no_year(data: &'_ [u8], cur_year: i64, cur_month: i64) -> Option<(
_ => &data[1..], _ => &data[1..],
}; };
Some((ltime, data)) Some((ltime - timezone_offset, data))
} }
type ByteSlice<'a> = &'a [u8]; type ByteSlice<'a> = &'a [u8];

View File

@ -149,7 +149,8 @@ pub fn strftime(format: &CStr, tm: &Tm) -> Result<String, Error> {
/// Wrapper around `strptime(3)` to parse time strings. /// Wrapper around `strptime(3)` to parse time strings.
pub fn strptime(time: &str, format: &CStr) -> Result<Tm, Error> { pub fn strptime(time: &str, format: &CStr) -> Result<Tm, Error> {
let mut out = MaybeUninit::<libc::tm>::uninit(); // zero memory because strptime does not necessarily initialize tm_isdst, tm_gmtoff and tm_zone
let mut out = MaybeUninit::<libc::tm>::zeroed();
let time = CString::new(time).map_err(|_| format_err!("time string contains nul bytes"))?; let time = CString::new(time).map_err(|_| format_err!("time string contains nul bytes"))?;
@ -167,3 +168,23 @@ pub fn strptime(time: &str, format: &CStr) -> Result<Tm, Error> {
Ok(Tm(unsafe { out.assume_init() })) Ok(Tm(unsafe { out.assume_init() }))
} }
pub fn date_to_rfc3339(date: &str) -> Result<String, Error> {
// parse the YYYY-MM-DD HH:MM:SS format for the timezone info
let ltime = strptime(date, c_str!("%F %T"))?;
// strptime assume it is in UTC, but we want to interpret it as local time and get the timezone
// offset (tm_gmtoff) which we can then append to be able to parse it as rfc3339
let ltime = Tm::at_local(ltime.as_utc_to_epoch())?;
let mut s = date.replacen(' ', "T", 1);
if ltime.tm_gmtoff != 0 {
let sign = if ltime.tm_gmtoff < 0 { '-' } else { '+' };
let minutes = (ltime.tm_gmtoff / 60) % 60;
let hours = ltime.tm_gmtoff / (60 * 60);
s.push_str(&format!("{}{:02}:{:02}", sign, hours, minutes));
} else {
s.push('Z');
}
Ok(s)
}