You're viewing version 3.3 of the OpenSearch documentation. This version is no longer maintained. For the latest version, see the current documentation. For information about OpenSearch version maintenance, see Release Schedule and Maintenance Policy.
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. |