Skip to content

feat: add timezone-aware DateTime support via optional chrono-tz integration #57

Description

@ZialeHub

Summary

All three timeflow types currently wrap chrono::Naive* variants, meaning they carry no timezone information. This is a deliberate and valid design choice for many use cases (e.g. storing timestamps that are already normalised to UTC, or working with local wall-clock times where timezone context is implicit).

However, it prevents adoption in distributed systems and web applications where UTC storage with local-time display is the standard pattern. Developers in these contexts must fall back to raw chrono for any timezone-aware operation, negating the library's replacement value.

This issue proposes adding lightweight timezone support as an opt-in feature, preserving the current zero-dependency behaviour by default.


Proposed Approach

Feature flag: timezone

Add an optional dependency on chrono-tz behind a timezone feature flag:

[dependencies]
chrono-tz = { version = "0.10", optional = true }

[features]
timezone = ["chrono-tz"]

New methods on DateTime (available with timezone feature)

// Convert to a specific IANA timezone
datetime.in_timezone("Europe/Paris")? -> ZonedDateTime
datetime.in_timezone("America/New_York")? -> ZonedDateTime
datetime.to_utc() -> ZonedDateTime

// Construct from a timezone-aware value
DateTime::now_utc()? -> DateTime         // current time in UTC (not local)
DateTime::now_in("Europe/Paris")? -> DateTime

New ZonedDateTime type

pub struct ZonedDateTime {
    inner: chrono::DateTime<chrono_tz::Tz>,
    format: String,
}

impl ZonedDateTime {
    pub fn timezone(&self) -> &str          // "Europe/Paris"
    pub fn to_naive(&self) -> DateTime      // strip timezone, return local wall time
    pub fn to_utc_naive(&self) -> DateTime  // convert to UTC, strip timezone
    pub fn offset_hours(&self) -> f64       // UTC offset at this instant
}

ZonedDateTime implements Display, Span<DateTimeUnit, i32>, and serde.


Minimal Viable Scope (v1)

If a full ZonedDateTime type is too large for a first iteration, a minimal useful surface would be:

// On DateTime, with the `timezone` feature
datetime.to_utc() -> DateTime              // treat self as local time, convert to UTC
datetime.from_utc_to("Europe/Paris")? -> DateTime  // treat self as UTC, convert to local
DateTime::now_utc()? -> DateTime           // UTC system time

This covers the most common pattern (store UTC, display local) without introducing a new type.


Implementation Notes

  • The timezone feature must not affect the public API or compilation of code that does not use it (#[cfg(feature = "timezone")] guards on all new items).
  • chrono-tz provides IANA timezone database at compile time — no runtime files needed.
  • Invalid timezone strings (e.g. "Invalid/Zone") should return Err(SpanError) rather than panicking.
  • ZonedDateTime serialization should include the timezone name: { "datetime": "2024-10-31 06:32:28", "timezone": "Europe/Paris" }.
  • Consider whether DST transitions during arithmetic (e.g. adding 1 hour across a DST boundary) should produce the correct wall-clock result or the correct elapsed-time result — document the chosen semantics explicitly.

Acceptance Criteria

  • timezone feature added to Cargo.toml with chrono-tz as optional dependency
  • DateTime::now_utc() and DateTime::now_in(tz) implemented
  • DateTime::to_utc() and DateTime::from_utc_to(tz) implemented (minimal v1)
  • Invalid timezone string returns SpanError, not a panic
  • All new items are behind #[cfg(feature = "timezone")]
  • Compiling without the timezone feature produces zero warnings and identical behaviour
  • Serde support for ZonedDateTime (if the full type is implemented)
  • README updated with a timezone usage example and feature flag documentation
  • Tests covering: UTC conversion, DST-crossing arithmetic, invalid timezone string

Metadata

Metadata

Assignees

No one assigned

    Labels

    A-domainbusiness logic / coreB-ideaDiscussion; or implementation attempt, to be reviewed before further work

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions