One currency, one timezone (normalise multi-account reporting first)
Published September 6, 2026
The 60-second version
- Summing USD and INR in one column rewrites history every time the exchange rate moves against you.
- Convert, then truncate — never the reverse. Shift to the reporting timezone before taking the date, or Tuesdays split in two.
- Normalise once in shared views: every report reads the views, raw stays immutable, and the FX method gets footnoted.
Your US ad account spends dollars. Your India account spends rupees. Your Meta account reports in Pacific Time and your Google account in IST — while your warehouse stores everything in UTC. Sum that raw data into one report and you get a number that is precisely wrong: exchange-rate drift baked into trends, and day boundaries that move revenue between dates depending on who is asking.
Every multi-account, multi-currency reporting setup needs a normalisation layer: one currency, one timezone, applied the same way by every query. This post builds it — the exchange-rate table, the timezone conversion, and the SQL view everything else reads from. two corruptors, one cure
Normalise once, read everywhere
The Normalisation Layer
Data JourneyRaw platform data
Spend in account currency, timestamps in account timezone. Correct per-account, incomparable across accounts.
Normalisation view
A single view converts every row to reporting currency and reporting timezone. All reports read from here, never from raw.
Every report agrees
Blended CAC, MER, pacing and ad-hoc questions share one definition of 'spend on Tuesday in INR'. Disputes become data questions, not logic disputes.
A reporting timezone is the single timezone all reporting is expressed in — usually the business's home timezone. Reporting currency is the same idea for money. The layer itself is boring infrastructure: a rates table, a view, a convention. Its absence is the cause of a remarkable share of "the dashboard is wrong" tickets.
Normalise at query time, not at load time. Storing converted values in raw tables destroys the original and freezes an exchange rate into history. Keep raw spend and raw timestamps immutable; convert in a view so the rate methodology can improve without reloading anything.
Problem 1: mixed currencies
Summing USD and INR spend in one column is obviously wrong, and everyone does a version of it anyway — usually by converting once in a spreadsheet with "the current rate" applied to all of history. That rewrites the past every time the rate moves: last quarter's CAC changes because this week's rupee did.
The rates table
You need one row per currency per day. Sources in ascending order of rigour: your payment processor's settlement rates (best — it is what you actually paid), a daily ECB/RBI reference feed, or a manual monthly average documented as such. What matters is not the source's perfection but that it is daily, versioned, and shared:
Settlement rates
Your payment processor's own rates. Best — it is what you actually paid, spread included, and finance already books it.
ECB or RBI reference feed
A daily public reference rate. Rigorous and free; differs slightly from settlement, but consistently and transparently.
Manual monthly average
A documented average, refreshed monthly. Acceptable only if labelled as such on every report that uses it.
Document the rate source on the report, not in a wiki. A footnote — "Spend converted at daily settlement rates" vs "at monthly average rates" — changes how finance reads every number on the page. Monthly-average conversion can shift a volatile month's CAC by 3–5% versus daily rates; that is larger than some optimisations you will debate all quarter.
Same spend, rewritten history
One current rate applied to all history moves last quarter's CAC by more than some optimisations you will debate all quarter. Use daily rates.
Problem 2: mixed day boundaries
Ad accounts report in their own timezone; warehouses store UTC; your business thinks in IST. An order at 11:30 PM IST on Tuesday is Wednesday in UTC. A Meta account on Pacific Time ends its "Tuesday" 13.5 hours after IST does. Compare daily spend (account timezone) against daily revenue (UTC date) and every single day is misaligned by the overlap gap.
The 9 PM order that moved days
The rule: convert, then truncate — never the reverse
-- WRONG: truncates in UTC, then labels it a business day.
DATE(order_created_at) -- a UTC day, not your Tuesday
-- RIGHT: shift to reporting timezone, then take the date.
DATE(order_created_at, 'Asia/Kolkata') -- your Tuesday
Apply the same to spend timestamps from each account's native timezone into the reporting timezone, and daily joins finally compare like with like. Keep the account timezone per account in a tiny dimension table — accounts do get recreated in different timezones, and hardcoding America/Los_Angeles in forty queries is how the bug returns.
An order lands at 2:00 AM IST on Wednesday. DATE(utc_timestamp) files it under which day?
The reporting mart
Three Layers, Each Owned Once
Reporting HierarchyRaw (immutable)
Exactly what the APIs delivered: native currency, native timestamps, UTC warehouse times. Append-only; never edited.
Normalised view
Currency and timezone conversion in one reviewed place. The only logic downstream is allowed to assume.
Reports (thin)
MER, CAC, pacing — all plain aggregations over the views. A report bug is now a filter bug, never a conversion bug.
Build the layer in an afternoon one afternoon, in order →
- Declare reporting currency and timezone — INR and Asia/Kolkata for most readers here. A business decision, written down, not a technical default.
- Backfill the FX table — daily rows per currency from settlement data or a reference feed. One table, shared by every query from now on.
- Build the two normalisation views — spend and orders, converted but otherwise untouched. Review the SQL once, carefully: everything downstream inherits it.
- Point reports at the views and footnote the method — migrate one report, reconcile against the old logic for two weeks, then migrate the rest.
Frequently Asked Questions
Should revenue be converted at the order-date rate or today's rate?
📊 Order-date rate
What were the economics when the decision was made? CAC, ROAS and channel efficiency all convert at the rate of the day the money moved.
- CAC and ROAS
- Channel efficiency
- Period comparisons
💰 Current rate
What is it worth right now? Only for cash-position views — never mixed into performance trends.
- Cash position today
- Treasury views
- Never in the same report as order-date numbers
Mixing the two in one report is the most common FX bug after summing raw currencies. pick one per report, footnote it
What about crypto-like intraday FX swings?
Daily granularity is enough for marketing reporting — your attribution windows are days wide and your spend decisions are daily. Sub-daily FX precision matters for treasury, not for CAC. Do not build hourly FX infrastructure for a dashboard read once a morning.
How does this interact with data freshness?
Independently — and that is the point. Freshness tells you whether Tuesday's data has arrived; normalisation tells you what Tuesday means. Debug them separately: first confirm both sides are complete for the window, then confirm both sides cut the window identically.
Summary & Next Steps
Multi-account reporting has exactly two silent corruptors — mixed currencies and mixed day boundaries — and one cure: a declared reporting currency and timezone, a daily FX table, and views that every report shares.
- Use reporting timezone conversion before any date truncation.
- Use schema mapping discipline so every table documents its native currency and timezone.
- Use freshness checks to separate "late data" from "wrong day" when numbers look off.
Takeaways for your next report
- Mixed currencies and mixed day boundaries are the two silent corruptors — one declared reporting currency and timezone cures both.
- Convert, then truncate: shift every timestamp to the reporting timezone before taking the date, or days split at the wrong meridian.
- Keep raw immutable, convert in shared views from a daily versioned FX table, and footnote the rate method on the report.
- Use order-date rates for performance and current rates for cash snapshots — never both in one report.
Blended CAC Calculator
Total spend across every channel, divided by total new customers — the acquisition cost number that reconciles with what you actually spent.
Chinmay Raibagkar
About author →Founder of DataLens AI. He helps non-technical teams read their ad and database numbers with confidence — which number to trust, what to do next, and what to ignore.