# Current time (DateTime.UtcNow, DurableClock.Now)

> Read the current instant. `DateTime.UtcNow` (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. `DateTime.Now` and `DateTime.Today` are C#'s local reads, and local is the APP's zone (`app.DefaultTimeZone`, UTC when none is declared) — never the host's.

<!-- id: function-current-time · area: function · stability: stable · html: https://osysharp.com/reference/function/current-time/ -->

## Summary        {#summary}
**`DateTime.UtcNow`** and **`DateTime.Today`** read the current instant, anywhere. Pasted C# that reads "now" runs
unchanged.

Inside a **workflow or durable body**, `DateTime.UtcNow` already reads 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. **`DurableClock.Now` /
`.UtcNow` / `.Today`** are the same read under a name that says so, and the only difference between the two is where
each is legal: `DurableClock` outside a durable execution fails loudly, because there is no engine instant for it to
promise. A body that is RE-ENTERED from the top — a `retry:` step's next attempt, a run moved to a new version — reads
either spelling afresh; an instant that must survive that is recorded in a step:
`var at = Workflow.Once("stamped-at", () => DateTime.UtcNow);`. `osy lint` points this out where it matters: the rule
`workflow-value-read-again-on-retry` fires when a clock read (or a `Guid.NewGuid()`) taken before a `retry:` step is
then written to a row, and suggests exactly that `Workflow.Once`. A read that is only logged or compared is not flagged.

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

| you want | write | where |
|---|---|---|
| the engine's instant, replay-stable | `DateTime.UtcNow`, or `DurableClock.UtcNow` to insist on it | a workflow or durable body |
| a one-shot reading of now | `DateTime.UtcNow` | anywhere |
| a value that KEEPS MOVING on screen | `DateTime.UtcNow` | anything that runs on a RENDER PASS — a `render` block, and any method render calls. See [below](#on-screen) |

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:

```osy syntax
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.

⚠ **`DateTime.Now` is LOCAL time, and local is the APP's zone** — exactly C#'s meaning, with the app standing where C#
has the machine. An app says which zone that is once, `app.DefaultTimeZone = TimeZoneInfo.FindSystemTimeZoneById("Europe/Stockholm");`,
and `DateTime.Now`, `DateTime.Today`, `DateTimeOffset.Now`, `d.ToLocalTime()` and `TimeZoneInfo.Local` all read in it —
on the server, in the browser and in a query alike. It is never the HOST's zone: a server runs in UTC wherever it is
deployed and a browser in whatever its machine is set to, so a host reading would make one app answer differently by
where it ran. An app that declares no zone reads its organization's time zone when deployed on Osyrin (the organization's
"Time zone" setting, from the next deploy), and UTC anywhere else — so locally `Now` and `UtcNow` are the same instant. For
somebody else's wall clock, name their zone: `TimeZoneInfo.ConvertTimeFromUtc(DateTime.UtcNow, zone)` ([Time zones — TimeZoneInfo](https://osysharp.com/reference/stdlib/zones/)).

### On screen: the clock that keeps moving        {#on-screen}

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:

```osy syntax
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      {#signature}
```osy syntax
DateTime.UtcNow                   // the current instant (UTC)
DateTime.Now                      // the wall clock in the app's zone (app.DefaultTimeZone; UTC when none)
DateTime.Today                    // that wall clock's date, at midnight
TimeZoneInfo.Local                // the app's zone itself
d.ToLocalTime()                   // an instant on the app's wall clock
DurableClock.Now / .UtcNow / .Today   // inside a workflow/durable body: the ENGINE's instant
DateTimeOffset.UtcNow             // the instant, at offset zero
DateTimeOffset.Now                // the instant, at the app's zone's offset
```

## Description    {#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** of a statement that suspended and resumed. (A body re-entered from the
top derives its instants afresh, as it re-runs everything not recorded in a step — see above.) 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**: a stored or computed `DateTime` is a UTC instant, because 9am in Frankfurt and
5pm in Tokyo are the same instant and that instant is what the value holds. **Local time is the app's zone** — what
`app.DefaultTimeZone` declares, UTC when it declares none — and `DateTime.Now` is that zone's wall clock, written as the
conversion it is (`TimeZoneInfo.ConvertTimeFromUtc(DateTime.UtcNow, TimeZoneInfo.Local)`), so it ticks on screen, follows
a pinned test clock, and means the same in a query as in memory. `DateTime.Today` is its date.

**Somebody else's local time is a question about that person** — so it is asked for by naming their zone, in one
line:

```osy syntax
var instant = DateTime.UtcNow;                                  // the fact
var local   = TimeZoneInfo.ConvertTimeFromUtc(instant, TimeZoneInfo.FindSystemTimeZoneById("Europe/Stockholm"));         // …read as somebody's wall clock
```

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

In a query they read the database's clock — or a test's pinned one — and `DateTime.Now` converts it to the app's zone
there, so `Slot.Where(s => s.At <= DateTime.Now)` counts the rows the same comparison counts in memory.

**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       {#examples}
```osy title="stamp a row, then ask how old it is" test app=function-current-time
entity Ticket {
  [Required, MaxLength(200)] string Title;
  DateTime DueAt;
  [CreatedAt] DateTime CreatedAt;   // the platform writes the instant the row was created
}

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 `[CreatedAt]` above: declared once, written by the platform, never assigned here.
  return DateTime.UtcNow - t.CreatedAt < TimeSpan.FromHours(1);       // TimeSpan arithmetic — see [TimeSpan (durations)](https://osysharp.com/reference/types/timespan/)
}
```

Local time is the app's zone — declared once, then `DateTime.Now` and `DateTime.Today` read in it, in a query too:

```osy title="local time is the app's zone" test app=function-current-time-local
app.DefaultTimeZone = TimeZoneInfo.FindSystemTimeZoneById("Europe/Stockholm");

entity Lesson {
  [Required, MaxLength(120)] string Title;
  DateTime StartsAt;
  security { allow read when IsAuthenticated; }
}

// Lessons starting later today on the school's own wall clock — the comparison runs in the database.
int LeftToday() {
  var endOfDay = TimeZoneInfo.ConvertTimeToUtc(DateTime.Today.AddDays(1), TimeZoneInfo.Local);
  return Lesson.Where(l => l.StartsAt >= DateTime.UtcNow && l.StartsAt < endOfDay).Count();
}

string ClockOnTheWall() { return DateTime.Now.ToString("HH:mm"); }
```

## See also       {#see-also}
- [Time zones — TimeZoneInfo](https://osysharp.com/reference/stdlib/zones/) — `TimeZoneInfo`: other people's zones, offsets, daylight saving
- [TimeSpan (durations)](https://osysharp.com/reference/types/timespan/) — durations, and `DateTime`/`TimeSpan` arithmetic
