# Declaring the time zones and cultures an app uses — zones { } and cultures { }

> The time zones and cultures an app names are a declared, closed set: a `zones { }` and a `cultures { }` block in any model file (not in `app.osy`). The platform ships a default set of each; an app's own block REPLACES it, so it lists every zone or culture the app uses. A zone or culture outside the set is a compile error that prints the line to add.

<!-- id: i18n-zones-and-cultures · area: i18n · stability: stable · html: https://osysharp.com/reference/i18n/zones-and-cultures/ -->

## Summary        {#summary}
A **`zones { }`** block lists the IANA time zones the app names; a **`cultures { }`** block lists the cultures it
formats and parses in. Each is a top-level declaration in **any model file** — `model/zones.osy` is a good home —
and never an entry in `app.osy`, which declares only where the source lives.

```osy title="the zones a European trade app shows the time in" test app=i18n-zones-and-cultures
zones { "UTC", "Europe/London", "Europe/Lisbon", "Europe/Madrid", "Europe/Paris", "Europe/Stockholm" }

string ZoneFor(bool iberian) {
  return iberian ? "Europe/Madrid" : "Europe/Stockholm";
}

string TimeThere(bool iberian) {
  return TimeZoneInfo.ConvertTimeFromUtc(DateTime.UtcNow, TimeZoneInfo.FindSystemTimeZoneById(ZoneFor(iberian))).ToString("HH:mm");
}
```

## Signature      {#signature}
```osy syntax
zones    { "<IANA zone id>", … }       // e.g. "Europe/Madrid", "Asia/Kolkata", "UTC"
cultures { "<BCP-47 culture id>", … }  // e.g. "en-US", "sv-SE", "ar-AE"
```

## Description    {#description}
**Why the set is declared.** A zone's daylight-saving rules and a culture's number and date patterns run in the
**browser** as well as on the server, and the browser is handed them for exactly the declared set — so a page can show
"18:40 in Madrid" or "1 290,00 €" with no round trip. A zone or culture outside the set has nothing to run with
there, which is why naming one is refused when the app COMPILES rather than discovered when a page renders.

**Your block replaces the default — it does not add to it.** With no block of its own an app gets the platform's
default packs (`osy docs stdlib-zones` lists the zones; `osy docs stdlib-culture-formatting` the cultures). The
moment the app writes `zones { … }`, that list IS the app's set, so keep the default zones you still use. Blocks in
several model files of one app add up; a capability's (kit's) own block is used only when the app declares none.

**What is checked, and where.**

| you write | refused when the zone/culture is undeclared |
|---|---|
| `TimeZoneInfo.FindSystemTimeZoneById("Europe/Madrid")` | at that literal |
| `TimeZoneInfo.FindSystemTimeZoneById(ZoneFor(c))`, where `ZoneFor` returns `"Europe/Madrid"` | at the literal in `ZoneFor` — the compile follows the value through functions, fields, `?:`, `??` and `switch` |
| `amount.ToString("C", "es-ES")` | at that literal |
| `TimeZoneInfo.FindSystemTimeZoneById(shop.TimeZone)` — a zone a PERSON stored | cannot be decided at compile time: a stored value is checked to be a real zone when it is written, and a page that meets one outside the set names it. Let people pick it with `TimeZonePicker`, which only offers declared zones |

The refusal carries the whole remedy — where the block goes and the exact line, the zones you already have plus the
missing one:

```text
model/people.osy:12  ERROR  zone 'Europe/Madrid' reaches `TimeZoneInfo.FindSystemTimeZoneById(…)` at model/people.osy:18 through `ZoneFor`, but
  it is not one of this app's declared time zones, so the browser is given no daylight-saving plan for it and the
  page would fail there. Declare the zones the app uses in a `zones { }` block in any model file (`model/zones.osy`,
  say — not in `app.osy`). Your block REPLACES the platform's default set, so it lists every zone the app uses:

  zones { "UTC", "Europe/London", …, "Europe/Madrid" }
```

**Each id is checked against the time-zone and culture data the platform ships**, so a misspelling (`"Europe/Madird"`)
is refused with a "did you mean"; an id listed twice is refused too.

**Listing them as data.** `TimeZoneInfo.GetSystemTimeZones()` is the declared zones as a `Zone[]`, in block order — what a picker offers.
With `use Osysharp.Locale;` they are also rows (`Osysharp.Locale.Zone`, `Osysharp.Locale.Culture`) a page can query.

## Examples       {#examples}

```osy title="the cultures a shop formats prices in" test app=i18n-zones-and-cultures
cultures { "en-US", "en-GB", "es-ES", "sv-SE" }

string PriceInSpain(decimal amount) {
  return amount.ToString("C", "es-ES");
}
```

## See also       {#see-also}
- [Time zones — TimeZoneInfo](https://osysharp.com/reference/stdlib/zones/) — the `TimeZoneInfo` type and what it does: offsets, daylight saving, a civil time to an instant
- [Culture formatting — ToString(format, culture)](https://osysharp.com/reference/stdlib/culture-formatting/) — formatting and parsing in a culture
- [app.osy](https://osysharp.com/reference/project/manifest/) — what `app.osy` declares, and why a `zones { }` block is not part of it
