Osy#betaRun it on Osyrin: osyrin.com

Reference / UI

The TimeZone kit — the time zone an app's days start in, picked from a list

use Osysharp.TimeZone@0; · policy ManagesClock => …; (default: IsAdmin) · app.Clock = new ClockSetup { TimeZone = TimeZoneInfo.FindSystemTimeZoneById("Europe/Stockholm"), WeekStart = DayOfWeek.Monday }; · TheClock() · clock.Local(instant) · clock.DayOf(instant) · clock.StartOf(day) · clock.WeekOf(day) · /settings/clock

`Osysharp.TimeZone` is the app's one answer to "what day is it here?" — the time zone the team's days start in and the day its week starts, picked by staff from the app's declared zones on one settings page. Reports count their days, weeks and months on it, and the daily digest goes out in the team's morning by it, so a team that moves its clock moves all of them at once.

preview1 example compiled by CIuikitzonetimezone

Source on GitHub: Osysharp.TimeZone — github.com/osysharp/timezone ↗

Summary#

use Osysharp.TimeZone@0; gives the app one clock: a typed TimeZoneInfo and the day a week starts, picked from a list by whoever ManagesClock, on the settings page the kit adds (Settings › Time and date). Code reads it with TheClock():

var clock = TheClock();
var today = clock.DayOf(DateTime.UtcNow);       // the date on the team's wall, not the server's
var since = clock.StartOf(today.AddDays(-6));   // midnight there, seven days ago — in summer time or not

Every kit that counts in days reads the same row: Osysharp.Reports buckets and periods, and the Osysharp.Notifications digest hour.

Signature#

use Osysharp.TimeZone@0;                       // with Osysharp.Ui

policy ManagesClock => <who moves the clock>;   // optional — the app's IsAdmin until it says otherwise

app.Clock = new ClockSetup {                // optional — the clock until staff pick one
  TimeZone = TimeZoneInfo.FindSystemTimeZoneById("Europe/Stockholm"),   // one of the app's declared zones; UTC by default
  WeekStart = DayOfWeek.Monday,             // Monday by default
};

AppClock TheClock()                         // staff's choice, else the app's ClockSetup
// on an AppClock:
Zone      TimeZone
DayOfWeek WeekStart
DateTime  Local(DateTime instant)           // the wall-clock time there
DateOnly  DayOf(DateTime instant)           // the date there
DateTime  StartOf(DateOnly day)             // the instant that day begins there
DateOnly  WeekOf(DateOnly day)              // the first day of the week holding `day`

// settings: /settings/clock (ClockSettingsPage) — a time-zone picker and a week-start dropdown

Description#

Why one clock for the whole app?#

A report that says "today" and a digest that goes out "in the morning" mean the same day only if they read the same zone. Each kit keeping its own would let them drift: the report's week would start on Monday in Stockholm while the digest went out at 06:00 UTC. So the zone lives in one row, and the kits that count in days read it rather than keep a copy. Changing it moves every one of them at the next read — nothing is cached.

What can staff pick from?#

The app's declared zones — its zones { } block, or the platform's default set ([[stdlib-zones#description]]). The setting is a TimeZoneInfo, drawn by the settings tier as a TimeZonePicker, so a name that is not a time zone cannot be saved: it cannot be typed. To offer a zone the default set does not have, declare the app's own zones { }; it replaces the default wholesale, so list every zone the team may want.

Who may change it?#

ManagesClock. The kit answers it with the app's IsAdmin until the app defines it. Everybody may read the clock, signed in or not — it is not a secret, and a public page may say "open until 17:00" on it.

Before anybody sets it#

No row means the app's ClockSetup — UTC and Monday unless the app says otherwise in app.osy. The settings page makes the row the first time it is opened, with the same defaults.

Examples#

A bakery that counts its days in Stockholm#

app Bakery {
  use Osysharp.Identity@0;
  use Osysharp.TimeZone@0;
  use Osysharp.Ui;
  model "model/**/*.osy";
}

using Osysharp.Identity;
using Osysharp.TimeZone;

[Role] enum BakeryRole { Authenticator, Baker, Owner }
policy ManagesClock => RoleGrant.Any(g => g.Member == user && g.Role == BakeryRole.Owner);

app.Clock = new ClockSetup { TimeZone = TimeZoneInfo.FindSystemTimeZoneById("Europe/Stockholm") };

/// The date on the bakery's wall now.
public DateOnly TodayAtTheBakery() { return TheClock().DayOf(DateTime.UtcNow); }

/// When this week began there — the first day of the week, at midnight.
public DateTime ThisWeekBegan() {
  var clock = TheClock();
  return clock.StartOf(clock.WeekOf(clock.DayOf(DateTime.UtcNow)));
}

See also#

Related

Time zones — TimeZoneInfo

Store and use civil time zones with C#'s `TimeZoneInfo`. A zone holds an IANA id (`"Europe/Stockholm"`), and C#'s…

[Settings] and [Setting]: a settings section declared on its row, each field drawn by its type

Mark the row staff configure `[Settings("Title", Authorize = Policy)]` and each field on it `[Setting("Label")]`, and…

The Reports kit — reports a team builds, and the overview it opens every morning

`Osysharp.Reports` lets a team build its own reports — start from one thing the app keeps, pick fields by following its…

The Notifications kit — telling a person that something needs them

`Osysharp.Notifications` tells a person that something needs them — in the app, a bell with a live count and a list…

settings a package asks its app for (app settings)

A package — or an app — declares a setting by marking a class `[AppSetting("Name")]`. The app that uses it fills it in…