fix: keep milliseconds on the guardrail log end bound

endOf("'day"') lands on 23:59:59.999 but format() truncated it to whole
seconds, so a row in the last 999ms of the range was still dropped.
This commit is contained in:
ujitkumar1 2026-08-12 18:57:41 +05:30
parent d37f1c81dc
commit 72f4de83ee
3 changed files with 23 additions and 17 deletions

View file

@ -51,8 +51,8 @@ describe("GuardrailDetail sends the viewer's local day as instants", () => {
const params = usageLogsQuery();
// 00:00 and 23:59:59 on 2026-08-10 in IST, expressed as instants.
expect(params?.get("start_date")).toBe("2026-08-09T18:30:00Z");
expect(params?.get("end_date")).toBe("2026-08-10T18:29:59Z");
expect(params?.get("start_date")).toBe("2026-08-09T18:30:00.000Z");
expect(params?.get("end_date")).toBe("2026-08-10T18:29:59.999Z");
});
it("never sends a bare calendar date, which the endpoint would read as UTC", async () => {

View file

@ -20,8 +20,8 @@ describe("toUtcInstantRange, east of UTC (Asia/Kolkata, +05:30)", () => {
it("resolves the local day to instants rather than the UTC day", () => {
expect(toUtcInstantRange("2026-08-10", "2026-08-10")).toEqual({
start: "2026-08-09T18:30:00Z",
end: "2026-08-10T18:29:59Z",
start: "2026-08-09T18:30:00.000Z",
end: "2026-08-10T18:29:59.999Z",
});
});
@ -46,10 +46,16 @@ describe("toUtcInstantRange, east of UTC (Asia/Kolkata, +05:30)", () => {
expect(Date.parse(end)).toBeGreaterThan(Date.parse("2026-08-10T12:00:00Z"));
});
it("keeps a row in the final millisecond of the local day", () => {
const { end } = toUtcInstantRange("2026-08-10", "2026-08-10");
// 23:59:59.500 local. Truncating the inclusive bound to whole seconds drops it.
expect(Date.parse(end)).toBeGreaterThanOrEqual(Date.parse("2026-08-10T18:29:59.500Z"));
});
it("spans a multi-day range end to end", () => {
expect(toUtcInstantRange("2026-08-03", "2026-08-10")).toEqual({
start: "2026-08-02T18:30:00Z",
end: "2026-08-10T18:29:59Z",
start: "2026-08-02T18:30:00.000Z",
end: "2026-08-10T18:29:59.999Z",
});
});
});
@ -59,8 +65,8 @@ describe("toUtcInstantRange, west of UTC (America/Los_Angeles, -07:00 in August)
it("resolves the local day to instants rather than the UTC day", () => {
expect(toUtcInstantRange("2026-08-10", "2026-08-10")).toEqual({
start: "2026-08-10T07:00:00Z",
end: "2026-08-11T06:59:59Z",
start: "2026-08-10T07:00:00.000Z",
end: "2026-08-11T06:59:59.999Z",
});
});
@ -80,8 +86,8 @@ describe("toUtcInstantRange, on UTC", () => {
it("matches the day the endpoint would have assumed", () => {
expect(toUtcInstantRange("2026-08-10", "2026-08-10")).toEqual({
start: "2026-08-10T00:00:00Z",
end: "2026-08-10T23:59:59Z",
start: "2026-08-10T00:00:00.000Z",
end: "2026-08-10T23:59:59.999Z",
});
});
});

View file

@ -5,14 +5,14 @@ export interface UtcInstantRange {
end: string;
}
const UTC_INSTANT = "YYYY-MM-DDTHH:mm:ss.SSS[Z]";
/**
* `/guardrails/usage/logs` filters `start_time`, a real timestamp column, but pads a
* bare `YYYY-MM-DD` out to `T00:00:00+00:00` / `T23:59:59+00:00`. Sending the picker's
* local calendar date therefore shifts the window by the viewer's UTC offset. Resolving
* the local day here and sending instants keeps the bound exact; the endpoint already
* parses a value carrying a `T`.
* `/guardrails/usage/logs` pads a bare `YYYY-MM-DD` to a whole UTC day, so the picker's
* local date lands the viewer's offset away from the range they chose. Milliseconds are
* kept because the inclusive end bound otherwise drops the final 999ms of that day.
*/
export const toUtcInstantRange = (startDate: string, endDate: string): UtcInstantRange => ({
start: moment(startDate).startOf("day").utc().format(),
end: moment(endDate).endOf("day").utc().format(),
start: moment(startDate).startOf("day").utc().format(UTC_INSTANT),
end: moment(endDate).endOf("day").utc().format(UTC_INSTANT),
});