Timezone handling
Genie stores timestamps in UTC and converts them to each user’s local zone for display and input. Every request carries an effective session time zone; the engine exposes it to your SQL so that date logic can be tenant- and user-correct without hard-coding an offset.
The rule
Section titled “The rule”- Storage is UTC.
DateTimeOffsetis used for timezone-correct persistence. - The UI converts. The React client formats UTC values into the user’s zone for display and converts local input back to UTC before sending — the backend never renders a localized string.
- SQL gets the zone as parameters (below), so server-side date expressions can localize too.
Resolving the session time zone
Section titled “Resolving the session time zone”SessionTimeZoneService (ISessionTimeZoneService) resolves the effective IANA zone id in priority
order, always validating the id before use:
- Session cookie — an explicit override set via the API (
SessionTimeZonecookie). - User preference — the persisted
TimeZoneIdon the user record. X-TimeZonerequest header — the browser’s auto-detected zone, sent by the client.UTC— the default fallback.
An invalid id at any level is skipped rather than trusted.
SQL session parameters
Section titled “SQL session parameters”Two parameters are available to any view/entity/badge SQL, populated per request from the resolved zone (see Entities → session parameters for the full list):
| Parameter | Value |
|---|---|
@SessionTimeZone |
the caller’s effective IANA zone (e.g. Asia/Karachi) |
@SessionTimeZoneOffset |
that zone’s current UTC offset, in minutes |
Localize a UTC column dialect-neutrally via the offset, or — for full DST-correctness across arbitrary dates on PostgreSQL — via the named zone:
-- SQL ServerDATEADD(MINUTE, @SessionTimeZoneOffset, SYSUTCDATETIME())
-- PostgreSQL (offset)now() + (@SessionTimeZoneOffset || ' minutes')::interval
-- PostgreSQL (DST-correct for any date)utcColumn AT TIME ZONE @SessionTimeZone| Method | Route | Purpose |
|---|---|---|
POST |
/api/v1/genie/timezone/switch |
set the session zone ({ TimeZoneId }); writes the cookie and persists it as the user’s preference |
GET |
/api/v1/genie/timezone/current |
get the current effective IANA zone id |
The switch cookie is HttpOnly + Secure + SameSite=Strict with a one-year expiry, so the override
survives across sessions until changed. A Profile time-zone picker in the UI drives this endpoint.