Link Search Menu Expand Document Documentation Menu

Date histogram aggregations

The date_histogram aggregation groups documents into time-based buckets using date math. Use it to roll up metrics per hour/day/month, chart traffic trends, or fill time series dashboards.

Choose the right interval

date_histogram supports two interval styles:

  • calendar_interval — Aligns buckets to calendar boundaries, such as days, months, or years. Use it when focusing on real-world calendar periods. Example values: "day", "1M", "year".
  • fixed_interval — Uses exact durations measured in SI units. Buckets are always the same length, independent of daylight saving or month length. Example values: "5m", "12h", "30d".

The legacy interval field is kept for compatibility but is deprecated. Use calendar_interval or fixed_interval instead.

Example: Monthly buckets (calendar interval)

Count documents per calendar month:

GET opensearch_dashboards_sample_data_logs/_search
{
  "size": 0,
  "aggs": {
    "logs_per_month": {
      "date_histogram": {
        "field": "@timestamp",
        "calendar_interval": "1M"
      }
    }
  }
}

Example: Uniform hourly buckets (fixed interval)

Retrieve buckets with a fixed interval of exactly 1 hour, regardless of daylight saving time changes:

GET my-logs/_search
{
  "size": 0,
  "aggs": {
    "by_hour": {
      "date_histogram": {
        "field": "timestamp",
        "fixed_interval": "1h"
      }
    }
  }
}

Example: Use a time zone

By default, bucketing occurs in UTC. Set time_zone to align bucket boundaries to a specific time zone.

Retrieve daily buckets using Europe/Dublin:

GET my-logs/_search
{
  "size": 0,
  "aggs": {
    "by_day_ie": {
      "date_histogram": {
        "field": "timestamp",
        "calendar_interval": "day",
        "time_zone": "Europe/Dublin"
      }
    }
  }
}

Example: Shift bucket start times using an offset

Use the offset parameter to move the bucket boundary forward or backward, for example, to define a “reporting day” that runs 06:00–06:00 instead of midnight–midnight:

GET my-logs/_search
{
  "size": 0,
  "aggs": {
    "by_day_shifted": {
      "date_histogram": {
        "field": "timestamp",
        "calendar_interval": "day",
        "offset": "+6h"
      }
    }
  }
}

Example: Include empty buckets

Set min_doc_count to 0 and provide a range in extended_bounds to return empty buckets across the entire time window.

Retrieve buckets with a fixed interval of 1 hour for the last 24 hours, including hours with no data:

GET my-logs/_search
{
  "size": 0,
  "aggs": {
    "last_24h": {
      "date_histogram": {
        "field": "timestamp",
        "fixed_interval": "1h",
        "min_doc_count": 0,
        "extended_bounds": {"min": "now-24h", "max": "now"}
      }
    }
  }
}

Example: Strictly limit the range

hard_bounds strictly limits the histogram to the specified minimum and maximum time range. No buckets are created outside these bounds, even if data falls beyond them.

Retrieve buckets with a fixed interval of 30 minutes for the period between 2025-09-01T00:00:00Z and 2025-09-01T06:00:00Z:

GET my-logs/_search
{
  "size": 0,
  "aggs": {
    "strict_range": {
      "date_histogram": {
        "field": "timestamp",
        "fixed_interval": "30m",
        "hard_bounds": {"min": "2025-09-01T00:00:00Z", "max": "2025-09-01T06:00:00Z"}
      }
    }
  }
}

Example: Return a map of buckets using keyed

Set keyed: true to return buckets as an object keyed by the formatted date string:

GET my-logs/_search
{
  "size": 0,
  "aggs": {
    "per_month": {
      "date_histogram": {
        "field": "timestamp",
        "calendar_interval": "1M",
        "format": "yyyy-MM-dd",
        "keyed": true
      }
    }
  }
}

Example response:

