Link Search Menu Expand Document Documentation Menu

Date range aggregations

Use the date_range aggregation to group documents into buckets defined by date boundaries. The date_range aggregation behaves like the numeric range aggregation but accepts date math in addition to ISO 8601 dates and epoch milliseconds.

Note the following details:

  • from is inclusive, to is exclusive.
  • To create an open-ended bucket, omit from or to.
  • Date math supports rounding: for example, now-7d/d (start of the day, 7 days ago).

Parameters

The following is a table of parameters accepted by date_range aggregations.

Parameter Required Description
field Yes The date field to aggregate on.
ranges Yes The non-empty array of range objects. Each object must specify at least one boundary, from and/or to.
ranges[].from One of from or to is required. Lower inclusive bound.
ranges[].to One of from or to is required. Upper exclusive bound.
ranges[].key No The label for the bucket.
format No Controls the *_as_string fields in the response, for example, yyyy-MM-dd.
time_zone No The IANA zone or UTC offset used when evaluating date math or rounding, for example,Europe/Dublin, +01:00.
keyed No If true, returns an object with the key key instead of an array.
missing No The value to substitute for documents in which the field is missing.

Accepted values for from and to

The following values of from and to are accepted:

  • ISO 8601 strings: "2025-10-01T00:00:00Z", "2025-10-01"
  • Date math: "now-7d/d", "now+1M/M", "2025-09-01||/M"
  • Epoch milliseconds: 1756684800000

If components are omitted in a date string, the missing parts are filled with defaults. For example, "2025-10" is treated as the start of October 2025.

Example: Three sliding windows

The following example produces three buckets (last 7 days, previous 7 days, and older), using date math and yyyy-MM-dd output format:

GET my-index/_search
{
  "size": 0,
  "aggs": {
    "by_range": {
      "date_range": {
        "field": "@timestamp",
        "format": "yyyy-MM-dd",
        "ranges": [
          { "from": "now-7d/d",  "to": "now+1d/d", "key": "last_7d" },
          { "from": "now-14d/d", "to": "now-7d/d", "key": "prev_7d" },
          { "to": "now-14d/d",                      "key": "older"  }
        ]
      }
    }
  }
}

Example response:

"aggregations": {
    "by_range": {
      "buckets": [
        {
          "key": "older",
          "to": 1758067200000,
          "to_as_string": "2025-09-17",
          "doc_count": 1
        },
        {
          "key": "prev_7d",
          "from": 1758067200000,
          "from_as_string": "2025-09-17",
          "to": 1758672000000,
          "to_as_string": "2025-09-24",
          "doc_count": 2
        },
        {
          "key": "last_7d",
          "from": 1758672000000,
          "from_as_string": "2025-09-24",
          "to": 1759363200000,
          "to_as_string": "2025-10-02",
          "doc_count": 2
        }
      ]
    }
  }

Example: Bucket for the last 10 days with a custom string format

The following request creates a single bucket that covers the last 10 calendar days. It starts at the beginning of the day 10 days ago (now-10d/d) and ends at the beginning of tomorrow (now+1d/d, exclusive). The format only affects the *_as_string fields in the response—not document matching:

GET my-index/_search
{
  "size": 0,
  "aggs": {
    "last_10_days": {
      "date_range": {
        "field": "@timestamp",
        "format": "yyyy-MM",
        "ranges": [ { "from": "now-10d/d", "to": "now+1d/d" } ]
      }
    }
  }
}

Example: Keyed response and custom keys

The following request returns an object organized by your labels for easier downstream processing:

GET my-index/_search
{
  "size": 0,
  "aggs": {
    "keyed_ranges": {
      "date_range": {
        "field": "@timestamp",
        "keyed": true,
        "ranges": [
          { "from": "now-1d/d", "to": "now+1d/d", "key": "today" },
          { "to": "now-1d/d", "key": "before_today" }
        ]
      }
    }
  }
}

Example response:

"aggregations": {
    "keyed_ranges": {
      "buckets": {
        "before_today": {
          "to": 1759190400000,
          "to_as_string": "2025-09-30T00:00:00.000Z",
          "doc_count": 4
        },
        "today": {
          "from": 1759190400000,
          "from_as_string": "2025-09-30T00:00:00.000Z",
          "to": 1759363200000,
          "to_as_string": "2025-10-02T00:00:00.000Z",
          "doc_count": 1
        }
      }
    }
  }

Example: Epoch milliseconds with a time zone

When the field value is provided in epoch milliseconds, you can still provide from and to parameters as numbers. For example, in the following request, time_zone affects date math and boundary evaluation:

GET my-index/_search
{
  "size": 0,
  "aggs": {
    "local_ranges": {
      "date_range": {
        "field": "event_time",
        "time_zone": "Europe/Dublin",
        "format": "epoch_millis",
        "ranges": [
          { "from": "1697328000000", "to": "1697932800000", "key": "week_sample" }
        ]
      }
    }
  }
}

Example: Handling missing dates

Use missing to route documents without a value into a bucket by substituting a default:

GET my-index/_search
{
  "size": 0,
  "aggs": {
    "dated_or_undated": {
      "date_range": {
        "field": "@timestamp",
        "missing": "1970-01-01",
        "ranges": [
          { "to": "2000-01-01", "key": "undated_or_old" },
          { "from": "2000-01-01", "key": "dated_recent" }
        ]
      }
    }
  }
}