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
EndUnixNanoseconds
This range's End, rendered the way the historical API's end
parameter expects it: Unix nanoseconds.
public long EndUnixNanoseconds { get; }
Property Value
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
StartUnixNanoseconds
This range's Start, rendered the way the historical API's start
parameter expects it: Unix nanoseconds.
public long StartUnixNanoseconds { get; }
Property Value
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
Returns
- DateTimeRange
The range.
Exceptions
- ArgumentException
endis not strictly afterstart.
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
startUnixNanosecondslongNanoseconds since the UNIX epoch, inclusive.
endUnixNanosecondslongNanoseconds 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
endUnixNanosecondsis not strictly afterstartUnixNanoseconds.
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
startInstantThe inclusive start instant.
lastInstantInstantThe last instant the range covers, inclusive.
Returns
- DateTimeRange
The range.
Exceptions
- ArgumentException
lastInstantis beforestart.
OnDay(LocalDate)
A range covering exactly one UTC calendar day, from midnight to the following midnight.
public static DateTimeRange OnDay(LocalDate date)
Parameters
dateLocalDateThe 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
Returns
- DateTimeRange
The range.
Exceptions
- ArgumentException
durationis 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 Start–End 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.