Table of Contents

Struct DateTimeRange

Namespace
DatabentoDotNet.Historical
Assembly
DatabentoDotNet.Historical.dll

A half-open UTC interval on the timeline: an inclusive Start instant and an exclusive End instant. This is the type the historical endpoints that query by exact time, rather than by calendar date, take.

public readonly record struct DateTimeRange : IEquatable<DateTimeRange>
Implements
Inherited Members

Examples

var start = Instant.FromUtc(2024, 1, 2, 14, 30);

DateTimeRange.Between(start, Instant.FromUtc(2024, 1, 2, 15, 0));
DateTimeRange.Spanning(start, Duration.FromMinutes(30));   // the same half hour
DateTimeRange.OnDay(new LocalDate(2024, 1, 2));            // midnight to midnight UTC

// The end is exclusive, and that was probed against the API rather than assumed: a record
// stamped exactly on End is not in the answer.
var range = DateTimeRange.Between(start, Instant.FromUtc(2024, 1, 2, 15, 0));

// Rejected at construction, as an empty or inverted range always is.
DateTimeRange.Between(start, start);   // throws ArgumentException

Remarks

The server agrees about the exclusive end, and that was probed rather than assumed (#46). All three endpoints that send one of these today — metadata.get_record_count, get_billable_size and get_cost, which share MetadataQueryParams — leave a record stamped exactly on End out of their answer, and all three refuse start == end with HTTP 422 data_time_range_start_on_or_after_end. That refusal is the decisive half: an endpoint reading its end as inclusive could not make it, because start == end is how such an endpoint spells a single instant. So no renderer converts anything here — unlike DateRange, whose two renderings exist because get_dataset_condition and list_datasets disagree with each other (#45).

This deliberately no longer claims that every such endpoint reads it this way. It used to, and that sentence was never earned: it was a universal claim about server behaviour with no probe behind it, which is the exact shape of the assumption #45 found to be false for DateRange and #37 found to be true — each only because someone asked. Three sibling endpoints agreeing is a strong prior for a fourth and is not a substitute for asking it; get_dataset_condition and list_datasets take an identical type and disagree to this day. timeseries.get_range (#38) and batch.submit_job (#39) are unprobed because they cost money, and each probes its own end before relying on this.

Port of upstream's DateTimeRange (databento-rs/src/historical.rs). Upstream's field is time::OffsetDateTime; this is Instant, not ZonedDateTime. The historical API is UTC throughout, so there is no time zone for a caller to get right or wrong — adding one would only invite a caller to pass a local wall-clock time that means something different from what they think. See PORTING.md §2.

As with DateRange, the named factories — OnDay(LocalDate), Between(Instant, Instant), Including(Instant, Instant), Spanning(Instant, Duration), FromUnixNanoseconds(long, long) — replace upstream's From impls, and each name says which end is exclusive. An empty or inverted range is rejected at construction, for the same reason DateRange rejects one: see that type's remarks and the M3 ROADMAP decision record.

Properties

End

The exclusive end instant.

public Instant End { get; }

Property Value

Instant

EndUnixNanoseconds

This range's End, rendered the way the historical API's end parameter expects it: Unix nanoseconds.

public long EndUnixNanoseconds { get; }

Property Value

long

Exceptions

InvalidOperationException

This is a default DateTimeRange value.

OverflowException

End is too far from the Unix epoch (roughly beyond the year 2262) for its nanosecond count to fit in a long. See CLAUDE.md, "Dates and times".

Start

The inclusive start instant.

public Instant Start { get; }

Property Value

Instant

StartUnixNanoseconds

This range's Start, rendered the way the historical API's start parameter expects it: Unix nanoseconds.

public long StartUnixNanoseconds { get; }

Property Value

long

Exceptions

InvalidOperationException

This is a default DateTimeRange value.

OverflowException

Start is too far from the Unix epoch (roughly beyond the year 2262) for its nanosecond count to fit in a long. See CLAUDE.md, "Dates and times".

Methods

Between(Instant, Instant)

A half-open range: start is included, end is not.

public static DateTimeRange Between(Instant start, Instant end)

Parameters

start Instant

The inclusive start instant.

end Instant

The exclusive end instant.

Returns

DateTimeRange

The range.

Exceptions

ArgumentException

end is not strictly after start.

FromUnixNanoseconds(long, long)

A range built directly from Unix-nanosecond integers, the representation in which the historical API's start/end parameters travel on the wire.

public static DateTimeRange FromUnixNanoseconds(long startUnixNanoseconds, long endUnixNanoseconds)

Parameters

startUnixNanoseconds long

Nanoseconds since the UNIX epoch, inclusive.

endUnixNanoseconds long

Nanoseconds since the UNIX epoch, exclusive.

Returns

DateTimeRange

The range.

Remarks

This is the crossing a query-response value comes back through. It exists specifically because it is exact where a BCL DateTimeOffset pair would not be: two Unix-nanosecond integers one apart collapse to the same DateTimeOffset (100 ns resolution), but round-trip through Instant unchanged. See CLAUDE.md, "Dates and times".

Exceptions

ArgumentException

endUnixNanoseconds is not strictly after startUnixNanoseconds.

Including(Instant, Instant)

A range where both start and lastInstant are included — the wire-representable instant immediately after lastInstant becomes the exclusive End, one nanosecond later.

public static DateTimeRange Including(Instant start, Instant lastInstant)

Parameters

start Instant

The inclusive start instant.

lastInstant Instant

The last instant the range covers, inclusive.

Returns

DateTimeRange

The range.

Exceptions

ArgumentException

lastInstant is before start.

OnDay(LocalDate)

A range covering exactly one UTC calendar day, from midnight to the following midnight.

public static DateTimeRange OnDay(LocalDate date)

Parameters

date LocalDate

The day.

Returns

DateTimeRange

The range.

Spanning(Instant, Duration)

A range starting at start and spanning exactly duration.

public static DateTimeRange Spanning(Instant start, Duration duration)

Parameters

start Instant

The inclusive start instant.

duration Duration

How long the range spans.

Returns

DateTimeRange

The range.

Exceptions

ArgumentException

duration is zero or negative.

ToDateRange()

Narrows this range to a DateRange: Start's UTC calendar date, inclusive, through End's, exclusive — rounded up to the next day when End does not fall exactly on a UTC midnight.

public DateRange ToDateRange()

Returns

DateRange

The narrowed range.

Remarks

The round-up is upstream's behavior, not an approximation this port introduces: a range ending mid-day still covers part of that day, and a DateRange can only name whole days. Rounding down would silently drop that day's data from a date-based query; rounding up is the direction that never loses data, at the cost of a query that — by design — may cover slightly more than StartEnd did.

ToString()

A debugging-oriented description, printing Start and End directly rather than through StartUnixNanoseconds/EndUnixNanoseconds.

public override string ToString()

Returns

string

The description.

Remarks

A hand-written override, not the compiler-synthesized record ToString: that synthesized version prints every public property, including StartUnixNanoseconds and EndUnixNanoseconds — which would make a supposedly inert ToString() call throw InvalidOperationException for a default value, defeating the point of leaving it unguarded. Printing Start/End instead never throws, for any value including default.