{"record":{"id":"7559d06b37e67125","repo":"bitwarden/server","slug":"date-range-must-be-367-days","errorCode":null,"errorMessage":"Date range must be < 367 days.","messagePattern":"Date range must be < 367 days\\.","errorType":"exception","errorClass":"BadRequestException","httpStatus":400,"severity":"error","filePath":"src/Api/Dirt/Public/Models/EventFilterRequestModel.cs","lineNumber":55,"sourceCode":"    public string ContinuationToken { get; set; }\n\n    public Tuple<DateTime, DateTime> ToDateRange()\n    {\n        if (!End.HasValue || !Start.HasValue)\n        {\n            End = DateTime.UtcNow.Date.AddDays(1).AddMilliseconds(-1);\n            Start = DateTime.UtcNow.Date.AddDays(-30);\n        }\n        else if (Start.Value > End.Value)\n        {\n            var newEnd = Start;\n            Start = End;\n            End = newEnd;\n        }\n\n        if ((End.Value - Start.Value) > TimeSpan.FromDays(367))\n        {\n            throw new BadRequestException(\"Date range must be < 367 days.\");\n        }\n\n        return new Tuple<DateTime, DateTime>(Start.Value, End.Value);\n    }\n}\n","sourceCodeStart":37,"sourceCodeEnd":61,"githubUrl":"https://github.com/bitwarden/server/blob/e93b962371d80964556f5590c6615f5160a437a1/src/Api/Dirt/Public/Models/EventFilterRequestModel.cs#L37-L61","documentation":"Thrown by EventFilterRequestModel.ToDateRange() when the span between Start and End exceeds 367 days. The method defaults to a 30-day window if either bound is null, swaps reversed ranges, but hard-rejects ranges wider than one year + one day. BadRequestException returns HTTP 400.","triggerScenarios":"Any event-filtering API call (e.g., GET /public/events or /organizations/{id}/events) with Start and End query parameters spanning more than 367 days. The check is (End - Start) > 367 days.","commonSituations":"Client UI date-picker defaulting to 'all time'; exporting a full audit history in one request; automated reporting script using a fixed start date far in the past; timezone mismatch causing the boundary to be off by a day.","solutions":["Split the request into multiple paginated calls each within the 367-day limit.","Client-side: clamp the date picker's maximum range to 367 days before submitting.","If only Start is provided, the server defaults End to today — ensure the resulting range is within bounds.","Use the ContinuationToken pagination mechanism for large date ranges instead of one wide window."],"exampleFix":"// before\nvar events = await api.GetEventsAsync(start: new DateTime(2020,1,1), end: DateTime.UtcNow);\n\n// after — paginate across 367-day windows\nvar chunkStart = new DateTime(2020, 1, 1);\nvar end = DateTime.UtcNow;\nwhile (chunkStart < end)\n{\n    var chunkEnd = chunkStart.AddDays(367) < end ? chunkStart.AddDays(367) : end;\n    var events = await api.GetEventsAsync(start: chunkStart, end: chunkEnd);\n    // process...\n    chunkStart = chunkEnd;\n}","handlingStrategy":"validation","validationCode":"// Validate date range client-side before calling the API\npublic static bool IsValidDateRange(DateTime? start, DateTime? end)\n{\n    if (!start.HasValue || !end.HasValue) return true; // server defaults\n    var actualStart = start.Value < end.Value ? start.Value : end.Value;\n    var actualEnd = start.Value < end.Value ? end.Value : start.Value;\n    return (actualEnd - actualStart) <= TimeSpan.FromDays(367);\n}","typeGuard":"public static bool IsWithinMaxRange(DateTime start, DateTime end) =>\n    (end - start) <= TimeSpan.FromDays(367);","tryCatchPattern":"try\n{\n    var events = await _eventService.GetEventsAsync(filter);\n}\ncatch (BadRequestException ex) when (ex.Message.Contains(\"367 days\"))\n{\n    // Split into chunks and retry\n    var chunked = SplitIntoMaxRanges(filter.Start.Value, filter.End.Value, TimeSpan.FromDays(367));\n    var allEvents = new List<EventLog>();\n    foreach (var (s, e) in chunked)\n        allEvents.AddRange(await _eventService.GetEventsAsync(new EventFilter { Start = s, End = e }));\n    return allEvents;\n}","preventionTips":["Clamp date pickers to a 367-day maximum range client-side.","Use the ContinuationToken pagination mechanism for large historical queries.","Split wide date ranges into multiple paginated API calls programmatically."],"tags":["events","date-validation","pagination","configuration","csharp","aspnet"],"backgroundTag":null,"analyzedSha":"e93b962371d80964556f5590c6615f5160a437a1","analyzedAt":"2026-08-13T14:22:19.382Z","schemaVersion":2},"datasetVersion":"2026-08-13T19:17:28.613Z"}