Skip to content

bug: no time information for files before UNIX epoch #1668

@yerlotic

Description

@yerlotic

Info

  • Version: v0.23.4
  • OS: Arch Linux x86_64

Steps to reproduce:

  1. Create files with random timestamps:
    mkdir a && cd a
    touch a -d '1 Jan 1970 00:00'
    touch b -d '2 Jan 1970 00:00'
    touch c -d '00:00'
    touch d -d '20000 years ago 00:00'
  2. Run ls and eza with -l --time-style full-iso:
    1. On ext4 filesystem it produces this output:
      a $ ls -l --time-style full-iso
      total 0
      -rw-r--r-- 1 user user 0 1970-01-01 00:00:00.000000000 +0100 a
      -rw-r--r-- 1 user user 0 1970-01-02 00:00:00.000000000 +0100 b
      -rw-r--r-- 1 user user 0 2025-11-23 00:00:00.000000000 +0000 c
      -rw-r--r-- 1 user user 0 1901-12-13 20:45:52.000000000 +0000 d
      a $ eza -l --time-style full-iso
      .rw-r--r-- 0 user -                                   a
      .rw-r--r-- 0 user 1970-01-01 23:00:00.000000000 +0000 b
      .rw-r--r-- 0 user 2025-11-23 00:00:00.000000000 +0000 c
      .rw-r--r-- 0 user -                                   d
      
    2. On tmpfs (in /tmp):
      a $ ls -l --time-style full-iso
      total 0
      -rw-r--r-- 1 user user 0 1970-01-01 00:00:00.000000000 +0100 a
      -rw-r--r-- 1 user user 0 1970-01-02 00:00:00.000000000 +0100 b
      -rw-r--r-- 1 user user 0 2025-11-23 00:00:00.000000000 +0000 c
      -rw-r--r-- 1 user user 0 -17975-11-23 00:00:00.000000000 -0001 d
      a $ eza -l --time-style full-iso
      .rw-r--r-- 0 user -                                   a
      .rw-r--r-- 0 user 1970-01-01 23:00:00.000000000 +0000 b
      .rw-r--r-- 0 user 2025-11-23 00:00:00.000000000 +0000 c
      .rw-r--r-- 0 user -                                   d
      
  3. Output is different and eza doesn't show time before the unix epoch

Here is code generated by Github Copilot that fixes this issue. It should probably be
reviewed.

    fn systemtime_to_naivedatetime(st: SystemTime) -> Option<NaiveDateTime> {
        // Try the usual case first: SystemTime >= UNIX_EPOCH
        if let Ok(dur) = st.duration_since(SystemTime::UNIX_EPOCH) {
            let secs = i64::try_from(dur.as_secs()).ok()?;
            let nsecs = u32::try_from(dur.subsec_nanos()).ok()?;
            // Convert to local DateTime and return its naive_local representation.
            // timestamp_opt returns a LocalResult; single() yields the DateTime if valid.
            return Local.timestamp_opt(secs, nsecs).single().map(|dt| dt.naive_local());
        }

        // Handle times before the UNIX epoch.
        let dur_before = SystemTime::UNIX_EPOCH.duration_since(st).ok()?;
        let secs = u64::try_from(dur_before.as_secs()).ok()?;
        let nsecs = u32::try_from(dur_before.subsec_nanos()).ok()?;

        // For timestamps before the epoch we need to produce a negative seconds value.
        // If there are subseconds (nsecs > 0) we borrow one second and invert the
        // nanoseconds: timestamp = -(secs + 1) + (1_000_000_000 - nsecs) nanoseconds.
        let (secs_adj, nsecs_adj) = if nsecs == 0 {
            (-(secs as i64), 0u32)
        } else {
            (-(secs as i64) - 1, 1_000_000_000u32 - nsecs)
        };

        Local.timestamp_opt(secs_adj, nsecs_adj).single().map(|dt| dt.naive_local())
    }

Output with this code replacing systemtime_to_naivedatetime() in src/fs/file.rs:

  1. On ext4:
    $ eza -l --time-style full-iso
    .rw-r--r-- 0 user 1970-01-01 00:00:00.000000000 +0000 a
    .rw-r--r-- 0 user 1970-01-02 00:00:00.000000000 +0000 b
    .rw-r--r-- 0 user 2025-11-23 00:00:00.000000000 +0000 c
    .rw-r--r-- 0 user 1901-12-13 20:45:52.000000000 +0000 d
    
  2. On tmpfs:
    $ eza -l --time-style full-iso
    .rw-r--r-- 0 user 1970-01-01 00:00:00.000000000 +0000   a
    .rw-r--r-- 0 user 1970-01-02 00:00:00.000000000 +0000   b
    .rw-r--r-- 0 user 2025-11-23 00:00:00.000000000 +0000   c
    .rw-r--r-- 0 user -17975-11-23 00:00:00.000000000 +0000 d
    

The output still differs from ls in timezone but I'm not sure if it is a bug in ls or eza

Metadata

Metadata

Assignees

No one assigned

    Labels

    errorsSomething isn't working

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions