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. |