The timezone rule
Eventonomy stores every instant in UTC and renders it in one of two zones. Which one depends on the question the pixel answers, not on the surface it sits on. Decided 2026-07-30 (board card 10112313558) after Free and Pro shipped two different answers in the same release.
The rule
| The pixel answers… | Zone | Examples |
|---|---|---|
| "When is this event?" | the EVENT's timezone | the date chip on a card, the date label, the start time, the single-event page, emails, the admin list |
| "Which group does this row belong in?" | the SITE's timezone | day-group headers, agenda buckets ("Today", "This weekend"), month-grid cells, sort order |
| "Which clock is that time on?" | the abbreviation, shown only when the zones differ at that instant | 10:30 am NZST |
Why it splits this way:
- An attendee cares about the venue's clock - "the gig starts at 7pm local to the venue". Showing that in the viewer's zone makes the badge disagree with the event page it links to, which is exactly what made the dashboard confirmation card a bug.
- A month grid or a day-grouped list must be framed in ONE zone or the cells stop lining up and a header can disagree with the rows beneath it. Per-event zones are simply wrong for grouping.
- Without the abbreviation the two halves contradict each other silently: a row filed under Monday showing "10:30 am" reads as Monday morning to the viewer when it is Tuesday morning at the venue.
So a divergent event renders, correctly, as:
May 2027 <- month header, SITE zone
Mon, May 10 · 10:30 am NZST <- date label SITE zone, time EVENT zone + abbr
Auckland Night
The viewer is never misled about which day the row is filed under, and never sees a bare time that means something different to them than to the venue.
How to apply it
Use evnm_event_card_dates( $utc, $event_tz ). It is the single definition
of this rule and returns every part already resolved in the correct zone:
$parts = evnm_event_card_dates( $row['next_occurrence_utc'], $row['timezone'] );
$parts['mon']; // 'May' - EVENT zone (chip)
$parts['day']; // '11' - EVENT zone (chip)
$parts['date']; // 'May 11, 2027' - EVENT zone
$parts['time']; // '10:30 am NZST' - EVENT zone, abbr only when zones differ
$parts['day_key']; // '2027-05-10' - SITE zone (grouping key)
$parts['day_label']; // 'Monday, May 10' - SITE zone (group header)
$parts['agenda_date']; // 'Mon, May 10' - SITE zone
$parts['tz_differs']; // bool
Consumers today: Free's events-list block, EventsController::enrich_cards()
(so a card appended by "Load more" cannot disagree with one rendered by the
page), and Pro's BuddyPress MemberEventsQuery.
For a single datetime outside a card context, use
evnm_format_event_datetime( $utc, $event_tz, $args ) - the canonical formatter
underneath, honouring the timezone_display setting (event by default, with
site and both available).
What NOT to do
date_i18n()/wp_date()for a per-event date. Both resolve in the SITE zone, so the chip silently contradicts the event's own page. This is what the list block and the REST card payload both did.gmdate()for a grouping key. That is a THIRD zone. The list block used it forday_keywhile labelling the same row withdate_i18n(), so near midnight a row could be grouped under one day and labelled another.- Per-event zones in a calendar grid. A month or week grid must stay in one
frame.
AbstractCatalogPage::format_site_datetime()is correctly site-zone for the same reason:created_atis a record stamp, not an event. - Deriving the parts again in a new surface. Three separate derivations are how the product came to answer one question three ways. Call the helper.