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.
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#
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#
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:
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#
cultures { "en-US", "en-GB", "es-ES", "sv-SE" }
string PriceInSpain(decimal amount) {
return amount.ToString("C", "es-ES");
}See also#
- Time zones — TimeZoneInfo — the
TimeZoneInfotype and what it does: offsets, daylight saving, a civil time to an instant - Culture formatting — ToString(format, culture) — formatting and parsing in a culture
- app.osy — what
app.osydeclares, and why azones { }block is not part of it