Date histogram aggregation

Date histogram aggregation #

This multi-bucket aggregation is similar to the normal histogram, but it can only be used with date fields. Dates are represented internally as epoch milliseconds, so the plain histogram can technically run on them too — but calendar intervals are not a fixed length (a month is 28–31 days), which is exactly what this aggregation handles.

How it works #

Date fields store epoch milliseconds at ingest, and the aggregation builds its grid on those numbers. calendar_interval and fixed_interval select between two grid types (exactly one must be set):

  • Calendar intervals (minute, hour, day, week, month, quarter, year, or 1m/1h/1d/1w/1M/1q/1y — single quantities only). Months, quarters, years and weeks floor to civil boundaries: bucket keys are real month starts and Monday weeks, so February is as long as it is. Use these for anything a human reads.
  • Fixed intervals (ms/s/m/h/d units with multiples, like "30m" or "7d"). A fixed number of milliseconds, anchored at the epoch — the grid never bends for the calendar.

Each bucket reports key (the boundary as epoch milliseconds) and key_as_string, rendered in UTC as strict_date_optional_time (2024-01-01T00:00:00.000Z). The format and time_zone parameters are accepted for compatibility; this engine currently always renders the UTC ISO form and computes boundaries in UTC.

min_doc_count (default 0) drops thin buckets. Unlike the numeric histogram, only buckets that actually received documents are emitted — there is no gap-filling between the data minimum and maximum. Two parameters widen the picture: offset (a duration string like "+1d" or a millisecond number) shifts every boundary — the keys report the shifted boundaries — and hard_bounds ({min, max} as epoch millis) both clips the window and force-materializes every bucket inside it, calendar units stepping real months, up to a 100,000-bucket guard. date_nanos-magnitude values (≥ 10¹⁷) are scaled down to milliseconds so both mappings land on one grid.

Examples #

Sales per year — DATE_TRUNC(date, 'year') is the same grouping key on the SQL side:

POST /sales/_search
{
  "aggs": {
    "sales_over_time": {
      "date_histogram": {
        "field": "date",
        "calendar_interval": "year"
      }
    }
  }
}

Response:

{
  ...
  "aggregations": {
    "sales_over_time": {
      "buckets": [
        { "key_as_string": "2024-01-01T00:00:00.000Z", "key": 1704067200000, "doc_count": 92 },
        { "key_as_string": "2025-01-01T00:00:00.000Z", "key": 1735689600000, "doc_count": 28 }
      ]
    }
  }
}

Parameters for date_histogram #

  • field
    (Required, string) Field you wish to aggregate (must be a date / epoch_millis field).
  • min_doc_count
    (Optional, integer) Minimum number of documents in a bucket to include it. Defaults to 0.
  • format
    (Optional, string) Date format for the bucket key string. Accepted for compatibility; this engine always renders the strict_date_optional_time UTC form.
  • time_zone
    (Optional, string) Time zone offset, e.g. +08:00 or -05:00. Accepted for compatibility; bucketing and keys are currently always UTC.
  • offset
    (Optional, string | number) Shift every bucket boundary by this duration ("+1d", "-2h") or millisecond count; keys report the shifted boundaries.
  • hard_bounds
    (Optional, object) {min, max} epoch-millis bounds: clip the window and force-materialize every bucket inside it, even at zero documents.
  • aggs
    (Optional, object) Nested sub-aggregations, computed per bucket.

One of calendar_interval or fixed_interval must be set.

calendar_interval #

(Optional, string) Calendar-aware intervals are configured with the calendar_interval parameter. You can specify calendar intervals using the unit name, such as month, or as a single unit quantity, such as 1M. For example, day and 1d are equivalent. Multiple quantities, such as 2d, are not supported.

The accepted calendar intervals are:

  • minute, 1m
    All minutes begin at 00 seconds. One minute is the interval between 00 seconds of the first minute and 00 seconds of the following minute in the specified time zone, compensating for any intervening leap seconds, so that the number of minutes and seconds past the hour is the same at the start and end.
  • hour, 1h
    All hours begin at 00 minutes and 00 seconds. One hour (1h) is the interval between 00:00 minutes of the first hour and 00:00 minutes of the following hour in the specified time zone, compensating for any intervening leap seconds, so that the number of minutes and seconds past the hour is the same at the start and end.
  • day, 1d
    All days begin at the earliest possible time, which is usually 00:00:00 (midnight). One day (1d) is the interval between the start of the day and the start of the following day in the specified time zone, compensating for any intervening time changes.
  • week, 1w
    One week is the interval between the start day_of_week:hour:minute:second and the same day of the week and time of the following week in the specified time zone.
  • month, 1M
    One month is the interval between the start day of the month and time of day and the same day of the month and time of the following month in the specified time zone, so that the day of the month and time of day are the same at the start and end.
  • quarter, 1q
    One quarter is the interval between the start day of the month and time of day and the same day of the month and time of day three months later, so that the day of the month and time of day are the same at the start and end.
  • year, 1y
    One year is the interval between the start day of the month and time of day and the same day of the month and time of day the following year in the specified time zone, so that the date and time are the same at the start and end.

fixed_interval #

Fixed intervals are configured with the fixed_interval parameter.

In contrast to calendar-aware intervals, fixed intervals are a fixed number of SI units and never deviate, regardless of where they fall on the calendar. One second is always composed of 1000ms. This allows fixed intervals to be specified in any multiple of the supported units.

However, it means fixed intervals cannot express other units such as months, since the duration of a month is not a fixed quantity. Attempting to specify a calendar interval like month or quarter will throw an exception.

The accepted units for fixed intervals are:

  • milliseconds (ms)
    A single millisecond. This is a very, very small interval.
  • seconds (s)
    Defined as 1000 milliseconds each.
  • minutes (m)
    Defined as 60 seconds each (60,000 milliseconds). All minutes begin at 00 seconds.
  • hours (h)
    Defined as 60 minutes each (3,600,000 milliseconds). All hours begin at 00 minutes and 00 seconds.
  • days (d)
    Defined as 24 hours (86,400,000 milliseconds). All days begin at the earliest possible time, which is usually 00:00:00 (midnight).

Range fields #

date_histogram accepts date_range fields: each stored range counts in every calendar bucket it overlaps. offset shifts the bucket grid (keys report the shifted boundaries), and hard_bounds clips the window while force-materializing every in-calendar-window bucket — open-ended stored ranges extend to that window.

Use cases #

  • Log timelines. The teaching logs dataset carries 150 events across one June week; a calendar_interval: "day" histogram returns one bucket per day (24 events each, 6 on the last), exactly the series a monitoring chart plots.
  • Period comparisons. Bucket sales by month or quarter and nest a sum — revenue per period falls out of one query. Over the 120 teaching sales, a year histogram splits them 92 (2024) / 28 (2025).
  • Business-day windows. calendar_interval: "week" floors to Mondays; combined with offset you can slide week boundaries to Sunday starts without changing the data.
  • Fixed-window SLA views. fixed_interval: "1h" gives wall-clock-aligned, never-drifting windows for rate charts, while calendar intervals keep human-readable month edges.
  • Bounded reports. hard_bounds pins a chart to the reporting window (empty in-window buckets included) regardless of when the first and last matching documents happened to arrive.
  • histogram — the same fixed-grid idea over plain numeric fields.
  • auto_date_histogram — pick the bucket count, let the engine choose day/week/month/year.
  • variable_width_histogram — adaptive bucket edges over numeric data.
  • date_range — a handful of named periods instead of a regular grid.
  • composite — paginate a date_histogram dimension with an after cursor.
Calendar October 6, 2026
Edit Edit this page