Pitfall #6: Trusting event_date Instead of event_timestamp
The Trap
Section titled “The Trap”There’s a friendly-looking column called event_date right there in the schema. It’s a string, formatted YYYYMMDD, easy to work with. You use it to filter, group, and aggregate your events by day. Your reports look fine. Then one day, you’re analyzing conversions by hour and you notice something: events that clearly happened at 11 PM on March 24th are showing up under March 25th. Your daily totals have been subtly wrong this entire time.
Why It Happens
Section titled “Why It Happens”Two things are working against you here, and they compound each other.
First: event_date is the date the event was processed and assigned to a daily table shard, not necessarily the date the event occurred. For most events, they’re the same. But for late-arriving hits — events from offline devices that sync later, or events buffered on mobile before being sent — the event can land in a table shard that doesn’t match when it actually happened. GA4 also applies its own processing logic that can shift which date an event is attributed to.
Second, and this is the bigger one: everything in the GA4 BigQuery export is in UTC. The event_timestamp field is microseconds since Unix epoch, in UTC. The event_date column is also derived in UTC. If your business operates in, say, Europe/Berlin (UTC+1 or UTC+2 depending on DST), then an event that happened at 11:30 PM Berlin time on March 24th has a UTC timestamp of March 25th 10:30 PM — wait, no. Let me be precise. An event at 11:30 PM CET (UTC+1) is 10:30 PM UTC on the same day. But an event at 00:30 AM CET on March 25th is 11:30 PM UTC on March 24th.
Here’s the thing: depending on your timezone offset direction, events get shifted forward or backward relative to your business day. The further you are from UTC, the bigger the mismatch. For teams in US Pacific time (UTC-7 or UTC-8), the shift is massive — nearly a third of your business day falls on the “wrong” UTC date.
And event_date gives you no way to fix this. It’s a pre-computed string. You can’t adjust it for timezone. You’re stuck with whatever date Google assigned.
The Fix
Section titled “The Fix”Always use event_timestamp for any time-based analysis. Convert it from microseconds to a proper TIMESTAMP, then apply your business timezone explicitly.
SELECT event_name, TIMESTAMP_MICROS(event_timestamp) AS event_ts_utc, DATETIME( TIMESTAMP_MICROS(event_timestamp), 'Europe/Berlin' ) AS event_datetime_berlin, DATE( TIMESTAMP_MICROS(event_timestamp), 'Europe/Berlin' ) AS event_date_berlin, event_date AS event_date_utc_originalFROM `your_project.analytics_123456789.events_*`WHERE _TABLE_SUFFIX BETWEEN '20260301' AND '20260307'LIMIT 100A few rules to internalize:
- Use
event_timestampfor grouping by date, hour, or any time window. Convert to your business timezone first. - Keep
_TABLE_SUFFIXfor partition pruning only. It’s a cost optimization tool, not an analytical date field. When you filter by_TABLE_SUFFIX, widen the range by a day on each side to catch edge cases, then apply your real date filter on the converted timestamp. - Be explicit about timezone in every query. Never assume UTC is “close enough.” Write
DATE(TIMESTAMP_MICROS(event_timestamp), 'America/New_York')instead ofevent_date. Every time. - Store the timezone conversion in your models. If you’re building intermediate tables, compute the business-timezone date once and include it as a column. Don’t make every downstream query re-derive it.
How to Check If You Have This Problem
Section titled “How to Check If You Have This Problem”This query finds events where the UTC-derived event_date and the business-timezone date disagree. Replace 'Europe/Berlin' with your own timezone:
SELECT event_date AS table_date_utc, DATE( TIMESTAMP_MICROS(event_timestamp), 'Europe/Berlin' ) AS actual_date_berlin, COUNT(*) AS event_countFROM `your_project.analytics_123456789.events_*`WHERE _TABLE_SUFFIX BETWEEN '20260301' AND '20260307'GROUP BY 1, 2HAVING table_date_utc != CAST(actual_date_berlin AS STRING)ORDER BY event_count DESCIf this returns rows, those are events that would land on a different date in your business reporting than where event_date puts them. The event_count tells you how many events are affected. For European timezones, expect a small but consistent percentage. For US timezones, the numbers can be significant — especially for events near midnight UTC, which is late afternoon or early evening in the US.
Any report or model that groups by event_date instead of a timezone-converted timestamp is carrying this error silently.