{
  "aggregations": {
    "per_month": {
      "buckets": {
        "2025-01-01": {"key_as_string": "2025-01-01", "key": 1735689600000, "doc_count": 3},
        "2025-02-01": {"key_as_string": "2025-02-01", "key": 1738368000000, "doc_count": 2}
      }
    }
  }
}

Example: Treat missing dates as a fixed value

Use the missing parameter to assign documents with no value to a synthetic bucket at the provided date:

GET articles/_search
{
  "size": 0,
  "aggs": {
    "published_per_year": {
      "date_histogram": {
        "field": "publish_date",
        "calendar_interval": "year",
        "missing": "2000-01-01"
      }
    }
  }
}

Example: Sort buckets

By default, buckets are returned sorted by _key in ascending order. Use the order parameter to change to descending if necessary.

Retrieve buckets with the most recent month first:

GET my-logs/_search
{
  "size": 0,
  "aggs": {
    "recent_months": {
      "date_histogram": {
        "field": "timestamp",
        "calendar_interval": "1M",
        "order": {"_key": "desc"}
      }
    }
  }
}

Order by bucket count (highest first):

GET my-logs/_search
{
  "size": 0,
  "aggs": {
    "busiest_hours": {
      "date_histogram": {
        "field": "timestamp",
        "fixed_interval": "1h",
        "order": {"_count": "desc"}
      }
    }
  }
}

Example: Scripted value source

You can use a Painless script to dynamically generate or modify the date value used for bucketing in a date_histogram. This provides flexibility for handling complex date logic at query time. A date_histogram aggregation does not work with date objects or strings directly. It requires a single, numerical value to represent each document’s timestamp. This value must be a long integer representing epoch milliseconds, the number of milliseconds that have passed since 00:00:00 UTC on January 1, 1970. Any script you provide must return a value of this type. The following example with script behaves in the same way as the previous examples with "field": "timestamp" but generates the correct return type for the date field:

GET my-logs/_search
{
  "size": 0,
  "aggs": {
    "by_hour_script": {
      "date_histogram": {
        "script": {
          "lang": "painless",
          "source": "return doc['timestamp'].value.toInstant().toEpochMilli();"
        },
        "fixed_interval": "1h"
      }
    }
  }
}

Parameters

The date_histogram aggregation supports the following parameters.

Parameter Required Type Description
field One of the following is required: field or script. String The date/datetime field to bucket on.
calendar_interval One of the following is required: calendar_interval, fixed_interval, or legacy interval. String The calendar-aware interval (for example, "day", "1M", "year"). Only singular calendar units are supported.
fixed_interval One of the following is required: calendar_interval, fixed_interval, or legacy interval. String The fixed interval, for example, "5m", "12h", "30d". Not for calendar units like months or quarters.
time_zone Optional String The time zone used for bucketing and formatting. Accepts a time zone, such as "Europe/Dublin", or UTC offsets, such as "-07:00".
format Optional String The output date format used for key_as_string, for example, "yyyy-MM-dd". If omitted, mapping defaults apply.
offset Optional String Shifts bucket boundaries by a positive or negative interval, for example, "+6h", "-30m". Calculated after time_zone is applied.
min_doc_count Optional Integer The minimum number of documents required in order to return a bucket. Default is 1. Set to 0 to include empty buckets.
extended_bounds Optional Object Extends the range of buckets beyond your data: {"min": "<date>", "max": "<date>"}. Often used with min_doc_count: 0.
hard_bounds Optional Object Strictly limits buckets to a range: {"min": "<date>", "max": "<date>"}. Buckets outside the range are never created.
missing Optional Date string Treats documents missing the field as if they had this date value.
keyed Optional Boolean When true, returns buckets as an object keyed by the formatted date string.
order Optional Object Sorts buckets by _key or _count, ascending or descending.
script One of the following is required: field or script. Object Optional script used to compute the value to bucket on. Since the scripts are operated to modify each value, they add overhead and should be used cautiously.