How does the time_zone parameter change bucket boundaries in an Elasticsearch date_histogram aggregation?
answer
- Dates are stored as UTC instants
- Something must decide where a day starts
- The parameter changes where rounding happens
- Zone ids follow DST, fixed offsets do not
- 23-hour and 25-hour buckets are legitimate
basics
~20 sElasticsearch stores dates as UTC milliseconds; time_zone makes the rounding to bucket boundaries happen in that zone instead. Day and month buckets then start at local midnight, and daylight-saving transitions make calendar day buckets 23 or 25 hours long.
solid answer
~50 sDates are stored internally as milliseconds since the epoch in UTC, so without `time_zone` a `1d` bucket runs from UTC midnight to UTC midnight — which is why a report for a Tokyo or New York audience looks shifted. `time_zone` accepts an ISO zone id such as `"America/New_York"` or a fixed offset such as `"-05:00"`, and it changes where rounding happens: timestamps are converted to that zone, rounded down to the interval boundary, and converted back. The `key` in the response is still epoch millis; `key_as_string` is formatted in the requested zone. The consequence to be ready for is daylight saving: with `calendar_interval`, a spring-forward day is a 23-hour bucket and an autumn day a 25-hour bucket, so raw counts across those days are not comparable. With `fixed_interval` the buckets stay exactly 24 hours and therefore drift away from local midnight after a transition. `offset` shifts boundaries further, for a business day that starts at 06:00.
code
json · 14 lines{
"size": 0,
"aggs": {
"per_business_day": {
"date_histogram": {
"field": "@timestamp",
"calendar_interval": "1d",
"time_zone": "America/New_York",
"offset": "+6h",
"min_doc_count": 0
}
}
}
}go deeper
Recall that dates are stored in UTC and that a day histogram uses UTC midnight unless you pass a time zone.
Explain that the parameter changes where rounding happens, and that key stays epoch millis while key_as_string is formatted in the requested zone.
Diagnose the DST-shaped anomalies: 23- and 25-hour calendar buckets, fixed intervals drifting off local midnight, and a range filter in a different zone from the histogram producing partial edge buckets.
Set the organisational rule for which zone reporting uses and where it is decided, so dashboards, exports and alerts over the same index cannot disagree about the boundaries of a day.
## What Elasticsearch actually stores A `date` field is stored as a long: milliseconds since the Unix epoch, in UTC. The string you indexed (`"2026-03-08T02:30:00-05:00"`) is parsed once at index time and the zone information is discarded — only the instant survives. That single fact explains everything about this parameter. Bucketing by "day" requires deciding where a day starts, and the stored data does not know; you have to tell it. ## What time_zone does With no `time_zone`, `date_histogram` rounds in UTC: a `calendar_interval` of `1d` produces buckets that begin at 00:00 UTC. Supplying `"time_zone": "America/New_York"` (an IANA zone id) or `"time_zone": "+05:30"` (a fixed offset) changes the rounding basis: each timestamp is interpreted in that zone, rounded down to the interval boundary *there*, and the resulting boundary converted back to an instant. Day buckets then begin at local midnight, month buckets at local midnight on the 1st, and so on. The response reflects this in two fields. `key` remains epoch milliseconds — the instant at which the bucket starts, in UTC. `key_as_string` is that instant formatted in the requested zone, which is what a dashboard shows. Candidates who only ever look at `key_as_string` are often surprised that `key` "doesn't match"; it does, it is just the same moment expressed differently. A fixed offset (`"-05:00"`) is not the same as a zone id (`"America/New_York"`). The offset is constant year-round and therefore never observes daylight saving; the zone id follows the region's DST rules. For anything a human reads, use the zone id — otherwise half the year is shifted by an hour. ## Daylight saving, and why it matters This is where the interview usually goes. On a spring-forward day in a DST zone the local day is only 23 hours long; on a fall-back day it is 25. With `calendar_interval: 1d` Elasticsearch does the semantically right thing: it produces one bucket per local day, of 23 or 25 hours. That is correct for "how many orders on Sunday" and wrong for "orders per 24 hours" — a naive comparison of that day's count with its neighbours reads as a 4% dip or spike that has nothing to do with the business. With `fixed_interval: 24h` the opposite happens: the buckets remain exactly 24 hours, so after a transition they no longer start at local midnight. Every subsequent bucket straddles two local days by an hour. Series maths stays clean, human interpretation gets subtly wrong. An hour-level histogram in a fall-back zone has a related surprise: the repeated local hour means two distinct instants map to the same local wall-clock hour, so that bucket legitimately contains two hours of data. ## offset `offset` shifts every bucket boundary by a duration, positive or negative — `"offset": "+6h"` makes a day run 06:00 to 06:00, which is how you model a business day, a retail trading day, or a night-shift roster. It composes with `time_zone`: rounding happens in the zone, then the boundary is shifted. Do not try to emulate a time zone with `offset`; it is a constant shift and knows nothing about DST. ## Practical guidance - Decide the zone at the *edge*, from the viewer or tenant, and pass it explicitly on every request. A dashboard that silently uses UTC and a finance export that uses local time will never reconcile, and the discrepancy is exactly one day's worth of boundary traffic. - Store the zone with the report definition rather than relying on a server default; server defaults move when you move hosts. - For alerting and rate computations prefer `fixed_interval`, and divide by the actual bucket width if you must use calendar intervals across a DST boundary. - When two systems must agree, agree on the zone first. Most "Elasticsearch counts don't match the database" tickets in reporting are a boundary disagreement, not a data problem. - Aggregating a very wide range at fine granularity in a non-UTC zone costs a little more, because boundary computation must consult the zone's transition rules rather than doing plain arithmetic. It is rarely the bottleneck, but it is a real difference from the UTC path. ## The related query-side trap `time_zone` on the histogram does not change which documents matched. If the surrounding range filter was expressed in UTC and the histogram in a local zone, the first and last buckets will be partial — the filter clipped the local day. Express the range filter in the same zone (date range queries accept `time_zone` too) so the window and the buckets agree.
- Why is "time_zone": "-05:00" not equivalent to "time_zone": "America/New_York"?A fixed offset is constant all year, so it ignores daylight saving entirely; New York is at -05:00 in winter and -04:00 in summer. Using the offset means every summer bucket boundary is an hour off local midnight. Use the IANA zone id whenever a human reads the result; the fixed offset is only right when you genuinely want a constant shift.
- A daily chart shows a suspicious dip on one Sunday each spring. What is the likely cause?A calendar day bucket in a DST zone that springs forward is only 23 hours long, so it holds roughly an hour less traffic than its neighbours. Nothing is broken — the bucket is semantically correct. Either normalise counts by the real bucket duration, or switch that panel to a fixed 24-hour interval if even-width comparison matters more than calendar alignment.
- How do you make a date_histogram day run from 06:00 to 06:00 local time?Set the histogram's `time_zone` to the local zone so rounding happens there, then add `"offset": "+6h"` to shift every boundary six hours forward. Offset is applied after the zone-aware rounding, so the two compose correctly. Do not use offset alone as a substitute for time_zone — it is a constant shift with no knowledge of daylight-saving transitions.
saying these in an interview costs you the question
- Assuming Elasticsearch stores the original timestamp's zone
- Using a fixed offset for a zone that observes DST
- Treating a 23-hour DST bucket as a data bug
- Expecting time_zone to change which documents matched
- Relying on a server default zone instead of passing one