Skip to content

feat: add DateRange and DateTimeRange interval types #51

Description

@ZialeHub

Summary

timeflow already provides elapsed and unit_elapsed for computing the distance between two points in time. A natural and frequently needed companion is a first-class range/interval type that encapsulates a start and end, and exposes operations over the interval as a whole.

Without this, consumers must manually carry paired (Date, Date) tuples and re-implement containment, overlap, and iteration logic in every project.


Proposed Types

pub struct DateRange {
    start: Date,   // inclusive
    end: Date,     // inclusive
}

pub struct DateTimeRange {
    start: DateTime,  // inclusive
    end: DateTime,    // inclusive
}

Proposed API

// Construction
let range = DateRange::new(start, end)?;         // Err if start > end
let range = DateRange::new_unchecked(start, end); // no validation

// Querying
range.contains(&date) -> bool
range.overlaps(&other_range) -> bool
range.is_adjacent_to(&other_range) -> bool       // end + 1 day == other.start
range.duration() -> chrono::Duration
range.unit_duration(DateUnit::Month) -> i64

// Mutation
range.extend(DateUnit::Day, 3)? -> DateRange     // grow end by 3 days
range.shift(DateUnit::Month, 1)? -> DateRange    // shift both bounds forward by 1 month

// Iteration
range.iter_days() -> impl Iterator<Item = Date>
range.iter_months() -> impl Iterator<Item = Date>  // first day of each month in range

Real-World Use Cases

  • Booking / reservation systems: check if a requested period overlaps an existing booking
  • Billing periods: iterate months within a contract range
  • Event scheduling: test whether a datetime falls within an event window
  • Token / session validity: express an access window as a DateTimeRange
  • Reporting: iterate over weeks or months within a fiscal quarter range

Implementation Notes

  • DateRange::new should return Err(SpanError) if start > end to prevent inverted ranges.
  • iter_days() should yield Date values from start to end inclusive, advancing by DateUnit::Day.
  • Overlap semantics: two ranges overlap if self.start <= other.end && self.end >= other.start (closed-interval semantics).
  • Serde: serialize as { "start": "...", "end": "..." }, respecting BASE_*_FORMAT.
  • Feature-gated by the date / datetime features respectively.

Acceptance Criteria

  • DateRange::new(start, end) returns Err when start > end
  • contains, overlaps, duration, unit_duration are implemented and tested
  • iter_days and iter_months return correct sequences including edge cases (leap years, month-end)
  • shift and extend return new instances (immutable API, consistent with existing types)
  • DateTimeRange mirrors the same API at datetime granularity
  • Serde support (default nested format)
  • All methods have doc comments with runnable examples

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