Skip to content

Bug: TimestampMicro and TimestampNano use wrong divisors — off by 1,000× and sub-second precision is silently dropped #48

Description

@ZialeHub

Bug Report

Description

The TryFrom implementations for TimestampMicro and TimestampNano both use incorrect
divisors when converting to Unix seconds before passing to chrono::DateTime::from_timestamp.
Sub-second precision is also silently discarded by passing 0 as the nanoseconds argument.

Type Current divisor Correct divisor Result
TimestampMicro / 1_000 (→ milliseconds) / 1_000_000 (→ seconds) 1,000× too large
TimestampNano / 1_000_000 (→ milliseconds) / 1_000_000_000 (→ seconds) 1,000× too large

Affected Code

timeflow/src/datetime.rs:

// TimestampMicro (~line 448)
chrono::DateTime::from_timestamp(*timestamp / 1_000, 0)
//                                             ^^^^^ should be / 1_000_000

// TimestampNano (~line 464)
chrono::DateTime::from_timestamp(*timestamp / 1_000_000, 0)
//                                             ^^^^^^^^^ should be / 1_000_000_000

Reproduction

  use timeflow::prelude::*;
  use timeflow::timestamp::{TimestampMicro, TimestampNano};

  // Real microsecond epoch for 2024-12-31 22:10:10
  let ts_micro = TimestampMicro::from(1_735_683_010_000_000);
  let dt = DateTime::try_from(ts_micro);
  // Expected: Ok("2024-12-31 22:10:10")
  // Actual:   Err(ParseFromTimestamp(...))

  // Real nanosecond epoch for 2024-12-31 22:10:10
  let ts_nano = TimestampNano::from(1_735_683_010_000_000_000);
  let dt = DateTime::try_from(ts_nano);
  // Expected: Ok("2024-12-31 22:10:10")
  // Actual:   Err(ParseFromTimestamp(...))

Why the existing tests don't catch this

All three test inputs are orders of magnitude smaller than real timestamps in their
advertised units. After dividing by the (wrong) current divisor, they happen to produce
a valid seconds-epoch value for the expected date. Example for TimestampMicro:
1_735_683_010_000 / 1_000 = 1_735_683_010 — a valid seconds value — but
1_735_683_010_000 is itself not a real microsecond timestamp for that date
(it would be 1_735_683_010_000_000).

Fix

  // TimestampMicro
  chrono::DateTime::from_timestamp(*timestamp / 1_000_000, 0)

  // TimestampNano
  chrono::DateTime::from_timestamp(*timestamp / 1_000_000_000, 0)
  
  To also preserve sub-second precision, prefer the dedicated chrono APIs:

  // TimestampMicro
  chrono::DateTime::from_timestamp_micros(*timestamp)

  // TimestampNano
  chrono::DateTime::from_timestamp_nanos(*timestamp) // returns DateTime directly, no Option

Severity

High — all callers supplying genuine micro- or nanosecond timestamps receive a runtime
error or a silently wrong date. Tests must also be updated to use real-scale values.

Metadata

Metadata

Assignees

No one assigned

    Labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions