skip to content

What is the difference between calendar_interval and fixed_interval in an Elasticsearch date_histogram?

level: middleimportance: should knowfreq 58%

answer

  1. Two mutually exclusive interval parameters
  2. One follows the calendar, one the clock
  3. Months and DST days are not fixed lengths
  4. One of them refuses multiples like 2d
  5. Month and year exist in only one of them

basics

~20 s

calendar_interval buckets by calendar units whose real length varies — months differ in length and daylight-saving days are not 24 hours — and accepts only a single unit. fixed_interval buckets by an exact multiple of milliseconds, so every bucket is identical.

solid answer

~50 s

A `date_histogram` must be given exactly one of the two. `calendar_interval` follows the calendar: `1m`, `1h`, `1d`, `1w`, `1M`, `1q`, `1y` (or their spelled-out names). Its buckets are semantically meaningful — a month bucket really is that month, whether it has 28 or 31 days — but their duration varies, and with a daylight-saving time zone a day bucket can be 23 or 25 hours. Crucially it accepts only a **single** unit: `2d` is rejected, because two calendar days have no fixed meaning. `fixed_interval` takes any multiple of a fixed-length unit — `ms`, `s`, `m`, `h`, `d` — so `30d`, `90m` and `12h` are all valid and every bucket is exactly that long. There is no fixed month or year, because neither has a fixed length. Use calendar for reports humans read, fixed for evenly-spaced time series maths.

code

json · 17 lines
json
{
  "size": 0,
  "aggs": {
    "per_month": {
      "date_histogram": {
        "field": "@timestamp",
        "calendar_interval": "1M"
      }
    },
    "per_30_days": {
      "date_histogram": {
        "field": "@timestamp",
        "fixed_interval": "30d"
      }
    }
  }
}

go deeper

for a junior

Know that a date_histogram takes exactly one of calendar_interval or fixed_interval, and that month and year are only available on the calendar one.

for a middle

Explain why calendar buckets vary in length, why only single units are allowed there, and which parameter you would pick for a monthly report versus a rate calculation.

for a senior

Show judgment about which semantics a given dashboard or alert actually needs, and handle the surrounding parameters — min_doc_count, extended_bounds, hard_bounds — so the series is continuous and bounded.

for a principal

Own the convention across teams: one agreed interval semantics and time zone per reporting surface, so two dashboards over the same index never disagree about what a day is.

## Why there are two parameters at all Time has two incompatible notions of an interval. One is physical: a duration in milliseconds that never changes. The other is civil: a calendar unit whose length depends on the calendar and on political decisions about daylight saving. "Every 24 hours" and "every day" are the same thing only in a time zone with no DST, and "every 30 days" and "every month" are never the same thing. Elasticsearch's `date_histogram` therefore exposes two mutually exclusive parameters, and you must supply exactly one. ## calendar_interval `calendar_interval` rounds each document's timestamp down to a calendar boundary. The accepted values are the single-unit ones — minute (`1m`), hour (`1h`), day (`1d`), week (`1w`), month (`1M`), quarter (`1q`), year (`1y`) — each also spellable in full (`"month"`, `"quarter"`). The rule that trips people up is that **only a multiple of one is allowed**. `"calendar_interval": "2d"` is rejected outright, and so is `3M`. There is no coherent definition of "two calendar days" that starts consistently anywhere, so the API refuses rather than guessing. The payoff is semantic correctness. A monthly revenue chart built with `1M` has one bucket per real month; February is shorter and that is exactly right. A yearly bucket handles leap years without you thinking about it. Combined with `time_zone`, day and month boundaries land on the local calendar boundary the reader expects. The cost is that bucket durations are not comparable. February's bucket covers fewer hours than March's, so a bare count comparison between them is slightly unfair, and a DST day is 23 or 25 hours long. If you are computing a rate, you have to divide by the bucket's real length. ## fixed_interval `fixed_interval` takes a number and a fixed-length unit: milliseconds (`ms`), seconds (`s`), minutes (`m`), hours (`h`), days (`d`). Any multiple is allowed — `10s`, `90m`, `12h`, `30d` are all fine. Every bucket is exactly the stated number of milliseconds wide, measured from the epoch, so buckets are perfectly comparable and rate maths is trivial. There is deliberately no fixed month, quarter or year unit, because none of them has a fixed length. `"fixed_interval": "1M"` is rejected. If you want roughly-monthly buckets of equal width you write `30d` and accept that the buckets drift relative to the calendar — the first will start at an epoch-aligned boundary, not on the 1st of a month. Note the unit-letter clash: `m` means *minute* in both parameters, `M` means *month* and is calendar-only. Case matters and a lowercase `m` where you meant a month is a silent, very confusing bug — you get minute buckets and a response far larger than you expected. ## Choosing between them - Human-facing reporting — "sales per month", "signups per day in the user's time zone" — wants `calendar_interval`, because the reader's mental model is the calendar. - Time-series analysis, anomaly detection, moving averages, anything that divides by elapsed time or compares adjacent buckets numerically — wants `fixed_interval`, because equal-width buckets keep the maths honest. - Sub-hour granularity is usually `fixed_interval` anyway (`"5m"`, `"30s"`), since calendar semantics below an hour add nothing. ## Related parameters worth knowing `time_zone` decides in which zone the rounding happens; it is what makes `1d` mean local midnight rather than UTC midnight. `offset` shifts the boundaries by a duration — `"+6h"` for a business day that starts at 06:00. `min_doc_count` defaults to 1 for most bucket aggregations but a date histogram is normally rendered as a continuous series, so `"min_doc_count": 0` is the common setting to emit empty buckets for quiet periods. `extended_bounds` forces the histogram to span a range even where no documents exist (it only works together with `min_doc_count: 0`), and `hard_bounds` does the opposite, clipping the histogram to a range so a stray out-of-range timestamp cannot generate a huge span of buckets. ## Version note Older Elasticsearch had a single `interval` parameter that accepted both kinds of value and guessed which semantics you meant. That ambiguity is exactly why the parameter was split; `interval` was deprecated during 7.x and is not available in 8.x. Any snippet you find that uses `"interval": "1d"` predates the split and must be rewritten to one of the two explicit forms.

  • Why does Elasticsearch reject a calendar_interval of 2d but accept a fixed_interval of 30d?
    "Two calendar days" has no well-defined boundary — there is no calendar rule that says where a two-day period begins, and DST makes the pairs unequal. So calendar_interval permits only single units. `fixed_interval` has no such problem: 30d is simply 30 × 86,400,000 milliseconds measured from the epoch, an unambiguous fixed width.
  • How do you make a date_histogram emit buckets for days with no documents?
    Set `"min_doc_count": 0`, which stops the aggregation from dropping empty buckets, and pair it with `extended_bounds` giving the min and max of the range you want covered — otherwise the histogram still starts at the first matching document and ends at the last. `hard_bounds` is the complement, clipping the histogram so an outlier timestamp cannot balloon the bucket count.

saying these in an interview costs you the question

  • Using calendar_interval 2d and expecting it to work
  • Assuming a calendar day bucket is always 24 hours
  • Writing lowercase m for months instead of minutes
  • Expecting fixed_interval to accept months or years
  • Comparing raw counts across unequal-length calendar buckets

context