ADR-0001: Run the application on the Kuwait clock, not UTC¶
- Status: Accepted
- Date: 2026-09-16
- Deciders: Project operator (MundoVenturesIT)
- Relates to: deviates from house rule "Timestamps stored UTC, timezone applied at presentation only. No service decides deadlines from its local clock."
Context¶
Yam3at is an events marketplace operating only in Kuwait. Customers, providers and
the operator are all in one timezone, Asia/Kuwait — UTC+3 year-round, no daylight
saving, and no change to that offset since 1950.
The house rule says a service applies a timezone at presentation only and never
decides deadlines from its local clock. Running APP_TIMEZONE=UTC and converting
in the client satisfies that rule literally. It also puts every day boundary three
hours out from the day everyone using the product is living in.
Forces:
- A booking is a wall-clock appointment, not an instant. A provider says
"Saturday, 10:00 to 22:00".
provider_availabilitystores10:00and22:00as times of day with a weekday number. There is no instant to store, and the provider means Kuwait time. - "Today" decides what the customer is offered.
AvailabilitySlotsdrops slots already past and offers the next open day. Under UTC, between 21:00 and 24:00 Kuwait the server's "today" is still yesterday, so the app offers slots that have already gone and hides the day that is actually next. - Deadlines are quoted to people in local terms. A 15-minute checkout hold and a 24-hour acceptance window are counted in elapsed time, but an order that "expires tomorrow" must expire on the day the provider calls tomorrow.
- MySQL
TIMESTAMPalready stores UTC. All 209 datetime columns areTIMESTAMP, which holds an absolute UTC epoch and converts on read using the session timezone. The stored value does not change with this decision — only how the application reads and writes it. - One operator, one market. There is no second region, and no prospect of one in the current plan.
Options considered¶
Option A — APP_TIMEZONE=UTC, convert in every client¶
- Pros: matches the house rule as written; a second market needs no server change.
- Cons: every "today", every day-boundary comparison and every date-only column becomes a conversion that each of three clients (Flutter app, Next.js dashboard, API consumers) has to get right independently. Force 2 recurs in each of them, and the failure — offering a slot that has passed — is silent.
Option B — APP_TIMEZONE=Asia/Kuwait, with the database session matching¶
- Pros: the server's day is the day the market is in; a date comparison means what it reads as; clients render what the server sends.
- Cons: deviates from the house rule; a second market later means a migration of logic, not just configuration; the app and database timezone settings must move together or every write lands three hours off.
Option C — Store wall-clock times in DATETIME and drop UTC entirely¶
- Cons: loses the absolute instant, so "when did this happen" becomes unanswerable across any future offset change. Rejected outright.
Decision¶
We will run the application on Asia/Kuwait (Option B).
config/app.phpsets'timezone' => env('APP_TIMEZONE', 'Asia/Kuwait').config/database.phpsets the MySQL session'timezone' => env('DB_TIMEZONE', '+03:00')— a numeric offset so it works without the named-timezone tables loaded, which is safe because Kuwait has no DST. These two must always move together; both files carry a comment saying so.- Storage stays UTC. Every datetime column remains
TIMESTAMP, holding an absolute UTC epoch. This decision changes the presentation and comparison timezone, not the storage format, so the house rule's storage requirement is still met. - The part we are deviating from is explicit: the server decides day boundaries and
deadline windows on the Kuwait clock.
AvailabilitySlots::TZand thenow()calls behind checkout holds, acceptance timeouts and stuck-order thresholds all resolve in Kuwait time.
Recorded deviation: the house rule "no service decides deadlines from its local clock" is deviated from for Yam3at from 2026-09-16 until 2027-09-16. On or before that date this decision is made again, not silently renewed.
Rationale¶
Forces 1 and 2 are the deciding ones. A provider's working hours are wall-clock facts with no instant attached, and the product's core question — "what can I book next?" — is a question about the local day. Under Option A the answer is computed three hours adrift and has to be corrected separately in each client, where being wrong shows up as a bookable slot that has already passed rather than as an error.
Force 4 is what makes the deviation cheap: because storage stays an absolute UTC epoch, this is reversible by changing two configuration values, not by migrating data.
The trade-off accepted: serving a second country means changing logic, not configuration. Availability, day boundaries and expiry windows would each need a per-vendor or per-user timezone rather than one application-wide setting.
Consequences¶
Positive:
- The server's "today" is the day the market is in. Slot generation, expiry sweeps and date filters agree with what a user in Kuwait sees on their own phone.
- Date-only comparisons (
whereDate) mean the local day, which is what every query in the codebase intends. - Clients render what the server sends without a conversion step of their own.
Negative:
- The application and database timezone settings are coupled. If
APP_TIMEZONEis changed withoutDB_TIMEZONE, the app sends Kuwait wall-clock while MySQL reads it as UTC, and every write silently lands three hours off. Nothing enforces this beyond the comments in both files. - A second market requires a logic change, not a configuration change.
- Anyone reading the database directly, from a client in a different session timezone, sees times converted to their session, not Kuwait's. Raw SQL against production must set the session timezone first.
- Scheduled commands (
ExpireStaleOrdersevery 5 minutes,PruneIdempotencyKeysdaily) run on the Kuwait clock. A daily job at "00:00" fires at 21:00 UTC.
Neutral:
- Because Kuwait has had a fixed +03:00 offset since 1950 and observes no DST, the numeric offset and the named zone are equivalent today. That equivalence is an assumption this ADR depends on, not a guarantee.
Revisit when¶
- A second country is added, or a provider outside Kuwait is onboarded.
- Kuwait adopts daylight saving or changes its offset — at which point
DB_TIMEZONEmust become the named zone and the timezone tables must be loaded. - Any incident is traced to a time that is three hours off.
- The deviation expires on 2027-09-16.