Timezone mismatch

Wrong dates on the report or the chart

A surprising number of "help desk" tickets come from this. The data is correct, the rule is correct, the UI is rendering in the wrong zone. This page walks the three places where timezones live.

The three timezones

OpenSense's stack has three timezones in play. They are usually different.

  1. Storage TZ. Always UTC. Every measurement, every event, every audit-log row carries ts_utc. This is what is in the database.
  2. Site TZ. Set per site (Europe/Bratislava, Asia/Tokyo, …). Used by the PDF report renderer for day boundaries and labels.
  3. Browser TZ. Used by the dashboard chart when rendered in your browser. The chart x-axis labels reflect your local clock.

This is intentional: an operator in the EU travelling to Tokyo wants to see the dashboard in JST, while the inspector at the Bratislava café gets a PDF in Europe/Bratislava regardless of who issued it.

Symptom: chart and PDF disagree

If the dashboard chart shows a +9 °C spike at 14:30 and the PDF shows the same spike at 12:30, that is the storage-TZ → browser-TZ vs storage-TZ → site-TZ difference. The data is the same; the labelling differs by your offset minus the site offset.

This is correct behaviour for travel scenarios but confuses operators who never leave their site. To make them agree, set the dashboard's "display timezone" to the site's timezone in Account → Preferences → Display timezone. The chart then matches the PDF.

Symptom: a "yesterday" report is empty

If you click "Yesterday's report" at 02:00 local time:

  • It is 01:00 UTC.
  • The report engine asks: "what measurements fall in 2026-05-16 00:00 site-local2026-05-16 24:00 site-local?"
  • For a site in Europe/Bratislava (UTC+2 in summer), that is 2026-05-15 22:00 UTC2026-05-16 22:00 UTC.
  • You have until 02:00 site-local on the 17th for "yesterday the 16th" to be a full day.

Fix: request "the previous full local day", or wait until later in the morning. The PDF service refuses to render an incomplete day with a friendly modal; the API returns an empty (header-only) PDF.

Symptom: DST shift makes a thresholdcross at the wrong hour

If a rule has a time-of-day window (FREQ=DAILY;BYHOUR=22), it fires at 22:00 in the site's timezone, respecting DST. On the morning after a spring-forward, the window happens 23 hours after the previous one; on the morning after a fall-back, 25 hours.

If your rule is for a process that runs on a fixed real-world clock (e.g. a thermal-disinfection cycle scheduled by a PLC at 03:00 local without DST awareness), the rule's window will drift from the process's actual time twice a year. Fix: use UTC in the rule's rrule (TZID=UTC;FREQ=DAILY;BYHOUR=...), or set the site's timezone to UTC (rare).

Symptom: "ts is in the future"

If your device's clock is wrong (no NTP, or NTP misconfigured), it may send ts in the future. OpenSense accepts ts up to 30 seconds in the future (clock skew tolerance) and rejects beyond that with 422 ts_in_future.

Fix the device clock. For ESPHome:

time:
  - platform: sntp
    servers:
      - pool.ntp.org

For Shelly, the device gets time from your WiFi router via DHCP option 42 or from the Shelly cloud. If your router does not advertise an NTP server, configure one in the Shelly's Time settings.

Symptom: the PDF labels show UTC instead of local

Your site's timezone field is empty or UTC. Set it: Site → Settings → Timezone → Europe/Bratislava (or whatever applies). Re-issue the report. The cached PDF for the period is invalidated and re-rendered with correct labels.