Elasticsearch Date Range Aggregation - Named, Non-Uniform Date Buckets - Syntax, Example, and Tips

The Elasticsearch date_range aggregation is a multi-bucket aggregation that groups documents into explicitly defined date intervals. Each range is given by a from/to pair (either bound is optional, both inclusive of from and exclusive of to), and can have a human-friendly key. Use it for non-uniform time partitions like "this week vs last week vs older", or for fixed reporting periods like fiscal quarters.

Syntax

GET /sales/_search
{
  "size": 0,
  "aggs": {
    "sales_periods": {
      "date_range": {
        "field": "order_date",
        "format": "yyyy-MM-dd",
        "time_zone": "UTC",
        "ranges": [
          { "to": "now-12M/M",                            "key": "older" },
          { "from": "now-12M/M", "to": "now-6M/M",        "key": "last_year_h1" },
          { "from": "now-6M/M",                           "key": "recent" }
        ]
      }
    }
  }
}

Each range produces one bucket with a doc_count and any sub-aggregations attached. Buckets can overlap - a document satisfying multiple ranges is counted in each.

Parameters

Parameter Default Description
field required date or date_nanos field.
ranges required Array of { from, to, key? } objects. At least one of from or to per range.
format - Java date format applied to both inputs and bucket keys.
time_zone UTC Time zone for boundary evaluation and date math.
keyed false Return the response as a map keyed by key instead of an array.
missing - Substitute date for documents missing the field.

from is inclusive, to is exclusive - mirroring half-open interval conventions. Documents on the to boundary fall into the next range.

Examples

Fixed reporting periods with keyed response for easier JSON parsing:

"date_range": {
  "field": "order_date",
  "format": "yyyy-MM-dd",
  "keyed": true,
  "ranges": [
    { "key": "Q1", "from": "2024-01-01", "to": "2024-04-01" },
    { "key": "Q2", "from": "2024-04-01", "to": "2024-07-01" },
    { "key": "Q3", "from": "2024-07-01", "to": "2024-10-01" },
    { "key": "Q4", "from": "2024-10-01", "to": "2025-01-01" }
  ]
}

Relative ranges with date math anchored to month boundaries:

"date_range": {
  "field": "@timestamp",
  "ranges": [
    { "to":   "now-1M/M", "key": "before_last_month" },
    { "from": "now-1M/M", "to": "now/M", "key": "last_month" },
    { "from": "now/M",    "key": "this_month" }
  ]
}

Date range with sum sub-aggregation:

"aggs": {
  "periods": {
    "date_range": {
      "field": "order_date",
      "ranges": [
        { "to": "now-30d", "key": "older" },
        { "from": "now-30d", "key": "last_30_days" }
      ]
    },
    "aggs": {
      "revenue": { "sum": { "field": "amount" } }
    }
  }
}

Performance Notes

date_range is cheap. It evaluates each document's field against a fixed list of ranges, so per-document cost is constant in the number of ranges (typically a handful). No per-bucket map is built beyond the range count itself, so memory usage is trivial compared to terms or date_histogram aggregations.

Date math expressions (now-1M/M, now/d) are evaluated once at request time relative to the coordinating node's clock and the configured time_zone. Subtle bugs arise when clusters span time zones or when clients pass time_zone inconsistently - results shift by a full bucket at boundaries. Use absolute timestamps for reproducible reports.

date_range is operationally safe but loses to date_histogram when ranges are uniform and continuous. Pulse helps identify dashboards that are repeatedly running expensive aggregations against Elasticsearch and OpenSearch and surfaces optimization opportunities where date_range could replace a more expensive pattern.

Common Mistakes

  1. Treating to as inclusive. It is exclusive; a document at exactly to falls into the next range.
  2. Building dozens of contiguous ranges manually instead of using date_histogram - date_histogram is purpose-built for uniform intervals.
  3. Mixing format between the index data and the aggregation. The aggregation parses from/to according to format; mismatches throw parse errors.
  4. Forgetting time zone. now/d in UTC and in Asia/Tokyo produce different bucket boundaries.
  5. Defining overlapping ranges accidentally and then being surprised that totals exceed hits.total.

Frequently Asked Questions

Q: Can date ranges overlap?
A: Yes. A document falling into multiple ranges contributes to each bucket. This is intentional - use it for "rolling vs absolute" comparisons - but verify it is what you want, since totals across overlapping buckets exceed the number of matched documents.

Q: Are from and to inclusive or exclusive?
A: from is inclusive, to is exclusive. A range { "from": "2024-01-01", "to": "2024-02-01" } covers all of January but not midnight on February 1.

Q: How are documents with missing date fields handled?
A: They are skipped unless missing is set, in which case the substitute date is used.

Q: Can I use date math like now-1M/M?
A: Yes. Date math is fully supported in from and to. The /M rounds down to the start of the month; /d to start of day. See the Elasticsearch date math docs for the full grammar.

Q: What is the difference between date_range and date_histogram?
A: The date_histogram aggregation creates uniform-width buckets continuously across the time range. The date_range aggregation creates an explicit, possibly non-contiguous, possibly overlapping set of buckets you define.

Q: Does date_range support date_nanos?
A: Yes. The format parameter controls how the bounds are parsed; nanosecond-precision bounds are accepted against date_nanos fields.

Subscribe to the Pulse Newsletter

Get early access to new Pulse features, insightful blogs & exclusive events , webinars, and workshops.

We use cookies to provide an optimized user experience and understand our traffic. To learn more, read our use of cookies; otherwise, please choose 'Accept Cookies' to continue using our website.