Handling money and time correctly across regions, two things famously easy to get subtly and expensively wrong.
Why it exists
Serving people in different places means money and time stop being simple: amounts are in different currencies and the same instant is a different clock time for each user. Currency and timezone handling exist to store and convert both correctly, so prices and timestamps are right for whoever is looking.
How it actually works
Money must track which currency, format correctly, and store amounts precisely (using whole units like paisa, not fractions of taka, to avoid rounding errors). Time must record when things happened in a way that survives time zones, usually by storing a single universal time and converting for display.
Get these wrong and you get double-charges, mismatched reports, orders timestamped on the wrong day, and reconciliation nightmares. They deserve deliberate care.
A senior PM walks you through it
A junior PM, stuck
Ops says Friday's order count on the daily report is short by exactly the number of very late orders, and wants to know if the report is broken. I checked and the orders are all there in the database, none are missing. So I cannot explain how the total can be right in the table but wrong on the report, and I do not want to guess at ops.
The report is not losing orders; it is filing some of them under the wrong day. Every order is stored with its time in UTC, one universal clock, but the store runs on Dhaka time, six hours ahead. An order placed just after midnight in Dhaka is still the previous day in UTC, so a report that groups by the raw UTC date drops it onto yesterday. Below are the real rows and the two versions of the count query. The difference is one line.
The orders table and two daily-count queries
orders table, times stored in UTC
1 id | amount_paisa | created_at
2 o_5512 | 64900 | 2026-03-06T12:41:00Z
3 o_5513 | 64900 | 2026-03-06T18:30:00Z
4 o_5590 | 64900 | 2026-03-05T21:07:00Z
the daily count, grouped by the raw UTC date
>SELECT date(created_at) AS day, count(*) FROM orders GROUP BY day;
6 day | count
7 2026-03-05 | 1
8 2026-03-06 | 2
the same count, converted to Dhaka time first
>SELECT date(created_at AT TIME ZONE 'Asia/Dhaka') AS day, count(*) FROM orders GROUP BY day;
10 day | count
11 2026-03-06 | 3
Click a step to see the lines it points at.
Mistakes I've seen
Escalating a timezone bug as lost data. The orders are all in the table, just grouped onto the wrong calendar day; sending ops on a hunt for missing rows burns a day when the fix is one clause in the query.
Storing local time instead of UTC. If each order saved Dhaka time directly, the day it grew or shrank a timezone would quietly corrupt every past timestamp; store one universal time and convert on the way out.
Reading amount_paisa as taka. The column holds whole paisa, so 64900 is 649 taka, not 64,900; money is stored in the smallest unit to dodge rounding, and a report that forgets divides or multiplies by a hundred.
Trusting a report total without asking its day boundary. Two dashboards can count the same orders and disagree purely on where they draw midnight; confirm the timezone the report groups by before you quote its number upward.
Tell ops: "The report is not broken and no orders are missing. It counts by UTC date while we run on Dhaka time, so after-midnight orders land on the day before; converting to Asia/Dhaka in the query puts Friday back to the right total." You traced a missing count to a timezone grouping and separated a query fix from lost data, which is the skill.
Where a PM meets this
"The daily report is off by a day for some users" is a classic timezone bug; recognizing the pattern speeds diagnosis.
Multi-currency pricing (round numbers, local norms, conversion) is a product decision, not just a technical conversion.
Hear it in a meeting
"Store everything in UTC and convert on display, or the reports will drift."