Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(time_display): time should use timezone settings #1327

Open
wants to merge 9 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
42 changes: 42 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 2 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,8 @@ name = "eza"
[dependencies]
rayon = "1.10.0"
chrono = { version = "0.4.34", default-features = false, features = ["clock"] }
chrono-tz = "0.10.0"
iana-time-zone = "0.1.58"
nu-ansi-term = { version = "0.50.1", features = [
"serde",
"derive_serde_style",
Expand Down
4 changes: 2 additions & 2 deletions src/fs/dir.rs
Original file line number Diff line number Diff line change
Expand Up @@ -108,7 +108,7 @@ pub struct Files<'dir, 'ig> {
total_size: bool,
}

impl<'dir, 'ig> Files<'dir, 'ig> {
impl<'dir> Files<'dir, '_> {
fn parent(&self) -> PathBuf {
// We can’t use `Path#parent` here because all it does is remove the
// last path component, which is no good for us if the path is
Expand Down Expand Up @@ -180,7 +180,7 @@ enum DotsNext {
Files,
}

impl<'dir, 'ig> Iterator for Files<'dir, 'ig> {
impl<'dir> Iterator for Files<'dir, '_> {
type Item = File<'dir>;

fn next(&mut self) -> Option<Self::Item> {
Expand Down
2 changes: 1 addition & 1 deletion src/fs/feature/xattr.rs
Original file line number Diff line number Diff line change
Expand Up @@ -672,7 +672,7 @@ struct BorrowedWriter<'a> {
pub buffer: &'a mut Vec<u8>,
}

impl<'a> io::Write for BorrowedWriter<'a> {
impl io::Write for BorrowedWriter<'_> {
fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
self.buffer.write(buf)
}
Expand Down
2 changes: 1 addition & 1 deletion src/fs/file.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1007,7 +1007,7 @@ pub enum FileTarget<'dir> {
// error — we just display the error message and move on.
}

impl<'dir> FileTarget<'dir> {
impl FileTarget<'_> {
/// Whether this link doesn’t lead to a file, for whatever reason. This
/// gets used to determine how to highlight the link in grid views.
pub fn is_broken(&self) -> bool {
Expand Down
2 changes: 1 addition & 1 deletion src/info/sources.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ use std::path::PathBuf;

use crate::fs::File;

impl<'a> File<'a> {
impl File<'_> {
/// For this file, return a vector of alternate file paths that, if any of
/// them exist, mean that *this* file should be coloured as “compiled”.
///
Expand Down
2 changes: 1 addition & 1 deletion src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -248,7 +248,7 @@ fn git_repos(options: &Options, args: &[&OsStr]) -> bool {
}
}

impl<'args> Exa<'args> {
impl Exa<'_> {
/// # Errors
///
/// Will return `Err` if printing to stderr fails.
Expand Down
6 changes: 3 additions & 3 deletions src/options/parser.rs
Original file line number Diff line number Diff line change
Expand Up @@ -399,7 +399,7 @@ pub struct MatchedFlags<'args> {
strictness: Strictness,
}

impl<'a> MatchedFlags<'a> {
impl MatchedFlags<'_> {
/// Whether the given argument was specified.
/// Returns `true` if it was, `false` if it wasn’t, and an error in
/// strict mode if it was specified more than once.
Expand Down Expand Up @@ -554,14 +554,14 @@ impl fmt::Display for ParseError {
fn os_str_to_bytes(s: &OsStr) -> &[u8] {
use std::os::unix::ffi::OsStrExt;

return s.as_bytes();
s.as_bytes()
}

#[cfg(unix)]
fn bytes_to_os_str(b: &[u8]) -> &OsStr {
use std::os::unix::ffi::OsStrExt;

return OsStr::from_bytes(b);
OsStr::from_bytes(b)
}

#[cfg(windows)]
Expand Down
2 changes: 1 addition & 1 deletion src/output/details.rs
Original file line number Diff line number Diff line change
Expand Up @@ -488,7 +488,7 @@ pub struct TableIter<'a> {
tree_trunk: TreeTrunk,
}

impl<'a> Iterator for TableIter<'a> {
impl Iterator for TableIter<'_> {
type Item = TextCell;

fn next(&mut self) -> Option<Self::Item> {
Expand Down
4 changes: 2 additions & 2 deletions src/output/file_name.rs
Original file line number Diff line number Diff line change
Expand Up @@ -165,7 +165,7 @@ pub struct FileName<'a, 'dir, C> {
mount_style: MountStyle,
}

impl<'a, 'dir, C> FileName<'a, 'dir, C> {
impl<C> FileName<'_, '_, C> {
/// Sets the flag on this file name to display link targets with an
/// arrow followed by their path.
pub fn with_link_paths(mut self) -> Self {
Expand All @@ -187,7 +187,7 @@ impl<'a, 'dir, C> FileName<'a, 'dir, C> {
}
}

impl<'a, 'dir, C: Colours> FileName<'a, 'dir, C> {
impl<C: Colours> FileName<'_, '_, C> {
/// Paints the name of the file using the colours, resulting in a vector
/// of coloured cells that can be printed to the terminal.
///
Expand Down
2 changes: 1 addition & 1 deletion src/output/grid.rs
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ pub struct Render<'a> {
pub filter: &'a FileFilter,
}

impl<'a> Render<'a> {
impl Render<'_> {
pub fn render<W: Write>(mut self, w: &mut W) -> io::Result<()> {
self.filter.sort_files(&mut self.files);

Expand Down
62 changes: 29 additions & 33 deletions src/output/render/permissions.rs
Original file line number Diff line number Diff line change
Expand Up @@ -19,50 +19,46 @@ pub trait PermissionsPlusRender {
impl PermissionsPlusRender for Option<f::PermissionsPlus> {
#[cfg(unix)]
fn render<C: Colours + FiletypeColours>(&self, colours: &C) -> TextCell {
match self {
Some(p) => {
let mut chars = vec![p.file_type.render(colours)];
let permissions = p.permissions;
chars.extend(Some(permissions).render(colours, p.file_type.is_regular_file()));

if p.xattrs {
chars.push(colours.attribute().paint("@"));
}

// As these are all ASCII characters, we can guarantee that they’re
// all going to be one character wide, and don’t need to compute the
// cell’s display width.
TextCell {
width: DisplayWidth::from(chars.len()),
contents: chars.into(),
}
if let Some(p) = self {
let mut chars = vec![p.file_type.render(colours)];
let permissions = p.permissions;
chars.extend(Some(permissions).render(colours, p.file_type.is_regular_file()));

if p.xattrs {
chars.push(colours.attribute().paint("@"));
}

// As these are all ASCII characters, we can guarantee that they’re
// all going to be one character wide, and don’t need to compute the
// cell’s display width.
TextCell {
width: DisplayWidth::from(chars.len()),
contents: chars.into(),
}
None => {
let chars: Vec<_> = iter::repeat(colours.dash().paint("-")).take(10).collect();
TextCell {
width: DisplayWidth::from(chars.len()),
contents: chars.into(),
}
} else {
let chars: Vec<_> = iter::repeat(colours.dash().paint("-")).take(10).collect();
TextCell {
width: DisplayWidth::from(chars.len()),
contents: chars.into(),
}
}
}

#[cfg(windows)]
fn render<C: Colours + FiletypeColours>(&self, colours: &C) -> TextCell {
match self {
Some(p) => {
let mut chars = vec![p.attributes.render_type(colours)];
chars.extend(p.attributes.render(colours));
if let Some(p) = self {
let mut chars = vec![p.attributes.render_type(colours)];
chars.extend(p.attributes.render(colours));

TextCell {
width: DisplayWidth::from(chars.len()),
contents: chars.into(),
}
TextCell {
width: DisplayWidth::from(chars.len()),
contents: chars.into(),
}
None => TextCell {
} else {
TextCell {
width: DisplayWidth::from(0),
contents: vec![].into(),
},
}
}
}
}
Expand Down
24 changes: 20 additions & 4 deletions src/output/render/times.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,23 +8,39 @@ use crate::output::cell::TextCell;
use crate::output::time::TimeFormat;

use chrono::prelude::*;
use chrono_tz::Tz;
use nu_ansi_term::Style;

pub trait Render {
fn render(self, style: Style, time_offset: FixedOffset, time_format: TimeFormat) -> TextCell;
fn render(self, style: Style, time_format: TimeFormat) -> TextCell;
}

impl Render for Option<NaiveDateTime> {
fn render(self, style: Style, time_offset: FixedOffset, time_format: TimeFormat) -> TextCell {
let datestamp = if let Some(time) = self {
fn render(self, style: Style, time_format: TimeFormat) -> TextCell {
let datestamp = if let Ok(timezone_str) = iana_time_zone::get_timezone() {
let timezone: Tz = timezone_str
.parse()
.unwrap_or_else(|_| panic!("The timezone cannot be parsed: {timezone_str}"));
if let Some(time) = self {
let time_offset = timezone.offset_from_utc_datetime(&time).fix();
time_format.format(&DateTime::<FixedOffset>::from_naive_utc_and_offset(
time,
time_offset,
))
} else {
String::from("-")
}
} else if let Some(time) = self {
// This is the next best thing, use the timezone now, instead of at the time of the
// timestamp.
let time_offset: FixedOffset = *Local::now().offset();
time_format.format(&DateTime::<FixedOffset>::from_naive_utc_and_offset(
time,
time_offset,
))
} else {
String::from("-")
};

TextCell::paint(style, datestamp)
}
}
7 changes: 0 additions & 7 deletions src/output/table.rs
Original file line number Diff line number Diff line change
Expand Up @@ -363,9 +363,6 @@ impl Default for TimeTypes {
///
/// Any environment field should be able to be mocked up for test runs.
pub struct Environment {
/// The computer’s current time offset, determined from time zone.
time_offset: FixedOffset,

/// Localisation rules for formatting numbers.
numeric: locale::Numeric,

Expand All @@ -381,16 +378,13 @@ impl Environment {
}

fn load_all() -> Self {
let time_offset = *Local::now().offset();

let numeric =
locale::Numeric::load_user_locale().unwrap_or_else(|_| locale::Numeric::english());

#[cfg(unix)]
let users = Mutex::new(UsersCache::new());

Self {
time_offset,
numeric,
#[cfg(unix)]
users,
Expand Down Expand Up @@ -568,7 +562,6 @@ impl<'a> Table<'a> {
} else {
self.theme.ui.date.unwrap_or_default()
},
self.env.time_offset,
self.time_format.clone(),
),
}
Expand Down
30 changes: 30 additions & 0 deletions src/output/time.rs
Original file line number Diff line number Diff line change
Expand Up @@ -196,4 +196,34 @@ mod test {
.all(|string| UnicodeWidthStr::width(string.as_str()) == max_month_width)
);
}

#[test]
fn display_timestamp_correctly_in_timezone_with_dst() {
let timezone_str = "Europe/Berlin";
let timezone: chrono_tz::Tz = timezone_str.parse().unwrap();
let time = DateTime::<Utc>::from_timestamp(1685867700, 0)
.unwrap()
.naive_utc();

let fixed_offset = timezone.offset_from_utc_datetime(&time).fix();
let real_converted = DateTime::<FixedOffset>::from_naive_utc_and_offset(time, fixed_offset);
let formatted = full(&real_converted);
let expected = "2023-06-04 10:35:00.000000000 +0200";
assert_eq!(expected, formatted);
}

#[test]
fn display_timestamp_correctly_in_timezone_without_dst() {
let timezone_str = "Europe/Berlin";
let timezone: chrono_tz::Tz = timezone_str.parse().unwrap();
let time = DateTime::<Utc>::from_timestamp(1699090500, 0)
.unwrap()
.naive_utc();

let fixed_offset = timezone.offset_from_utc_datetime(&time).fix();
let real_converted = DateTime::<FixedOffset>::from_naive_utc_and_offset(time, fixed_offset);
let formatted = full(&real_converted);
let expected = "2023-11-04 10:35:00.000000000 +0100";
assert_eq!(expected, formatted);
}
}
Loading
Loading