[go: up one dir, main page]

Link Search Menu Expand Document Documentation Menu

Date range aggregation

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. A range that specifies neither from nor to matches every value.
ranges[].from No Lower inclusive bound.
ranges[].to No Upper exclusive bound.
ranges[].key No The label for the bucket.
format No The date format used to display the bucket boundaries in the from_as_string and to_as_string response fields and to parse date strings in from and to, for example, yyyy-MM-dd.
time_zone No The IANA time zone or UTC offset used to evaluate date math, rounding, and date strings in from and to that don’t specify an offset, for example, Europe/Dublin or +01:00. With Europe/Dublin, "from": "2025-10-01" is evaluated as 2025-10-01T00:00:00+01:00.
keyed No If true, returns the buckets as an object instead of an array. Each bucket is keyed by its key value, which is either the custom key that you specify for the range or a generated key, such as *-2025-09-20T00:00:00.000Z. Default is false.
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 setup

The examples on this page use a my-index index. Create the index and map the event_time field to accept epoch milliseconds:

PUT my-index
{
  "mappings": {
    "properties": {
      "@timestamp": { "type": "date" },
      "event_time": { "type": "date", "format": "epoch_millis" }
    }
  }
}

Index the following documents. Document 6 has no @timestamp value:

POST my-index/_bulk?refresh
{ "index": { "_id": "1" } }
{ "@timestamp": "2025-09-10T12:00:00Z", "event_time": 1697400000000 }
{ "index": { "_id": "2" } }
{ "@timestamp": "2025-09-18T09:00:00Z", "event_time": 1697500000000 }
{ "index": { "_id": "3" } }
{ "@timestamp": "2025-09-22T15:00:00Z", "event_time": 1698000000000 }
{ "index": { "_id": "4" } }
{ "@timestamp": "2025-09-26T08:00:00Z" }
{ "index": { "_id": "5" } }
{ "@timestamp": "2025-10-01T07:30:00Z" }
{ "index": { "_id": "6" } }
{ "note": "no timestamp" }

So that the responses on this page don’t depend on when you run the examples, the date math in the examples is calculated from the fixed date 2025-10-01 using the || separator. To calculate the ranges from the current time, replace 2025-10-01|| with now. For example, use now-7d/d instead of 2025-10-01||-7d/d.

Example: Three sliding windows

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

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

The response contains the three buckets:

{
  "aggregations": {
    "by_range": {
      "buckets": [
        {
          "key": "older",
          "to": 1758067200000.0,
          "to_as_string": "2025-09-17",
          "doc_count": 1
        },
        {
          "key": "prev_7d",
          "from": 1758067200000.0,
          "from_as_string": "2025-09-17",
          "to": 1758672000000.0,
          "to_as_string": "2025-09-24",
          "doc_count": 2
        },
        {
          "key": "last_7d",
          "from": 1758672000000.0,
          "from_as_string": "2025-09-24",
          "to": 1759363200000.0,
          "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 10 days before October 1, 2025, and the whole day of October 1. The bucket starts at the beginning of the day 10 days earlier (-10d/d) and ends at the beginning of the following day (+1d/d, exclusive).

The format determines how the bucket boundaries are displayed in the from_as_string and to_as_string response fields. It is also used to parse date strings in from and to, so the date in each boundary must use the same format. In this example, the format is yyyy-MM, so the boundaries start with 2025-10, which is parsed as October 1, 2025:

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

The response contains documents 3, 4, and 5. The from_as_string and to_as_string fields use the yyyy-MM format:

{
  "aggregations": {
    "last_10_days": {
      "buckets": [
        {
          "key": "2025-09-2025-10",
          "from": 1758412800000.0,
          "from_as_string": "2025-09",
          "to": 1759363200000.0,
          "to_as_string": "2025-10",
          "doc_count": 3
        }
      ]
    }
  }
}

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": "2025-10-01||/d",
            "to": "2025-10-01||+1d/d",
            "key": "today"
          },
          {
            "to": "2025-10-01||/d",
            "key": "before_today"
          }
        ]
      }
    }
  }
}

The response contains a today bucket and a before_today bucket:

{
  "aggregations": {
    "keyed_ranges": {
      "buckets": {
        "before_today": {
          "to": 1759276800000.0,
          "to_as_string": "2025-10-01T00:00:00.000Z",
          "doc_count": 4
        },
        "today": {
          "from": 1759276800000.0,
          "from_as_string": "2025-10-01T00:00:00.000Z",
          "to": 1759363200000.0,
          "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. 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"
          }
        ]
      }
    }
  }
}

The time_zone is applied to the boundaries even though they are epoch milliseconds. In October 2023, Dublin was one hour ahead of UTC, so both boundaries in the response are one hour (3600000 milliseconds) earlier than the values in the request:

{
  "aggregations": {
    "local_ranges": {
      "buckets": [
        {
          "key": "week_sample",
          "from": 1697324400000.0,
          "from_as_string": "1697324400000",
          "to": 1697929200000.0,
          "to_as_string": "1697929200000",
          "doc_count": 2
        }
      ]
    }
  }
}

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"
          }
        ]
      }
    }
  }
}

Document 6, which has no @timestamp value, is counted in the undated_or_old bucket:

{
  "aggregations": {
    "dated_or_undated": {
      "buckets": [
        {
          "key": "undated_or_old",
          "to": 946684800000.0,
          "to_as_string": "2000-01-01T00:00:00.000Z",
          "doc_count": 1
        },
        {
          "key": "dated_recent",
          "from": 946684800000.0,
          "from_as_string": "2000-01-01T00:00:00.000Z",
          "doc_count": 5
        }
      ]
    }
  }
}