Osy#the first language built for agents
Agents firstAgentic appsWorkflowsDurable Execution — built inSecurityTestingThe editorThe UI modelOne program

Reference / Function

Current time (DateTime.UtcNow, DurableClock.Now)

DateTime.UtcNow / DateTime.Today · DurableClock.Now / DurableClock.UtcNow / DurableClock.Today · DateTimeOffset.UtcNow

Read the current instant. `DateTime.UtcNow`/`Today` (and the platform-idiomatic `DurableClock.Now`/…) return the current time from the platform's replay-safe clock — so durable functions resume deterministically and any time-reading logic is time-travel-testable. All are UTC; there is no separate DateTimeOffset type, and `DateTime.Now` names the same instant as `UtcNow`. To read that instant as somebody's wall clock, name their zone.

stable1 example compiled by CIfunctiondatetimeclocktime

Summary#

DateTime.UtcNow and DateTime.Today read the current instant, anywhere. Pasted C# that reads "now" runs unchanged.

Inside a workflow or durable body, reach for DurableClock.Now / .UtcNow / .Today instead. They read the instant the ENGINE is processing at — the same one every clock armed on that pass derives from, and the one a pinned test clock moves. That is the whole difference: DateTime.UtcNow in a workflow body answers whatever the machine says right now, which is neither replay-stable nor testable.

There are three clocks in total, and each is legal exactly where it means something:

you wantwritewhere
the engine's instant, replay-stableDurableClock.UtcNowa workflow or durable body
a one-shot reading of nowDateTime.UtcNowanywhere
a value that KEEPS MOVING on screenDateTime.UtcNowanything that runs on a RENDER PASS — a render block, and any method render calls. See below

Reach for the wrong one and the compiler says which to pick: DurableClock in a render block is refused (it would freeze at first paint), and outside a durable execution it fails loudly rather than quietly reading wall time.

The boundary is WHEN THE CODE RUNS, not which body it is written in. Pulling a render expression out into a well-named method does not move it off the render path — it still runs on every pass, so it still wants DateTime.UtcNow, and a DateTime.UtcNow left behind there freezes the page exactly as it would have in the slot:

bool IsDue(Plant p, DateTime now) => p.LastWateredAt.AddDays(p.EveryDays) <= now;  // the instant is passed IN
// ⛔ NOT `bool IsDue(Plant p) => … <= DateTime.UtcNow;` — a clock read INSIDE a helper does not advance, and the
//    compiler refuses it from a `live var`: "Reactivity is decided where the read is WRITTEN, not where the
//    function is called." Read it at the call site — `items.Where(p => IsDue(p, DateTime.UtcNow))`.
action Water(Plant p) { p.LastWateredAt = DateTime.UtcNow; }              // runs once per press → the one-shot read

osy lint follows the calls, so it flags the first form written with DateTime.UtcNow and stays silent on a method only an action reaches.

Every Now spelling answers the same instantDateTime.Now, DateTime.UtcNow, DateTimeOffset.Now, DateTimeOffset.UtcNow. That is not an alias papering over a difference: this platform has no local-time DateTime for them to differ by. To read the instant as somebody's wall clock, say whose: DateTime.UtcNow.InZone(Zone.Of("Europe/Stockholm")). See Time zones — the Zone type and its operations.

On screen: the clock that keeps moving#

A reactive context — a live var or a render slot — subscribes to the clock, so a value derived from it counts down on screen with no refetch, no polling and no code beyond the expression:

live var left = deadline - DateTime.UtcNow;        // ✓ advances
var openedAt  = DateTime.UtcNow;                   // ✓ reads once — the instant the page mounted

Every reader on a page ticks together — one clock per page, not one per row — so two countdowns never disagree about what second it is. The cadence is one second; that is a display rate, not a precision claim. A page that never reads the clock starts no timer and costs nothing.

On screen it is a DISPLAY clock, never an authority. It is the browser's wall clock, so a viewer whose machine is skewed sees a skewed countdown:

  • Nothing may be decided on it. Whether an SLA breached, whether an offer expired, whether a token is still valid — those are settled on the server against the server's clock and arrive as data. Render that answer.
  • The honest division: the server decides what is true; the display clock animates an interval whose endpoint is already known.

Signature#

DateTime.UtcNow                   // the current instant (UTC)
DateTime.Now                      // the same instant — everything here is UTC
DateTime.Today                    // the current date at midnight (UTC)
DurableClock.Now / .UtcNow / .Today   // inside a workflow/durable body: the ENGINE's instant
DateTimeOffset.Now / .UtcNow      // yields a DateTime (there is no DateTimeOffset type)

Description#

The platform owns "now". Because a durable function can suspend and resume, a naive system clock would return a different value on resume and corrupt replay — so every current-time read goes through the platform clock, which returns the same instant on replay. The same mechanism makes time-reading logic time-travel-testable: a test can fix the clock and assert behavior at a chosen moment.

The platform is UTC-internal, and that is what makes every Now spelling mean one thing. A DateTime here is a UTC instant, not a wall-clock-plus-timezone reading, because a server has no single "local" zone to be correct for — 9am in Frankfurt and 5pm in Tokyo are the same instant, and that instant is what the value holds. So Now and UtcNow are not two readings the language collapses into one; there is only ever one reading, and both names say it. DateTimeOffset.Now/.UtcNow yield a plain DateTime (there is no separate DateTimeOffset type), and DateTime.Today is the current date at midnight.

"Local time" is a question about a person, not about the platform — which is why it is asked for rather than assumed. Naming the zone is the whole answer, and it is one line:

var instant = DateTime.UtcNow;                                  // the fact
var local   = instant.InZone(Zone.Of("Europe/Stockholm"));         // …read as somebody's wall clock

Stored and computed DateTimes stay UTC; the zone belongs at the display edge, where you know whose it is.

These are current-instant reads, so they run in memory (they are not deterministic SQL expressions).

Parsing and formatting. DateTime.Parse(s) turns a string into a DateTime (and DateOnly.Parse / TimeOnly.Parse / TimeSpan.Parse for the others); each throws on an unparseable string. value.ToString("fmt") formats a date/time/duration with a standard .NET/C# format string (the same engine as a $"{d:fmt}" interpolation hole) — e.g. d.ToString("yyyy-MM-dd"). Both are in-memory.

Examples#

entity Ticket {
  [Required, MaxLength(200)] string Title;
  DateTime DueAt;
}

Ticket Open(string title) {
  return new Ticket { Title = title, DueAt = DateTime.UtcNow + TimeSpan.FromDays(7) };   // faithful C# — the platform clock
}

bool IsOverdue(Ticket t) {
  return DateTime.UtcNow > t.DueAt;
}

bool OpenedRecently(Ticket t) {
  // `CreatedAt` is provided for you — every entity is audited automatically, so you never declare it.
  return DateTime.UtcNow - t.CreatedAt < TimeSpan.FromHours(1);       // TimeSpan arithmetic — see [TimeSpan (durations)](/reference/types/timespan/)
}

See also#

Related

TimeSpan (durations)

`TimeSpan` is the duration type — a length of time, as in C#. Build one with the `TimeSpan.FromX` factories or `new…