Summary#
A Zone is a value-kind that stores a civil time zone by its IANA id ("Europe/Stockholm", "Asia/Kolkata").
You store it on an entity like any scalar, and its operations are DST-aware:
entity Shop {
Zone OpensInZone; // stored as the IANA token, e.g. "Europe/Stockholm"
TimeOnly OpenTime;
}var z = Zone.Of("Europe/Stockholm");
var offset = z.OffsetAt(instant); // the UTC offset AT that instant — +1h in winter, +2h in summer
var summer = z.IsDst(instant); // is daylight-saving in effect then?
var local = instant.InZone(z); // show a UTC instant as local wall-clock time
var opensUtc = z.Resolve(today, opens); // "09:00 in the zone" → the UTC instantFor a declared zone named as a literal, OffsetAt / IsDst / InZone / Resolve run in the browser,
byte-identical to the server — no round trip.
Signature#
Zone Zone(string ianaId) // the factory — the id must be a declared zone
TimeSpan zone.OffsetAt(DateTime instant) // total UTC offset at an instant (DST-aware, sub-hour exact)
bool zone.IsDst(DateTime instant) // is daylight-saving in effect at an instant
DateTime instant.InZone(Zone zone) // a UTC instant as the zone's local wall-clock time (for display)
DateTime zone.Resolve(DateOnly date, TimeOnly timeOfDay) // a civil local time → the UTC instantDescription#
Declared, closed set. A zone id written as a literal (Zone.Of("Europe/Stockholm")) must belong to the app's declared
zones { } set. The platform ships a default pack (UTC, Europe/London, Europe/Stockholm, Europe/Berlin,
Europe/Paris, Europe/Dublin, America/New_York, America/Chicago, America/Denver, America/Los_Angeles,
America/Sao_Paulo, Asia/Kolkata, Asia/Kathmandu, Asia/Tokyo, Asia/Shanghai, Australia/Sydney); an app can
vendor the block to curate it. A literal id outside the set is a compile error (with a "did you mean"). The declared
zones are also browsable data (an Osyrin.Locale.Zone table) so a picker can list them.
Stored value is the token. A Zone field stores the IANA string itself, not a foreign key — portable and stable.
A value written at runtime is validated to be a real IANA zone; a garbage token is rejected.
The underlying methods. The instance spellings above are the idiomatic form of the Zone stdlib module:
Zone.New (the factory), Zone.OffsetAt, Zone.IsDst, Zone.InZone and Zone.Resolve. For a literal declared zone,
Zone.OffsetAt / Zone.IsDst / Zone.InZone / Zone.Resolve all run in the browser (Zone.New folds at compile
time). A dynamic zone — a stored Zone field or a variable — has no bundled client plan, so its operations run on
the server (where the answer is identical); side-inference routes them automatically.
DST-aware, and correct for hard zones. Offsets come from the pinned time-zone data, so sub-hour zones
(Asia/Kolkata +05:30, Asia/Kathmandu +05:45) and negative-DST zones (Europe/Dublin) are exact — not just
whole-hour Western zones.
Resolve — the civil-time rule. zone.Resolve(date, timeOfDay) answers "what UTC instant is it when the wall clock
in this zone reads date at timeOfDay?" — the DST-aware way to store "the shop opens 09:00 local". At the twice-a-year
edges it is deterministic: an ambiguous local time (the fall-back hour that happens twice) resolves to the standard
offset; an invalid local time (the spring-forward hour that never happens) is skipped forward past the gap. Real
business hours never fall in that 02:00–03:00 window.
Examples#
Show a stored UTC timestamp in a fixed zone, and read its offset:
DateTime InStockholm(DateTime instant) {
return instant.InZone(Zone.Of("Europe/Stockholm")); // 12:00 UTC → 13:00 (winter) / 14:00 (summer)
}
TimeSpan StockholmOffset(DateTime instant) {
return Zone.Of("Europe/Stockholm").OffsetAt(instant); // +01:00 in winter, +02:00 in summer
}The civil-time rule — "the shop opens 09:00 in its own zone" → a UTC instant:
entity Shop {
[Required, MaxLength(120)] string Name;
Zone OpensInZone;
TimeOnly OpenTime;
}
DateTime OpeningUtc(Shop shop, DateOnly on) {
return shop.OpensInZone.Resolve(on, shop.OpenTime);
}The same rule with a literal zone runs in the browser (no round trip):
DateTime StockholmOpening(DateOnly on, TimeOnly at) {
return Zone.Of("Europe/Stockholm").Resolve(on, at); // 09:00 civil → the UTC instant, DST-aware
}Is daylight-saving in effect right now for a zone?
bool SummerTime(Zone zone) {
return zone.IsDst(DateTime.UtcNow);
}See also#
- DateTime — the
DateTime/DateOnly/TimeOnlyvalues these operations take and return - Culture formatting — ToString(format, culture) — format the resulting local time for a culture